001 /*
002 Copyright (c) 1996-2012, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029 package org.hd.d.pg2k.svrCore.location;
030
031 import java.util.ArrayList;
032 import java.util.Collections;
033 import java.util.Enumeration;
034 import java.util.HashMap;
035 import java.util.Map;
036 import java.util.Properties;
037 import java.util.Set;
038 import java.util.StringTokenizer;
039
040 import org.hd.d.pg2k.svrCore.ExhibitName;
041 import org.hd.d.pg2k.svrCore.Name;
042 import org.hd.d.pg2k.svrCore.PGException;
043 import org.hd.d.pg2k.svrCore.TextUtils;
044 import org.hd.d.pg2k.svrCore.collections.LRUMapAutoSizeForHitRate;
045
046 // TODO: optimise serialised form on the wire (and in cache).
047 // TODO: optimise prefix sharing (to minimise memory use) between Names in this DB.
048
049 /**
050 * Created by IntelliJ IDEA.
051 * User: Damon Hart-Davis
052 * Date: 28-Sep-2003
053 * Time: 13:45:37
054 *
055 * Based on pre-PG2K code from ImageInfoCache.
056 */
057
058 /**Holds a prefix-based lookup map from name to Location.
059 * Is immutable.
060 * <p>
061 * FIXME: needs proper serialisation support such as read/write/validation.
062 */
063 public final class LocationMap implements java.io.Serializable
064 {
065 /**Are location area prefixes case-sensitive? */
066 private static final boolean LOCAREA_PREFIX_CASE_SENSITIVE = false;
067
068 /**Location key prefix for properties (containing trailing dot). */
069 private static final String locKeyPrefix = "location.";
070
071 /**Location alias key prefix for properties (containing trailing dot). */
072 private static final String locAliasKeyPrefix = "localias.";
073
074 /**Timestamp passed in constructor; never negative.
075 * Guaranteed zero if the map is empty.
076 */
077 public final long timestamp;
078
079 /**Empty, immutable, zero-timestamp, lookup map. */
080 public LocationMap() { this(new Properties(), 0); }
081
082
083 /**Two LocationMaps are equal if the underlying maps are. */
084 @Override
085 public boolean equals(final Object obj)
086 {
087 if(!(obj instanceof LocationMap)) { return(false); }
088 return(mapFromNamePrefixToLocation.equals(
089 ((LocationMap) obj).mapFromNamePrefixToLocation));
090 }
091
092 /**Hash code is that of the underlying map and zero if empty. */
093 @Override
094 public int hashCode()
095 { return(mapFromNamePrefixToLocation.hashCode()); }
096
097
098 /**Constructed from non-null Properties containing auxInfo data. */
099 public LocationMap(final Properties p, final long timestamp)
100 {
101 if((timestamp < 0) || (p == null))
102 { throw new IllegalArgumentException(); }
103
104 mapFromNamePrefixToLocation = Collections.unmodifiableMap(buildMap(p));
105 this.timestamp = ((mapFromNamePrefixToLocation.isEmpty())) ? 0 : timestamp;
106
107 // Size lookup cache in proportion to memory already used
108 // and to allow for plenty of negative results to be cached too.
109 // Note that 2*sizeMFNPTL is typically more than is ever used...
110 // We're prepared to discard this entirely under acute memory stress.
111 final int sizeMFNPTL = mapFromNamePrefixToLocation.size();
112 lookupCache = LRUMapAutoSizeForHitRate.<Name, Location.Base>create(0.01f, 0, 11 + 16*sizeMFNPTL, 0.75f, "lookupCache");
113
114 // Store reverse-lookup parameters if present and valid.
115 final String rls = p.getProperty(PNAME_REVERSE_LOOKUP_SECTION);
116 revLookupSection = (!ExhibitName.validNameInitialComponentSyntax(rls)) ? null :
117 Name.create(LOCAREA_PREFIX_CASE_SENSITIVE ? rls : rls.toLowerCase());
118 int minWords = -1;
119 try { minWords = Integer.parseInt(p.getProperty(PNAME_REVERSE_LOOKUP_MINWORDS), 10); }
120 catch(final NumberFormatException e) { }
121 revLookupMinWords = minWords;
122
123 // Prepare the reverse map...
124 final Map<Location.Base,Name> revMap = new HashMap<Location.Base, Name>(sizeMFNPTL);
125 final Set<Map.Entry<Name,Location.Base>> entries = mapFromNamePrefixToLocation.entrySet();
126 for(final Map.Entry<Name,Location.Base> entry : entries)
127 {
128 final Name fwdKey = entry.getKey();
129 // If we have a preferred reverse-lookup section
130 // then discard keys starting with anything else.
131 if((revLookupSection != null) && !TextUtils.startsWith(fwdKey, revLookupSection))
132 { continue; }
133 final Name extant = revMap.get(entry.getValue());
134 if(extant == null)
135 {
136 revMap.put(entry.getValue(), fwdKey);
137 continue;
138 }
139 System.err.println("WARNING: LocationMap: duplicate keys: " + extant + " and " + fwdKey);
140 if(extant.length() > fwdKey.length())
141 {
142 // Keep the shorter key.
143 revMap.put(entry.getValue(), fwdKey);
144 }
145 }
146 mapFromLocationToNamePrefix = Collections.unmodifiableMap(revMap);
147 }
148
149 /**Immutable Map from Name prefix to Location.Base; never null.
150 * Map from "virtual" exhibit name (prefix) to Location object
151 * so that we can reuse previously generated objects.
152 * The exhibit name key consists of the top-level section/directory
153 * and some leading portion of the name;
154 * no intermediate directories are stored.
155 * <p>
156 * A HashMap is preferred for speed.
157 */
158 private final Map<Name,Location.Base> mapFromNamePrefixToLocation;
159
160 /**Reverse section used for reverse matches, or null if none. */
161 private final Name revLookupSection;
162
163 /**Minimum number of words matched for reverse matches, or non-positive if none. */
164 private final int revLookupMinWords;
165
166 /**Immutable reverse map from Location to prefix (as Name); may remove some duplicates.
167 * Must be reconstructed after deserialisation.
168 */
169 private final transient Map<Location.Base,Name> mapFromLocationToNamePrefix;
170
171 /**Get immutable map from "virtual" prefix to Location.Base (as Name); never null.
172 * We return the internal immutable map.
173 */
174 public Map<Name,Location.Base> getMapFromNamePrefixToLocation()
175 { return(mapFromNamePrefixToLocation); }
176
177 /**Get immutable reverse map from Location.Base to "virtual" prefix (as Name); never null.
178 * We return the internal immutable map.
179 * <p>
180 * Note that Location values have to be intern()ed or must compare
181 * exactly for equality for this map to be of use.
182 */
183 public Map<Location.Base,Name> getMapFromLocationToNamePrefix()
184 { return(mapFromLocationToNamePrefix); }
185
186 /**Property name for section to do reverse lookups in. */
187 public static final String PNAME_REVERSE_LOOKUP_SECTION = "_reverse_match_.section";
188
189 /**Property name for minimum words to match for a reverse lookup. */
190 public static final String PNAME_REVERSE_LOOKUP_MINWORDS = "_reverse_match_.minWords";
191
192 /**Build the basic map from prefix (as Name) to Location; never null.
193 * The result may be mutable.
194 *
195 * @param rawProperties properties to extract the data from; never null
196 */
197 private static Map<Name,Location.Base> buildMap(final Properties rawProperties)
198 {
199 if(rawProperties == null)
200 { throw new IllegalArgumentException(); }
201
202 // We use a HashMap for lookup speed.
203 final Map<Name,Location.Base> result = new HashMap<Name,Location.Base>(1 + rawProperties.size());
204
205 // Create a temporary lookup cache from full exhibit name to location.
206 final LRUMapAutoSizeForHitRate<Name, Location.Base> cache = LRUMapAutoSizeForHitRate.<Name, Location.Base>create(0.01f, 1+rawProperties.size(), "LocationMap.buildMap() cache");
207
208 // Get property names in sorted order
209 // to help generate more efficient internal representation.
210 final ArrayList<String> propertyNames = (ArrayList<String>)Collections.list(rawProperties.propertyNames());
211 Collections.sort(propertyNames);
212
213 // 'prev' Name key value for attempting to enhance prefix sharing and reduce memory footprint.
214 Name prev = null;
215 Name.ExhibitFull prevEF = null;
216
217 // First deal with the basic location properties.
218 for(final String key : propertyNames)
219 {
220 // If this is not a location key, ignore it.
221 if(!key.startsWith(locKeyPrefix)) { continue; }
222 // OK, parse it, skipping the prefix.
223 final StringTokenizer st = new StringTokenizer(
224 key.substring(locKeyPrefix.length()), ".");
225 // Abort if not at least two tokens
226 // (the location and a key for Location to interpret).
227 if(st.countTokens() < 2)
228 {
229 System.err.println("[WARNING: malformed auxInfo key ``" + key + "'': too few components to be valid.]");
230 continue;
231 }
232 // Retrieve the prefix and stop if we already
233 // have an entry for it in result.
234 final String synthLocPrefix = st.nextToken();
235 final Name slKey = Name.create(LOCAREA_PREFIX_CASE_SENSITIVE ?
236 synthLocPrefix : synthLocPrefix.toLowerCase(),
237 prev);
238 prev = slKey;
239 if(result.get(slKey) != null) { continue; }
240 // If the embedded key does not end with a '-' (or '/'), complain...
241 if(!synthLocPrefix.endsWith(ExhibitName.WORD_SEPS) &&
242 !synthLocPrefix.endsWith("/"))
243 {
244 System.err.println("[WARNING: malformed auxInfo key ``" + key + "'': no trailing '"+ExhibitName.WORD_SEP+"' (or '/') in "+synthLocPrefix+".]");
245 continue;
246 }
247 // Now attempt to read the (generic) location and
248 // put it in the result.
249 try {
250 final Location.Base lb = Location.Base.buildFromProperties(
251 false, locKeyPrefix + synthLocPrefix + '.', rawProperties);
252 result.put(slKey, lb);
253 }
254 catch(final PGException e)
255 {
256 System.err.println("[WARNING: malformed auxInfo location info for ``" + synthLocPrefix + "'': " + e.getMessage() + ".]");
257 continue;
258 }
259 }
260
261 // Now deal with any location aliases.
262 for(final String key : propertyNames)
263 {
264 // If this is not a location key, ignore it.
265 if(!key.startsWith(locAliasKeyPrefix)) { continue; }
266 // OK, parse it, ignoring the prefix.
267 // If the key does not end with a '-' (or a '/'), complain...
268 if(!key.endsWith(ExhibitName.WORD_SEPS) &&
269 !key.endsWith("/"))
270 {
271 System.err.println("[WARNING: malformed auxInfo key ``" + key + "'': no trailing '"+ExhibitName.WORD_SEP+"' (or '/').]");
272 continue;
273 }
274 final StringTokenizer st = new StringTokenizer(
275 key.substring(locAliasKeyPrefix.length()), ".");
276 // Abort if not exactly one token.
277 // (the name to be aliased from).
278 if(st.countTokens() != 1)
279 {
280 System.err.println("[WARNING: malformed auxInfo key ``" + key + "'': too few components to be valid.]");
281 continue;
282 }
283
284 // Retrieve the prefix and stop if we already
285 // have an entry for it in result.
286 final String synthLocPrefix = st.nextToken();
287 final Name slKey = Name.create(LOCAREA_PREFIX_CASE_SENSITIVE ?
288 synthLocPrefix : synthLocPrefix.toLowerCase(),
289 prev);
290 prev = slKey;
291 if(result.get(slKey) != null)
292 {
293 System.err.println("[WARNING: duplicate location alias key ``" + key + "''.]");
294 continue;
295 }
296
297 // Now attempt to look up the pointed-to location
298 // and file our duplicate key/link to it.
299 final String target = rawProperties.getProperty(key);
300 // If the target does not end with a "-", complain...
301 if(!target.endsWith(ExhibitName.WORD_SEPS))
302 {
303 System.err.println("[WARNING: malformed auxInfo key target ``" + key + "'': no trailing '"+ExhibitName.WORD_SEP+"'.]");
304 continue;
305 }
306 final Name.ExhibitFull synthName;
307 try {
308 synthName = Name.ExhibitFull.create(
309 target + (target.endsWith("-") ? "" : "-") +
310 "DHD.jpg", // Fake suffix to make legal full exhibit name.
311 prevEF);
312 prevEF = synthName;
313 }
314 catch(final IllegalArgumentException e)
315 {
316 e.printStackTrace();
317 System.err.println("[ERROR: problem with location alias: key ``" + key + "'': " + e.getMessage() + ".]");
318 continue;
319 }
320
321 //System.err.println("[Looking up Location alias key ``" + key + "'' as ``"+ synthName +"''.]");
322
323 // Do lookup (ignoring effect of attribute words).
324 final Location.Base lb = _locLookup(result,
325 synthName,
326 Collections.<String>emptySet(),
327 null,
328 -1,
329 cache);
330 if(lb == Location.NONE)
331 {
332 System.err.println("[ERROR: location alias key ``" + key + "'' does not point to a valid location (is dangling).]");
333 continue;
334 }
335
336 //System.err.println("[Valid Location alias key ``" + key + "''.]");
337
338 // Add duplicate.
339 result.put(slKey, lb);
340 }
341
342 return(result);
343 }
344
345
346
347 /**Lookup of Location by prefix; never null.
348 * Returns Location.NONE if nothing found, never null.
349 *
350 * @param exhibitName name of exhibit to look up; not null
351 * @param allAttrWords attribute words; not null
352 */
353 public Location.Base locLookup(final Name.ExhibitFull exhibitName,
354 final Set<String> allAttrWords)
355 {
356 return(_locLookup(mapFromNamePrefixToLocation,
357 exhibitName,
358 allAttrWords,
359 revLookupSection,
360 revLookupMinWords,
361 lookupCache));
362 }
363
364 /**Private LRU lookup cache from full name or prefix to location; never null.
365 * Thread-safe and sized in proportion to the size of our main map,
366 * allowing for lots of negative lookups too.
367 * <p>
368 * Not part of the serialised state of the object.
369 * <p>
370 * Results are filed against keys that are both:
371 * <ul>
372 * <li>Exhibit names, for speed ie without needing any string manipulation.
373 * <li>Normalised full forward/reverse names stripped of attribute words
374 * and monocased as appropriate.
375 * </ul>
376 */
377 private final transient LRUMapAutoSizeForHitRate<Name,Location.Base> lookupCache;
378
379 /**Lookup of Location by prefix/suffix in given Map; never null.
380 * Returns Location.NONE if nothing found, never null.
381 *
382 * @param exhibitName full name of exhibit to look up; not null
383 * @param allAttrWords set of all attribute words
384 * (if empty, canonical-key entries are not used); never null
385 * @param cache writable auto-sizing cache map from full or prefix of exhibit name to location
386 */
387 private static Location.Base _locLookup(final Map<Name,Location.Base> nameToLoc,
388 final Name.ExhibitFull exhibitName,
389 final Set<String> allAttrWords,
390 final Name revLookupSection,
391 final int revLookupMinWords,
392 final LRUMapAutoSizeForHitRate<Name, Location.Base> cache)
393 {
394 // Try cache lookup on full name for positive and negative results.
395 final Location.Base fromCache = cache.get(exhibitName);
396 if(fromCache != null) { return(fromCache); }
397
398 // Convert the final portion of the exhibit path
399 // to lower-case for comparison.
400 // Note that we are discarding attributes here.
401 // Note the addition of a trailing '-' to ensure
402 // that we match any prefix that should exactly
403 // match our main text.
404 final CharSequence mainWords = exhibitName.getShortName().getMainWordsComponent(allAttrWords).toString();
405
406 // We need to be careful to lowercase our collection-name key
407 // if everything else is being lowercased.
408 final String collection = ExhibitName.getCategoryComponent(exhibitName).toString();
409 final String collNameKey = (LOCAREA_PREFIX_CASE_SENSITIVE ?
410 collection : collection.toLowerCase());
411
412 // Construct the canonical "virtual" name stem.
413 final Name searchKeyFwd = Name.create(collNameKey + '/' +
414 (LOCAREA_PREFIX_CASE_SENSITIVE ? mainWords : mainWords.toString().toLowerCase()) + '-');
415
416 final Location.Base fc1 = cache.get(searchKeyFwd);
417 // If we have a negative result then we won't try a fwd lookup.
418 final boolean dontTryFwd = Location.NONE.equals(fc1);
419 if(!dontTryFwd)
420 {
421 // If we got a positive response then return it immediately.
422 if(fc1 != null) { return(fc1); }
423
424 // fullLocKey is the lookup key for the Location data,
425 // or null if we found nothing suitable.
426 Name fullLocKey = null;
427
428 // We can choose one of two algorithms to search
429 // for a longest match in the locations map:
430 //
431 // 1) Look through all the keys in the database in
432 // turn (in random order).
433 //
434 // 2) Look for matches for successively shorter
435 // prefixes of our (main text) name.
436 //
437 // The advantages of (2) are its fixed upper bound
438 // on search time (dependent on the name of the
439 // item to be looked up) and its simplicity.
440 //System.err.println("[Searching directly for location by prefix of " + exhibitName.mainText + "; db.size() = " + mapFromNamePrefixToLocation.size() + ".]");
441
442 // Search for successively shorter prefixes,
443 // stripping off one trailing word each time round the loop.
444 // The first found, if any, is taken to be the best.
445 for(Name lookupKeyFwd = searchKeyFwd; ; )
446 {
447 // assert(lookupKeyFwd.endsWith(ExhibitName.WORD_SEPS));
448 // Now try for the collection-specific.
449 final Location.Base loc = nameToLoc.get(lookupKeyFwd);
450 if(loc != null)
451 {
452 // Got it!
453 fullLocKey = lookupKeyFwd;
454 break;
455 }
456
457 // Try to find the penultimate word separator
458 // and retain it at the end of the new shorter key.
459 final int pl = TextUtils.lastIndexOf(lookupKeyFwd, ExhibitName.WORD_SEP, lookupKeyFwd.length()-2);
460 if(pl == -1) { break; }
461 lookupKeyFwd = Name.create(lookupKeyFwd.subSequence(0, pl+1), lookupKeyFwd);
462 }
463
464 // If we found a match then return it now.
465 if(fullLocKey != null)
466 {
467 //System.out.println("[Found location prefix match ``"+fullLocKey+"'' for ``"+exhibitName.mainText+"''.]");
468 final Location.Base result = nameToLoc.get(fullLocKey);
469 assert(result != null);
470 assert(result != Location.NONE);
471 // Cache result under full name.
472 cache.put(exhibitName, result);
473 // Record this positive result against the canonical key.
474 cache.put(searchKeyFwd, result);
475 return(result);
476 }
477 // Else cache a negative result for this canonical forward key.
478 else
479 { cache.put(searchKeyFwd, Location.NONE); }
480 }
481
482
483 // Try reverse lookup...
484 // but not for exhibits already in the target section.
485 // We will only use trailing capitalised words for reverse lookup.
486 if((revLookupMinWords > 0) && (revLookupSection != null) &&
487 !revLookupSection.equals(collNameKey))
488 {
489 // Build the new key in reverse order of main words.
490 final StringBuilder sb = new StringBuilder(mainWords.length());
491 int words = 0;
492 for(final Enumeration wordsEn = ExhibitName.getMainWords(exhibitName, allAttrWords); wordsEn.hasMoreElements(); )
493 {
494 final String word = (String) wordsEn.nextElement();
495 // If this word does not have an initial capital,
496 // reset the collected component.
497 // Only trailing capitalised words are used.
498 final char firstLetter = word.charAt(0);
499 if((firstLetter < 'A') || (firstLetter > 'Z'))
500 {
501 sb.setLength(0);
502 words = 0;
503 continue;
504 }
505
506 sb.insert(0, '-');
507 sb.insert(0, word);
508 ++words;
509 }
510 final Name searchKeyRev = Name.create(revLookupSection.toString() + '/' +
511 (LOCAREA_PREFIX_CASE_SENSITIVE ? sb : sb.toString().toLowerCase()),
512 revLookupSection); // TODO: find better 'prev' value to share prefix with.
513
514 // Start off looking for the entire canonical key.
515 //System.out.println("Reverse key is "+searchKeyRev+" from exhibit " + exhibitName);
516
517 final Location.Base fc2 = cache.get(searchKeyRev);
518 // If we have a negative result then we won't try a fwd lookup.
519 final boolean dontTryRev = Location.NONE.equals(fc2);
520 if(!dontTryRev)
521 {
522 // If we got a positive response then return it immediately.
523 if(fc2 != null) { return(fc2); }
524
525 // fullLocKey is the lookup key for the Location data,
526 // or null if we found nothing suitable.
527 Name fullLocKey = null;
528
529 // Search for successively shorter prefixes,
530 // removing one whole word at a time...
531 // The first/longest found, if any, is taken to be the best.
532 // Stop when there are too few words left
533 // to meet our minimum-match-length restriction.
534 for(Name lookupKeyRev = searchKeyRev; words-- >= revLookupMinWords; )
535 {
536 // assert(lookupKeyRev.endsWith(ExhibitName.WORD_SEPS)) : ("Key should end with dash: " + lookupKeyRev);
537 final Location.Base loc = nameToLoc.get(lookupKeyRev);
538 //System.out.println("Lookup (rev) of "+lookupKeyRev+" yields "+loc+ " (words="+(words+1)+")...");
539 if(loc != null)
540 {
541 // Got it!
542 fullLocKey = lookupKeyRev;
543 //System.out.println("Reverse lookup found "+lookupKeyRev+" for exhibit " + exhibitName);
544 break;
545 }
546
547 // Try to find the penultimate word separator
548 // and retain it at the end of the new shorter key.
549 final int pl = TextUtils.lastIndexOf(lookupKeyRev, ExhibitName.WORD_SEP, lookupKeyRev.length()-2);
550 if(pl == -1) { break; }
551 lookupKeyRev = Name.create(lookupKeyRev.subSequence(0, pl+1), lookupKeyRev);
552 }
553
554 // If we found a match then return it now.
555 if(fullLocKey != null)
556 {
557 final Location.Base result = nameToLoc.get(fullLocKey);
558 assert(result != null);
559 assert(result != Location.NONE);
560 // Cache result under full name.
561 cache.put(exhibitName, result);
562 // Record this positive result against the canonical key.
563 cache.put(searchKeyRev, result);
564 return(result);
565 }
566 // Else cache a negative result for this canonical key.
567 else
568 { cache.put(searchKeyRev, Location.NONE); }
569 }
570 }
571
572 // Cache negative result under full name.
573 cache.put(exhibitName, Location.NONE);
574 return(Location.NONE);
575 }
576
577 /**Returns true if the map is completely empty, eg as built by the default constructor.
578 */
579 public boolean isEmpty()
580 { return(mapFromNamePrefixToLocation.isEmpty()); }
581
582
583
584
585
586
587 // /**Retrieves the built-in LocationMap from properties; never null.
588 // * This is a backup/fallback, checked in with the code.
589 // * @return LocationMap, possibly empty but not null
590 // */
591 // private static LocationMap _getBuiltInLocationMap()
592 // {
593 // try
594 // {
595 // // Retrieve the location-map bundle...
596 // final ResourceBundle lp = ResourceBundle.getBundle(CoreConsts.LOCDB_PROPS_NAME);
597 //
598 // // Copy into a simple Peroperties object.
599 // final Properties p = new Properties();
600 // final Enumeration<String> en = lp.getKeys();
601 // while(en.hasMoreElements())
602 // {
603 // final String key = en.nextElement();
604 // p.setProperty(key, lp.getString(key));
605 // }
606 //
607 // return(new LocationMap(p, 0));
608 // }
609 // catch(final Exception e)
610 // {
611 // System.err.println("ERROR: could not load built-in location map (using empty one) from: " + CoreConsts.LOCDB_PROPS_NAME);
612 // e.printStackTrace();
613 // return(new LocationMap(new Properties(), 0));
614 // }
615 // }
616 //
617 // /**Built-in, possibly vestigial/fallback-only, location map. */
618 // private static final LocationMap builtInLocationMap = _getBuiltInLocationMap();
619 //
620 // /**Get public view of built-in location map instance. */
621 // public static LocationMap getBuiltInLocationMap()
622 // { return(builtInLocationMap); }
623
624
625
626
627
628 /**Serialisation UID. */
629 private static final long serialVersionUID=-8356330012477285265L;
630 }