Skip to content

Commit 8018da8

Browse files
committed
Replaced static DataAccess with singleton DataModel
1 parent f0adb2c commit 8018da8

File tree

15 files changed

+226
-93
lines changed

15 files changed

+226
-93
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
[![Build Status](https://img.shields.io/travis/mapcode-foundation/mapcode-java.svg)](https://travis-ci.org/mapcode-foundation/mapcode-java)
44
[![Coverage Status](https://coveralls.io/repos/github/mapcode-foundation/mapcode-java/badge.svg?branch=master)](https://coveralls.io/github/mapcode-foundation/mapcode-java?branch=master)
5-
[![Release](https://img.shields.io/github/release/mapcode-foundation/mapcode-java.svg)](https://github.com/mapcode-foundation/mapcode-java/releases)
6-
[![Maven Central](https://img.shields.io/maven-central/v/com.mapcode/mapcode.svg)](https://maven-badges.herokuapp.com/maven-central/com.mapcode/mapcode)
5+
[![License](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)]()
6+
[![Release](https://img.shields.io/github/release/mapcode-foundation/mapcode-java.svg?maxAge=2592000)](https://github.com/mapcode-foundation/mapcode-java/releases)
7+
[![Maven Central](https://img.shields.io/maven-central/v/com.mapcode/mapcode.svg?maxAge=2592000)](https://maven-badges.herokuapp.com/maven-central/com.mapcode/mapcode)
78

89
**Copyright (C) 2014-2016 Stichting Mapcode Foundation (http://www.mapcode.com)**
910

src/main/java/com/mapcode/Boundary.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class Boundary {
3131
private int latMicroDegMin; // Minimum latitude (in microdegrees). Inclusive.
3232
private int latMicroDegMax; // Minimum latitude (in microdegrees). Exclusive.
3333

34+
private static final DataModel DATA_MODEL = DataModel.getInstance();
35+
3436
private Boundary(
3537
final int lonMicroDegMin,
3638
final int lonMicroDegMax,
@@ -46,10 +48,10 @@ private Boundary(
4648
@Nonnull
4749
static Boundary createFromTerritoryRecord(final int territoryRecord) {
4850
return new Boundary(
49-
DataAccess.getLonMicroDegMin(territoryRecord),
50-
DataAccess.getLonMicroDegMax(territoryRecord),
51-
DataAccess.getLatMicroDegMin(territoryRecord),
52-
DataAccess.getLatMicroDegMax(territoryRecord));
51+
DATA_MODEL.getLonMicroDegMin(territoryRecord),
52+
DATA_MODEL.getLonMicroDegMax(territoryRecord),
53+
DATA_MODEL.getLatMicroDegMin(territoryRecord),
54+
DATA_MODEL.getLatMicroDegMax(territoryRecord));
5355
}
5456

5557
int getLonMicroDegMin() {

src/main/java/com/mapcode/Data.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,20 @@ class Data {
3333
'A', 'E', 'U' // Vowels.
3434
};
3535

36+
private static final DataModel DATA_MODEL = DataModel.getInstance();
37+
3638
private Data() {
3739
// Disabled.
3840
}
3941

4042
static boolean isNameless(final int territoryRecord) {
41-
assert (0 <= territoryRecord) && (territoryRecord < DataAccess.getNrTerritoryRecords());
42-
return (DataAccess.getDataFlags(territoryRecord) & 64) != 0;
43+
assert (0 <= territoryRecord) && (territoryRecord < DATA_MODEL.getNrTerritoryRecords());
44+
return (DATA_MODEL.getDataFlags(territoryRecord) & 64) != 0;
4345
}
4446

4547
static boolean isSpecialShape(final int territoryRecord) {
46-
assert (0 <= territoryRecord) && (territoryRecord < DataAccess.getNrTerritoryRecords());
47-
return (DataAccess.getDataFlags(territoryRecord) & 1024) != 0;
48+
assert (0 <= territoryRecord) && (territoryRecord < DATA_MODEL.getNrTerritoryRecords());
49+
return (DATA_MODEL.getDataFlags(territoryRecord) & 1024) != 0;
4850
}
4951

5052
static final int TERRITORY_RECORD_TYPE_NONE = 0;
@@ -53,24 +55,24 @@ static boolean isSpecialShape(final int territoryRecord) {
5355
static final int TERRITORY_RECORD_TYPE_STAR = 3;
5456

5557
static int getTerritoryRecordType(final int territoryRecord) {
56-
assert (0 <= territoryRecord) && (territoryRecord < DataAccess.getNrTerritoryRecords());
57-
return (DataAccess.getDataFlags(territoryRecord) >> 7) & 3; // 1=pipe 2=plus 3=star
58+
assert (0 <= territoryRecord) && (territoryRecord < DATA_MODEL.getNrTerritoryRecords());
59+
return (DATA_MODEL.getDataFlags(territoryRecord) >> 7) & 3; // 1=pipe 2=plus 3=star
5860
}
5961

6062
static boolean isRestricted(final int territoryRecord) {
61-
assert (0 <= territoryRecord) && (territoryRecord < DataAccess.getNrTerritoryRecords());
62-
return (DataAccess.getDataFlags(territoryRecord) & 512) != 0;
63+
assert (0 <= territoryRecord) && (territoryRecord < DATA_MODEL.getNrTerritoryRecords());
64+
return (DATA_MODEL.getDataFlags(territoryRecord) & 512) != 0;
6365
}
6466

6567
static int getCodex(final int territoryRecord) {
66-
assert (0 <= territoryRecord) && (territoryRecord < DataAccess.getNrTerritoryRecords());
67-
final int codexflags = DataAccess.getDataFlags(territoryRecord) & 31;
68+
assert (0 <= territoryRecord) && (territoryRecord < DATA_MODEL.getNrTerritoryRecords());
69+
final int codexflags = DATA_MODEL.getDataFlags(territoryRecord) & 31;
6870
return (10 * (codexflags / 5)) + (codexflags % 5) + 1;
6971
}
7072

7173
@Nonnull
7274
static String headerLetter(final int i) {
73-
final int flags = DataAccess.getDataFlags(i);
75+
final int flags = DATA_MODEL.getDataFlags(i);
7476
if (((flags >> 7) & 3) == 1) {
7577
return Character.toString(ENCODE_CHARS[(flags >> 11) & 31]);
7678
}
Lines changed: 84 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.slf4j.Logger;
2020
import org.slf4j.LoggerFactory;
2121

22+
import javax.annotation.Nonnull;
2223
import java.io.ByteArrayOutputStream;
2324
import java.io.IOException;
2425
import java.io.InputStream;
@@ -30,14 +31,8 @@
3031
*
3132
* This class contains the module that reads the Mapcode areas into memory and processes them.
3233
*/
33-
class DataAccess {
34-
private static final Logger LOG = LoggerFactory.getLogger(DataAccess.class);
35-
36-
private static final int NR_TERRITORIES;
37-
private static final int NR_TERRITORY_RECORDS;
38-
39-
private static final int[] INDEX;
40-
private static final int[] DATA;
34+
class DataModel {
35+
private static final Logger LOG = LoggerFactory.getLogger(DataModel.class);
4136

4237
private static final int HEADER_ID_1 = 0;
4338
private static final int HEADER_ID_2 = 1;
@@ -65,18 +60,49 @@ class DataAccess {
6560
private static final int POS_INDEX_FIRST_RECORD = 0;
6661
private static final int POS_INDEX_LAST_RECORD = 1;
6762

68-
private static final String FILE_NAME = "/com/mapcode/mminfo.dat";
63+
private static final String DATA_FILE_NAME = "/com/mapcode/mminfo.dat";
6964
private static final int FILE_BUFFER_SIZE = 50000;
7065

7166
private static final int DATA_VERSION_MIN = 220;
7267

73-
// Read data only once in static initializer.
74-
static {
75-
LOG.info("DataAccess: reading regions from file: {}", FILE_NAME);
68+
private static int readIntLoHi(final int lo, final int hi) {
69+
return (lo & 0xff) + ((hi & 0xff) << 8);
70+
}
71+
72+
private static int readLongLoHi(final int lo, final int mid1, final int mid2, final int hi) {
73+
return ((lo & 0xff)) + ((mid1 & 0xff) << 8) + ((mid2 & 0xff) << 16) + ((hi & 0xff) << 24);
74+
}
75+
76+
// Data.
77+
private final int nrTerritories;
78+
private final int nrTerritoryRecords;
79+
80+
private final int[] index;
81+
private final int[] data;
82+
83+
@SuppressWarnings("StaticNonFinalField")
84+
private static volatile DataModel instance = null;
85+
private static final Object mutex = new Object();
86+
87+
@SuppressWarnings({"DoubleCheckedLocking", "SynchronizationOnStaticField"})
88+
public static DataModel getInstance() {
89+
if (instance == null) {
90+
synchronized (mutex) {
91+
if (instance == null) {
92+
instance = new DataModel(DATA_FILE_NAME);
93+
}
94+
}
95+
}
96+
return instance;
97+
}
98+
99+
DataModel(@Nonnull final String fileName) throws IncorrectDataModelException {
100+
// Read data only once in static initializer.
101+
LOG.info("DataAccess: reading regions from file: {}", fileName);
76102
final byte[] readBuffer = new byte[FILE_BUFFER_SIZE];
77103
int total = 0;
78104
try {
79-
final InputStream inputStream = DataAccess.class.getResourceAsStream(FILE_NAME);
105+
final InputStream inputStream = DataModel.class.getResourceAsStream(fileName);
80106
try {
81107
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
82108
try {
@@ -94,49 +120,53 @@ class DataAccess {
94120
assert total == bytes.length;
95121
if (total < 12) {
96122
LOG.error("DataAccess: expected more than {} bytes", total);
97-
throw new IllegalStateException("Data file corrupt: " + FILE_NAME);
123+
throw new IncorrectDataModelException("Data file corrupt: " + fileName);
98124
}
99125

100126
// Read "MC", VERSION.
101127
assert total > 8; // "MC" (2) + VERSION (2) + NR TERRITORIES (2) + NR TERRITORY RECORDS (2).
102-
assert (char) bytes[HEADER_ID_1] == 'M';
103-
assert (char) bytes[HEADER_ID_2] == 'C';
128+
if ((bytes[HEADER_ID_1] != 'M') || (bytes[HEADER_ID_2] != 'C')) {
129+
throw new IncorrectDataModelException("Data file does not start with correct header: " + fileName);
130+
}
104131
final int dataVersion = readIntLoHi(bytes[HEADER_VERSION_LO], bytes[HEADER_VERSION_HI]);
105-
assert dataVersion >= DATA_VERSION_MIN;
132+
133+
if (dataVersion < DATA_VERSION_MIN) {
134+
throw new IncorrectDataModelException("Data file version " + dataVersion + " too low: " + fileName);
135+
}
106136

107137
// Read header: NR TERRITORIES, NR RECTANGLE RECORDS.
108-
NR_TERRITORY_RECORDS = readIntLoHi(bytes[HEADER_NR_TERRITORIES_RECS_LO], bytes[HEADER_NR_TERRITORIES_RECS_HI]);
109-
NR_TERRITORIES = readIntLoHi(bytes[HEADER_NR_TERRITORIES_LO], bytes[HEADER_NR_TERRITORIES_HI]);
138+
nrTerritoryRecords = readIntLoHi(bytes[HEADER_NR_TERRITORIES_RECS_LO], bytes[HEADER_NR_TERRITORIES_RECS_HI]);
139+
nrTerritories = readIntLoHi(bytes[HEADER_NR_TERRITORIES_LO], bytes[HEADER_NR_TERRITORIES_HI]);
110140

111141
// Check if the number of territories matches the enumeration in Territory.
112-
if (NR_TERRITORIES != Territory.values().length) {
113-
LOG.error("DataAccess: expected {} territories, got {}", Territory.values().length, NR_TERRITORIES);
114-
throw new IllegalStateException("Data file corrupt: " + FILE_NAME);
142+
if (nrTerritories != Territory.values().length) {
143+
LOG.error("DataAccess: expected {} territories, got {}", Territory.values().length, nrTerritories);
144+
throw new IncorrectDataModelException("Data file corrupt: " + fileName);
115145
}
116146

117147
// Check if the expected file size matched what we found.
118148
final int expectedSize = HEADER_SIZE +
119-
((NR_TERRITORIES + 1) * BYTES_PER_INT) +
120-
(NR_TERRITORY_RECORDS * (DATA_FIELDS_PER_REC * BYTES_PER_LONG));
149+
((nrTerritories + 1) * BYTES_PER_INT) +
150+
(nrTerritoryRecords * (DATA_FIELDS_PER_REC * BYTES_PER_LONG));
121151

122152
if (expectedSize != total) {
123153
LOG.error("DataAccess: expected {} bytes, got {}", expectedSize, total);
124-
throw new IllegalStateException("Data file corrupt: " + FILE_NAME);
154+
throw new IncorrectDataModelException("Data file corrupt: " + fileName);
125155
}
126-
LOG.debug("DataAccess: version={} territories={} territory records={}", dataVersion, NR_TERRITORIES, NR_TERRITORY_RECORDS);
156+
LOG.debug("DataAccess: version={} territories={} territory records={}", dataVersion, nrTerritories, nrTerritoryRecords);
127157

128158
// Read DATA+START array (2 bytes per territory, plus closing record).
129-
INDEX = new int[NR_TERRITORIES + 1];
159+
index = new int[nrTerritories + 1];
130160
int i = HEADER_SIZE;
131-
for (int k = 0; k <= NR_TERRITORIES; k++) {
132-
INDEX[k] = readIntLoHi(bytes[i], bytes[i + 1]);
161+
for (int k = 0; k <= nrTerritories; k++) {
162+
index[k] = readIntLoHi(bytes[i], bytes[i + 1]);
133163
i += 2;
134164
}
135165

136166
// Read territory rectangle data (DATA_FIELDS_PER_REC longs per record).
137-
DATA = new int[NR_TERRITORY_RECORDS * DATA_FIELDS_PER_REC];
138-
for (int k = 0; k < (NR_TERRITORY_RECORDS * DATA_FIELDS_PER_REC); k++) {
139-
DATA[k] = readLongLoHi(bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]);
167+
data = new int[nrTerritoryRecords * DATA_FIELDS_PER_REC];
168+
for (int k = 0; k < (nrTerritoryRecords * DATA_FIELDS_PER_REC); k++) {
169+
data[k] = readLongLoHi(bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]);
140170
i += 4;
141171
}
142172
} finally {
@@ -148,74 +178,62 @@ class DataAccess {
148178
inputStream.close();
149179
}
150180
} catch (final IOException e) {
151-
throw new ExceptionInInitializerError("Cannot initialize static data structure from: " +
152-
FILE_NAME + ", exception=" + e);
181+
throw new IncorrectDataModelException("Cannot initialize static data structure from: " +
182+
fileName + ", exception=" + e);
153183
}
154184
LOG.info("DataAccess: regions initialized, read {} bytes", total);
155185
}
156186

157-
private static int readIntLoHi(final int lo, final int hi) {
158-
return (lo & 0xff) + ((hi & 0xff) << 8);
159-
}
160-
161-
private static int readLongLoHi(final int lo, final int mid1, final int mid2, final int hi) {
162-
return ((lo & 0xff)) + ((mid1 & 0xff) << 8) + ((mid2 & 0xff) << 16) + ((hi & 0xff) << 24);
163-
}
164-
165-
private DataAccess() {
166-
// Empty.
167-
}
168-
169187
/**
170188
* Get number of territories.
171189
*
172190
* @return Number of territories.
173191
*/
174-
static int getNrTerritories() {
175-
return NR_TERRITORIES;
192+
int getNrTerritories() {
193+
return nrTerritories;
176194
}
177195

178196
/**
179197
* Get number of territory records (rectangles per territory).
180198
*
181199
* @return Number of rectangles per territory.
182200
*/
183-
static int getNrTerritoryRecords() {
184-
return NR_TERRITORY_RECORDS;
201+
int getNrTerritoryRecords() {
202+
return nrTerritoryRecords;
185203
}
186204

187205
@SuppressWarnings("PointlessArithmeticExpression")
188-
static int getLonMicroDegMin(final int territoryRecord) {
189-
return DATA[((territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_LON_MICRO_DEG_MIN)];
206+
int getLonMicroDegMin(final int territoryRecord) {
207+
return data[((territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_LON_MICRO_DEG_MIN)];
190208
}
191209

192-
static int getLatMicroDegMin(final int territoryRecord) {
193-
return DATA[(territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_LAT_MICRO_DEG_MIN];
210+
int getLatMicroDegMin(final int territoryRecord) {
211+
return data[(territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_LAT_MICRO_DEG_MIN];
194212
}
195213

196-
static int getLonMicroDegMax(final int territoryRecord) {
197-
return DATA[(territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_LON_MICRO_DEG_MAX];
214+
int getLonMicroDegMax(final int territoryRecord) {
215+
return data[(territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_LON_MICRO_DEG_MAX];
198216
}
199217

200-
static int getLatMicroDegMax(final int territoryRecord) {
201-
return DATA[(territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_LAT_MICRO_DEG_MAX];
218+
int getLatMicroDegMax(final int territoryRecord) {
219+
return data[(territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_LAT_MICRO_DEG_MAX];
202220
}
203221

204-
static int getDataFlags(final int territoryRecord) {
205-
return DATA[(territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_DATA_FLAGS] & MASK_DATA_DATA_FLAGS;
222+
int getDataFlags(final int territoryRecord) {
223+
return data[(territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_DATA_FLAGS] & MASK_DATA_DATA_FLAGS;
206224
}
207225

208-
static int getSmartDiv(final int territoryRecord) {
209-
return DATA[(territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_DATA_FLAGS] >> SHIFT_POS_DATA_SMART_DIV;
226+
int getSmartDiv(final int territoryRecord) {
227+
return data[(territoryRecord * DATA_FIELDS_PER_REC) + POS_DATA_DATA_FLAGS] >> SHIFT_POS_DATA_SMART_DIV;
210228
}
211229

212230
// Low-level routines for data access.
213231
@SuppressWarnings("PointlessArithmeticExpression")
214-
static int getDataFirstRecord(final int territoryNumber) {
215-
return INDEX[territoryNumber + POS_INDEX_FIRST_RECORD];
232+
int getDataFirstRecord(final int territoryNumber) {
233+
return index[territoryNumber + POS_INDEX_FIRST_RECORD];
216234
}
217235

218-
static int getDataLastRecord(final int territoryNumber) {
219-
return INDEX[territoryNumber + POS_INDEX_LAST_RECORD] - 1;
236+
int getDataLastRecord(final int territoryNumber) {
237+
return index[territoryNumber + POS_INDEX_LAST_RECORD] - 1;
220238
}
221239
}

src/main/java/com/mapcode/Decoder.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class Decoder {
2828

2929
private static final char GREEK_CAPITAL_ALPHA = '\u0391';
3030

31+
private static final DataModel dataModel = DataModel.getInstance();
32+
3133
private Decoder() {
3234
// Prevent instantiation.
3335
}
@@ -81,8 +83,8 @@ static Point decode(@Nonnull final String argMapcode,
8183

8284
final int ccode = territory.getNumber();
8385

84-
final int fromTerritoryRecord = DataAccess.getDataFirstRecord(ccode);
85-
final int uptoTerritoryRecord = DataAccess.getDataLastRecord(ccode);
86+
final int fromTerritoryRecord = dataModel.getDataFirstRecord(ccode);
87+
final int uptoTerritoryRecord = dataModel.getDataLastRecord(ccode);
8688

8789
final int incodexhi = mapcode.indexOf('.');
8890
final int incodex = (incodexhi * 10) + (incodexlen - incodexhi);
@@ -290,7 +292,7 @@ private static MapcodeZone decodeGrid(final String str, final int minx, final in
290292

291293
final int divx;
292294
int divy;
293-
divy = DataAccess.getSmartDiv(m);
295+
divy = dataModel.getSmartDiv(m);
294296
if (divy == 1) {
295297
divx = Common.xSide[prelen];
296298
divy = Common.ySide[prelen];
@@ -434,7 +436,7 @@ private static MapcodeZone decodeNameless(final String str, final int firstrec,
434436

435437
final int territoryRecord = firstrec + nrX;
436438

437-
int side = DataAccess.getSmartDiv(territoryRecord);
439+
int side = dataModel.getSmartDiv(territoryRecord);
438440
int xSIDE = side;
439441

440442
Boundary boundary = createFromTerritoryRecord(territoryRecord);

0 commit comments

Comments
 (0)