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
030 package org.hd.d.pg2k.svrCore.location;
031
032 import java.net.Inet6Address;
033 import java.net.InetAddress;
034 import java.util.Collections;
035 import java.util.Enumeration;
036 import java.util.HashMap;
037 import java.util.HashSet;
038 import java.util.Iterator;
039 import java.util.Map;
040 import java.util.MissingResourceException;
041 import java.util.ResourceBundle;
042 import java.util.Set;
043 import java.util.SortedMap;
044 import java.util.StringTokenizer;
045 import java.util.TreeMap;
046
047 import org.hd.d.pg2k.svrCore.AddrTools;
048 import org.hd.d.pg2k.svrCore.MemoryTools;
049
050 import ORG.hd.d.IsDebug;
051
052
053 /**Geographical-related utility functions.
054 * This contains utilities, for example, to guess the approximate geographical
055 * location of an HTTP client from its IP address.
056 */
057 public final class GeoUtils
058 {
059 /**Prevent construction of an instance. */
060 private GeoUtils() { }
061
062
063 /**The base name of the default geographic proximity bundle.
064 * Does not include ".properties" or ".class" suffix,
065 * and is assumed to be relative to the root of the package structure.
066 */
067 private static final String BUNDLE_NAME_DEFAULT_GEO_PROXIMITY = "defaultGeoProximity";
068
069 /**The base name of the default IP-to-location bundle.
070 * Does not include ".properties" or ".class" suffix,
071 * and is assumed to be relative to the root of the package structure.
072 */
073 private static final String BUNDLE_NAME_DEFAULT_CCTLD_FROM_IP_PREFIX = "ccTLDFromIPPrefix";
074
075 /**Immutable Map for default proximity from CCTLD to Set of all neighbours.
076 * The values (Set<CCTLD>) are also immutable,
077 * so they can be returned to callers safely without copying.
078 */
079 private static final Map<GeoUtils.CCTLD,Set<GeoUtils.CCTLD>> defaultGeoMap;
080
081 static
082 {
083 final ResourceBundle dGP; // = null;
084 final Map<CCTLD,Set<CCTLD>> m = new HashMap<CCTLD, Set<CCTLD>>();
085 try
086 {
087 // Get the bundle if present...
088 dGP = ResourceBundle.getBundle(BUNDLE_NAME_DEFAULT_GEO_PROXIMITY);
089
090 // Expand into the quick-lookup format.
091 for(final Enumeration<String> en = dGP.getKeys(); en.hasMoreElements(); )
092 {
093 final String key = en.nextElement();
094 final StringTokenizer st = new StringTokenizer(dGP.getString(key));
095 final Set<CCTLD> group = new HashSet<CCTLD>();
096 while(st.hasMoreTokens())
097 {
098 final String cc = st.nextToken();
099 final CCTLD cctld = new CCTLD(MemoryTools.intern(cc));
100 group.add(MemoryTools.intern(cctld));
101 }
102
103 // Add whole group to neighbours of all members of that group.
104 // Keep the value Sets immutable.
105 for(final Iterator<CCTLD> it = group.iterator(); it.hasNext(); )
106 {
107 final CCTLD cctld = it.next();
108 final Set<CCTLD> s = m.get(cctld);
109 final Set<CCTLD> newSet = (s == null) ? new HashSet<CCTLD>() : new HashSet<CCTLD>(s);
110 newSet.addAll(group);
111 m.put(cctld, Collections.unmodifiableSet(newSet));
112 }
113 }
114 }
115 catch(final Exception e)
116 {
117 System.err.println("ERROR: failed to load/parse geo-proximity bundle: " + BUNDLE_NAME_DEFAULT_GEO_PROXIMITY);
118 e.printStackTrace();
119 }
120 finally
121 {
122 // defaultGeoProximity = dGP;
123 defaultGeoMap = Collections.unmodifiableMap(m);
124 }
125 }
126
127 /**Get geographically-close ccTLDs to the specified ccTLD; empty if none, but never null.
128 * This logically returns the union of all the groups that
129 * the specified ccTLD is present in,
130 * from static, built-in data, and any loadable data.
131 * <p>
132 * If no near neighbours for the specified ccTLD are known
133 * then an empty Set is returned,
134 * otherwise the specified ccTLD will be present in the result
135 * ie a country is always close to itself.
136 */
137 public static Set<GeoUtils.CCTLD> getCloseCCTLDs(final GeoUtils.CCTLD ccTLD)
138 {
139 final Set<GeoUtils.CCTLD> result = defaultGeoMap.get(ccTLD);
140
141 // If this is not in group, return an empty set.
142 if(result == null)
143 {
144 final Set<GeoUtils.CCTLD> empty = Collections.emptySet();
145 return(empty);
146 }
147
148 return(result);
149 }
150
151 /**Get ccTLDs in the specified region; empty if none, but never null.
152 * This should be a valid registry name
153 * or a name from the "defaultGeoProximity" properties.
154 * <p>
155 * The region name should <em>not</em> be a ccTLD
156 * nor a numeric partial/full address.
157 * <p>
158 * Currently this is computed on the fly each time;
159 * we may need to precompute or cache this result.
160 */
161 public static Set<GeoUtils.CCTLD> getCountriesInRegion(final String region)
162 {
163 // Quick check on argument validity.
164 if((region == null) || (region.length() < 1))
165 { throw new IllegalArgumentException(); }
166
167 final Set<GeoUtils.CCTLD> result = new HashSet<CCTLD>();
168
169 try
170 {
171 final ResourceBundle dGP = ResourceBundle.getBundle(BUNDLE_NAME_DEFAULT_GEO_PROXIMITY);
172 final String countries = dGP.getString(region);
173
174 // Compute the countries list...
175 final StringTokenizer st = new StringTokenizer(countries);
176 while(st.hasMoreTokens())
177 { result.add(new CCTLD(st.nextToken())); }
178 }
179 catch(final MissingResourceException e)
180 {
181 // Ignore these entirely...
182 }
183 catch(final Exception e)
184 {
185 // Absorb errors, though we don't expect any, so report this...
186 e.printStackTrace();
187 }
188
189 // Save some heap by returning a fixed empty set if nothing found...
190 if(result.isEmpty())
191 {
192 final Set<GeoUtils.CCTLD> empty = Collections.emptySet();
193 return(empty);
194 }
195
196 return(result);
197 }
198
199
200
201 /**An immutable DNS ccTLD (two-letter) country code.
202 * Lower-case, two-ASCII-letter code.
203 * <p>
204 * This is <em>not</em> the ISO3316-1 code;
205 * eg the United Kingdom is "uk" not "gb"
206 * though "gb" may be acceptable as an alias.
207 */
208 public static final class CCTLD implements MemoryTools.Internable
209 {
210 /**Construct instance.
211 * Supplied code must not be null
212 * and must consist of exactly two lower-case 7-bit-ASCII letters.
213 * <p>
214 * The code is only checked for syntax,
215 * not that such a country code actually exists.
216 */
217 public CCTLD(final String code)
218 {
219 if(!isSyntaticallyValidCcTLD(code))
220 { throw new IllegalArgumentException("code must be non-null length-two lower-case-ASCII"); }
221
222 this.code = code;
223 }
224
225 /**The two-letter lower-case-ASCII ccTLD; never null. */
226 public final String code;
227
228 /**True if argument is a (non-null) syntactically-valid ccTLD code.
229 * This only looks for syntactic validity.
230 *
231 * @return true iff two-letter lower-case-ASCII argument;
232 * false for null or otherwise-invalid arguments
233 */
234 public static boolean isSyntaticallyValidCcTLD(final String code)
235 {
236 if((code == null) ||
237 (code.length() != 2))
238 { return(false); }
239
240 final char c0 = code.charAt(0);
241 if((c0 < 'a') || (c0 > 'z'))
242 { return(false); }
243 final char c1 = code.charAt(1);
244 if((c1 < 'a') || (c1 > 'z'))
245 { return(false); }
246
247 // Looks OK.
248 return(true);
249 }
250
251 /**A fast hash reflecting approx ~5 bits of information per char. */
252 @Override
253 public int hashCode()
254 { return(code.charAt(0) + 29*code.charAt(1)); }
255
256 /**Two ccTLDs are the same if their underlying codes are the same. */
257 @Override
258 public boolean equals(final Object obj)
259 {
260 if(obj == this) { return(true); }
261 if(!(obj instanceof CCTLD)) { return(false); }
262 final CCTLD other = (CCTLD) obj;
263 return((code.charAt(0) == other.code.charAt(0)) &&
264 (code.charAt(1) == other.code.charAt(1)));
265 }
266
267 /**This returns the code as its String representation. */
268 @Override
269 public String toString()
270 { return(code); }
271 }
272
273
274 /**Default maximum prefix octets to look up in registry; strictly positive.
275 * This indicates the most specific lookup that we will attempt.
276 * <p>
277 * We will try the least-specific lookup first anyway,
278 * and just remember the most specific we found, if any,
279 * or failing any match an appropriate (short) numeric prefix instead.
280 * <p>
281 * This may be overridable from the data.
282 */
283 private static final int DEFAULT_MAX_OCTETS_LOOKUP = 3;
284
285 /**Test if argument is a (non-null) syntactically-valid region/registry code.
286 * This only checks syntactic validity.
287 *
288 * @return true iff three-or-more-ASCII-letter argument (1st upper-case);
289 * false for null or otherwise-invalid arguments
290 */
291 public static boolean isSyntaticallyValidRegistryName(final String code)
292 {
293 if(code == null) { return(false); }
294 final int l = code.length();
295 if(l < 3) { return(false); }
296
297 // First letter upper-case; the rest any case:
298 // eg RIPE and AfriNIC.
299 final char c0 = code.charAt(0);
300 if((c0 < 'A') || (c0 > 'Z'))
301 { return(false); }
302
303 for(int i = l; --i > 0; )
304 {
305 final char c = code.charAt(i);
306 final boolean isLetter =
307 ((c >= 'A') && (c <= 'Z')) ||
308 ((c >= 'a') && (c <= 'z'));
309 if(!isLetter)
310 { return(false); }
311 }
312
313 // Looks OK.
314 return(true);
315 }
316
317 /**Routine to validate a putative RHS value for the ccTLDFromIPPrefix table.
318 * A valid RHS (right-hand-side) value is one of:
319 * <ul>
320 * <li>An empty string ("") meaning:
321 * "make sure that there is no mapping for this value".
322 * <li>A valid country code (two-letter lower-case ASCII).
323 * <li>A valid region code (three-or-more-letter mixed-case ASCII).
324 * </ul>
325 *
326 * @return true iff the value is valid on the right-hand-side of an entry in
327 * a ccTLDFromIPPrefix map
328 */
329 private static boolean isValidCcTldFromIPPrefixMapValue(final String v)
330 {
331 if("".equals(v)) { return(true); }
332 if(CCTLD.isSyntaticallyValidCcTLD(v)) { return(true); }
333 if(isSyntaticallyValidRegistryName(v)) { return(true); }
334 return(false); // Not valid.
335 }
336
337 /**Cached immutable default ccTLD-from-IP-prefix SortedMap, or empty if none.
338 * Loaded at class initialisation.
339 * <p>
340 * This may have non-lossy transformations performed on it
341 * before it is stored.
342 * <p>
343 * The values will have been de-duped (intern()ed) for memory efficiency,
344 * since there are relatively few distinct values.
345 * <p>
346 * The keys are essentially unique and are NOT intern()ed,
347 * since attempting to do so would waste memory and time.
348 */
349 private static final SortedMap<AddrTools.AddrPrefix,String> ccTLDFromIPPrefix;
350
351 /**Get built-in immutable IPv4-to-ccTLD map; never null. */
352 public static SortedMap<AddrTools.AddrPrefix,String> getCCTLDFromIPPrefix()
353 { return(ccTLDFromIPPrefix); }
354
355 /**Longest key in octets in ccTLDFromIPPrefix; non-negative.
356 * Can be used to restrict search effort at lookup.
357 */
358 private static final int ccTLDFromIPPrefixLongestKey;
359
360 /**Initialise ccTLDFromIPPrefix. */
361 static
362 {
363 // Create in a HashMap for lookup/insertion speed...
364 // We guess a reasonable initial working size.
365 final Map<AddrTools.AddrPrefix,String> m = new HashMap<AddrTools.AddrPrefix, String>(250123);
366 int longestKey = 0;
367 try
368 {
369 // Get the bundle if present...
370 final ResourceBundle rb = ResourceBundle.getBundle(BUNDLE_NAME_DEFAULT_CCTLD_FROM_IP_PREFIX);
371
372 // Maximum number of times to warn about discarded (over-long) prefixes.
373 int maxWarnOverlong = 10;
374
375 // Set of unique RHS values that we've seen so far
376 // (for de-duping with fewer calls to intern(), which is very slow).
377 final HashMap<String,String> deDupedValues = new HashMap<String,String>(511);
378
379 // Parse each key/value in turn and add to the map if acceptable.
380 final Enumeration<String> keys = rb.getKeys();
381 while(keys.hasMoreElements())
382 {
383 final String key = keys.nextElement();
384 final AddrTools.AddrPrefix ap;
385 try { ap = new AddrTools.AddrPrefix(key); }
386 catch(final IllegalArgumentException e)
387 {
388 System.err.println("ERROR: skipped unparsable address prefix `"+key+"' in ccTLD-from-IP-prefix bundle: " + BUNDLE_NAME_DEFAULT_CCTLD_FROM_IP_PREFIX);
389 continue;
390 }
391
392 final int apLen = ap.length();
393 if(apLen > DEFAULT_MAX_OCTETS_LOOKUP)
394 {
395 if(--maxWarnOverlong >= 0)
396 { System.err.println("WARNING: skipped too-long address prefix `"+key+"' (max "+DEFAULT_MAX_OCTETS_LOOKUP+") in ccTLD-from-IP-prefix bundle: " + BUNDLE_NAME_DEFAULT_CCTLD_FROM_IP_PREFIX); }
397 continue;
398 }
399
400 // Get RHS (ccTLD/region/etc) code.
401 final String value = rb.getString(key);
402
403 // Locally remove RHS duplicates,
404 // intern()ing (and validating) new values once.
405 String dedupedValue = deDupedValues.get(value);
406 if(null == dedupedValue)
407 {
408 // First time we've seen this RHS value so validate/veto it.
409 if(!isValidCcTldFromIPPrefixMapValue(value))
410 {
411 System.err.println("ERROR: skipped unparsable/illegal ccTLD/region `"+value+"' for key `"+key+"' in ccTLD-from-IP-prefix bundle: " + BUNDLE_NAME_DEFAULT_CCTLD_FROM_IP_PREFIX);
412 continue;
413 }
414
415 dedupedValue = MemoryTools.intern(value);
416 deDupedValues.put(dedupedValue, dedupedValue);
417 }
418
419 // Note the longest key to help make lookups more efficient.
420 if(apLen > longestKey)
421 { longestKey = apLen; }
422
423 // Add valid entry to map...
424 m.put(ap, dedupedValue);
425 //System.out.println("Map size now: " + m.size());
426 }
427
428 //System.out.println("deDupedValues size: " + deDupedValues.size());
429 //System.out.println("Map size: " + m.size());
430 }
431 catch(final Exception e)
432 {
433 System.err.println("ERROR: failed to load/parse ccTLD-from-IP-prefix bundle: " + BUNDLE_NAME_DEFAULT_CCTLD_FROM_IP_PREFIX);
434 e.printStackTrace();
435 }
436 finally
437 {
438 // Store as an unmodifiable SortedMap.
439 ccTLDFromIPPrefix = Collections.unmodifiableSortedMap(new TreeMap<AddrTools.AddrPrefix,String>(m));
440 // And record the longest key actually present in the map.
441 ccTLDFromIPPrefixLongestKey = longestKey;
442 }
443
444 if(IsDebug.isDebug) { System.out.println("[GeoUtils.ccTLDFromIPPrefix.size() = " + ccTLDFromIPPrefix.size() + ".]"); }
445 }
446
447 /**Guess geographical "location" of given IP address (usually of an HTTP client).
448 * This attempts to guess the top-level country code corresponding to
449 * the likely physical/geographic location of the machine/interface whose
450 * IP address is passed.
451 * <p>
452 * This may try a number of methods, some of which may require live DNS
453 * or other Internet connectivity, but if it fails, will return null.
454 * The results are not guaranteed to be accurate.
455 * <p>
456 * The code returned, if any, is the lower-case, two-letter,
457 * Internet country code, for the region that the IP is in.
458 * (This is not the ISO3166-1 code; the United Kingdom is "uk" not "gb".)
459 * <p>
460 * (Our implementation here is to get the region,
461 * and then return anything that looks like a country-code, else null.)
462 *
463 * @param addr the (client) address to look up
464 * @param quick if true, this will try to minimise time taken
465 * and resources used, eg it may avoid going to DNS,
466 * so the answer may be less accurate or null more often
467 */
468 public static CCTLD getCCTLDByAddress(final InetAddress addr,
469 final boolean quick)
470 {
471 final String guess = getRegionByAddress(addr, quick);
472
473 // If this looks like a ccTLD,
474 // then try to return it as one.
475 if(CCTLD.isSyntaticallyValidCcTLD(guess))
476 { return(new CCTLD(guess)); }
477
478 // Don't know.
479 return(null);
480 }
481
482 /**Guess country or geographic region of IP address; never returns null nor "".
483 * This attempts to guess the top-level country code (ccTLD) corresponding to
484 * the likely physical/geographic location of the machine/interface whose
485 * IP address is passed. (A two-letter lower-case country code.)
486 * <p>
487 * Failing that this routine may attempt to return the regional IP registry
488 * (eg RIPE or APNIC) that delegates the address,
489 * which usually indicates the continent from which the IP address hails.
490 * (Starting with an upper-case letter and being more than two characters,
491 * ie guaranteed to look like neither a ccTLD nor an IP-address prefix.)
492 * <p>
493 * Failing that this will return one or more octets of an IP(v4) address
494 * (eg looking like "127" or "127.0" or "127.0.0" or "127.0.0.1")
495 * or some leading hex encoding of the network part of an IP(v6) address or a fixed token.
496 * <p>
497 * The number of different values that this routine can return is finite,
498 * certainly no more than thousands, and all values are human-readable,
499 * so this should be directly usable as a key in stats info,
500 * for example.
501 * <p>
502 * This may try a number of methods, some of which may require live DNS
503 * or other Internet connectivity, but if they fail,
504 * this will return a numeric value.
505 * <p>
506 * The results are not guaranteed to be accurate.
507 *
508 * @param addr the (client) address to look up; never null
509 * @param quick if true, this will try to minimise time taken
510 * and resources used, eg it may avoid going to DNS,
511 * so the answer may be less accurate or numeric more often
512 *
513 * @return non-null, non-empty, short, human-readable, plain-ASCII String
514 */
515 public static String getRegionByAddress(final InetAddress addr,
516 final boolean quick)
517 {
518 if(addr == null)
519 { throw new IllegalArgumentException(); }
520
521 // FIXME
522 if(addr instanceof Inet6Address)
523 { return("IPv6"); }
524
525 // Look up the address in our map
526 // for the most-specific information available.
527 //
528 // If the result is a null (and we're restricted to a quick lookup)
529 // then we return the numeric representation of the first octet...
530 String result = lookupAddrInIPToCcTLDMap(addr,
531 ccTLDFromIPPrefix, ccTLDFromIPPrefixLongestKey);
532 if(result == null)
533 { result = new AddrTools.AddrPrefix(addr.getAddress(), 1).toPaddedDottedPrefix(); }
534
535 // If the caller is prepared for us to do a slow (ie more accurate or comprehensive) lookup, AND
536 // if the result so far is NOT a country code, AND
537 // if the address looks like a normal unicast address,
538 // then try doing a reverse lookup on the IP address and examine any resulting domain name
539 // and if the name ends with a ccTLD then we will return that ccTLD.
540 if(!quick &&
541 !GeoUtils.CCTLD.isSyntaticallyValidCcTLD(result) &&
542 !addr.isMulticastAddress())
543 {
544 try
545 {
546 // Try to do a (reverse) lookup of the client host name
547 // from its IP address...
548 // We don't need to verify this name;
549 // if the client lies then it is probably their problem!
550 // Note that this may have a trailing "."
551 // to show that it is an absolute FQFN.
552 String name = AddrTools.USE_DNSJAVA ? AddrTools.doReverseLookup(addr, false) : addr.getHostName();
553
554 //System.out.println("[Lookup "+addr.getHostAddress()+" --> "+name+"]");
555
556 if(name != null)
557 {
558 // Strip off any canonical trailing dot...
559 if(name.endsWith("."))
560 { name = name.substring(0, name.length()-1); }
561 assert !name.endsWith(".") : ("had more than one trailing dot in name: "+name+".");
562
563 // Allow for names as short as "x.cc"...
564 final int nameLen = name.length();
565 if(nameLen > 3)
566 {
567 // If the final component is exactly two characters...
568 if(name.lastIndexOf('.') == nameLen - 3)
569 {
570 // Take the final component and force to lower-case.
571 final String putativeCCTLD =
572 name.substring(nameLen - 2).toLowerCase();
573
574 // If it is a valid ccTLD,
575 // then return it immediately...
576 if(GeoUtils.CCTLD.isSyntaticallyValidCcTLD(putativeCCTLD))
577 { return(putativeCCTLD); }
578 }
579 }
580 }
581 }
582 catch(final Exception e)
583 {
584 e.printStackTrace();
585 // Simply ignore any lookup errors...
586 }
587 }
588
589
590 // Return special (but non-null) value to indicate a config error
591 // if nothing was set...
592 // This does not look like a valid CC nor region nor numeric prefix.
593 if(result == null)
594 { return("-"); }
595
596 return(result);
597 }
598
599 /**Do a lookup in the supplied table for the most-specific available location data for the given key; null if none.
600 * Never uses DNS or other external data.
601 * <p>
602 * FIXME: only works at all for IPv4 addresses currently
603 *
604 * @param addr the address to look up; never null
605 * @param map the prefix map; never null
606 * @param maxOctetsLookup the maximum number of octets of prefix to look up; non-negative
607 *
608 * @return the most-specific value; null if nothing (or a "" value) found
609 */
610 public static String lookupAddrInIPToCcTLDMap(final InetAddress addr,
611 final Map<AddrTools.AddrPrefix,String> map,
612 final int maxOctetsLookup)
613 {
614 String result = null;
615
616 final byte[] rawAddr = addr.getAddress();
617
618 // FIXME: cannot deal with non-IPv4 addresses at the moment.
619 if(rawAddr.length > 4)
620 { return(null); }
621
622 // Make an increasingly long nnn[.nnn[.nnn ...]] prefix.
623 // Don't go longer than the longest key actually present in the map
624 // since that will always fail to find anything.
625 for(int octets = 1; octets <= maxOctetsLookup; ++octets)
626 {
627 // Stop extending the prefix when we run out of address!
628 if(octets > rawAddr.length)
629 { break; }
630
631 final AddrTools.AddrPrefix addrPrefix = new AddrTools.AddrPrefix(rawAddr, octets);
632
633 // Attempt to look up the region.
634 // Absorb complaint if prefix not present in data.
635 final String region = map.get(addrPrefix);
636
637 // If we got an empty value then clear any previous result.
638 if("".equals(region))
639 { result = null; }
640 // If we got a non-null non-empty result,
641 // then it is our most-specific so far, so keep it.
642 else if(region != null)
643 { result = region; }
644 }
645
646 return(result);
647 }
648
649 /**Compute approximate proximity of one host to a given country.
650 * This is most useful where we know the location of (for example)
651 * a mirror host for sure.
652 * <p>
653 * Returns "NONE" if it cannot compute a proximity
654 * or if the hosts appear to be a long way apart.
655 *
656 * @param host1 IP address of first host; never null
657 * @param host2CC country of second host; never null
658 * @param quick if true, do a quick estimate,
659 * else be prepared to spend some time and CPU cycles to be as accurate as possible
660 * (for example we might have to to reverse DNS lookups)
661 */
662 public static GeoProximity computeProximityByAddress(final InetAddress host1,
663 final CCTLD host2CC,
664 final boolean quick)
665 {
666 if((host1 == null) || (host2CC == null))
667 { throw new IllegalArgumentException(); }
668
669 // Look up region/country (if known) for the first host.
670 final String region1 = getRegionByAddress(host1, quick);
671
672 return(computeProximity(region1, host2CC));
673 }
674
675 /**Compute approximate proximity of a host in a given region to a given country; never null.
676 * This is most useful where we know the location of (for example)
677 * a mirror host for sure.
678 * <p>
679 * Returns "NONE" if it cannot compute a proximity
680 * or if the hosts appear to be a long way apart.
681 *
682 * @param region1 region or country of first host; never null
683 * @param host2CC country of second host; never null
684 */
685 public static GeoProximity computeProximity(final String region1,
686 final CCTLD host2CC)
687 {
688 if((host2CC == null) || (region1 == null) || (region1.length() < 1))
689 { throw new IllegalArgumentException(); }
690
691 // If the first host is known to be in the same country as the second
692 // then we're done.
693 if(region1.equals(host2CC.code))
694 { return(GeoProximity.COUNTRY); }
695
696 // If the first host is a country
697 // then check if we're in the same group/region.
698 if(CCTLD.isSyntaticallyValidCcTLD(region1))
699 {
700 if(getCloseCCTLDs(host2CC).contains(new CCTLD(region1)))
701 { return(GeoProximity.COUNTRYGROUP); }
702
703 return(GeoProximity.NONE); // Cannot determine proximity.
704 }
705
706 // If region seems not to be a valid region name
707 // then give up now...
708 if(!GeoUtils.isSyntaticallyValidRegistryName(region1))
709 { return(GeoProximity.NONE); /* Cannot determine proximity. */ }
710
711 // Deal with the same registry/region/continent...
712 if(getCountriesInRegion(region1).contains(host2CC))
713 { return(GeoProximity.CONTINENT); }
714
715 return(GeoProximity.NONE); // Cannot determine proximity.
716 }
717 }