1919import org .slf4j .Logger ;
2020import org .slf4j .LoggerFactory ;
2121
22+ import javax .annotation .Nonnull ;
2223import java .io .ByteArrayOutputStream ;
2324import java .io .IOException ;
2425import java .io .InputStream ;
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}
0 commit comments