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    /*
031     * Created by IntelliJ IDEA.
032     * User: Administrator
033     * Date: 28-Dec-02
034     * Time: 22:24:51
035     */
036    package org.hd.d.pg2k.test.dev;
037    
038    import java.io.File;
039    import java.net.InetAddress;
040    import java.net.UnknownHostException;
041    import java.util.ArrayList;
042    import java.util.Arrays;
043    import java.util.BitSet;
044    import java.util.HashMap;
045    import java.util.HashSet;
046    import java.util.Iterator;
047    import java.util.List;
048    import java.util.Properties;
049    import java.util.Random;
050    import java.util.Set;
051    import java.util.SortedSet;
052    import java.util.TreeSet;
053    
054    import junit.framework.TestCase;
055    
056    import org.hd.d.pg2k.svrCore.AddrTools;
057    import org.hd.d.pg2k.svrCore.ExhibitPropsGlobalImmutable;
058    import org.hd.d.pg2k.svrCore.Name;
059    import org.hd.d.pg2k.svrCore.location.GeoProximity;
060    import org.hd.d.pg2k.svrCore.location.GeoUtils;
061    import org.hd.d.pg2k.svrCore.location.Location;
062    import org.hd.d.pg2k.svrCore.location.LocationMap;
063    import org.hd.d.pg2k.svrCore.props.LocalProps;
064    import org.hd.d.pg2k.webSvr.virtualHosts.AlohaEarth.AEParams;
065    import org.mutabilitydetector.unittesting.AllowedReason;
066    import org.mutabilitydetector.unittesting.MutabilityAssert;
067    import org.mutabilitydetector.unittesting.MutabilityMatchers;
068    
069    /**Test of various aspects of Location and GeoUtils, etc.
070     * This includes geographical lookup by client IP address,
071     * though maybe these should be moved to a separate GeoUtilsTests class.
072     * <p>
073     *
074     * TODO: test that exhibits correctly get specific/generic flag
075     * depending on details being with exhibit or looked up in generic map
076     */
077    public final class LocationTest extends TestCase
078        {
079        public LocationTest(final String name)
080            {
081            super(name);
082            }
083    
084        /**Basic tests of LocationMap behaviour.
085         * These tests are not dependent on external data.
086         */
087        public static void testLocationMapBasics()
088            {
089            // Default constructor should yield empty map with zero hashcode.
090            final LocationMap elm = new LocationMap();
091            //assertImmutable(LocationMap.class);
092            assertTrue("Default LocationMap must be empty", elm.isEmpty());
093            assertTrue("Default LocationMap must have zero timestamp",
094                       elm.hashCode() == 0);
095    
096            // TODO: some real tests!
097            }
098    
099        /**Tests use of contains() and related operations on Location.Estd.
100         * We use some randomness to stochastically test the entire space
101         * of parameters that should be unimportant (ie the "specific" flag).
102         */
103        public static void testLocationEstdContains()
104            throws Exception
105            {
106            final Properties propsAllZeros = new Properties();
107            propsAllZeros.setProperty("type", "Estd");
108            propsAllZeros.setProperty("E", "0");
109            propsAllZeros.setProperty("EErr", "0");
110            propsAllZeros.setProperty("N", "0");
111            propsAllZeros.setProperty("NErr", "0");
112            final Location.Estd allZeros =
113                new Location.Estd(rnd.nextBoolean(), "", propsAllZeros);
114    
115            final Properties propsZeroCentre = new Properties();
116            propsZeroCentre.setProperty("type", "Estd");
117            propsZeroCentre.setProperty("E", "0");
118            propsZeroCentre.setProperty("EErr", "1");
119            propsZeroCentre.setProperty("N", "0");
120            propsZeroCentre.setProperty("NErr", "1");
121            final Location.Estd zeroCentre =
122                new Location.Estd(rnd.nextBoolean(), "", propsZeroCentre);
123    
124            final Properties propsAllOnes = new Properties();
125            propsAllOnes.setProperty("type", "Estd");
126            propsAllOnes.setProperty("E", "1");
127            propsAllOnes.setProperty("EErr", "1");
128            propsAllOnes.setProperty("N", "1");
129            propsAllOnes.setProperty("NErr", "1");
130            final Location.Estd allOnes =
131                new Location.Estd(rnd.nextBoolean(), "", propsAllOnes);
132    
133    
134            // All items should contain their own centre.
135            // All items should be contained by WHOLE_EARTH and not contain it.
136            final Location.Estd allEstdItems[] =
137                {
138                    allZeros, zeroCentre, allOnes
139                };
140            for(int i = allEstdItems.length; --i >= 0; )
141                {
142                final Location.Estd l = allEstdItems[i];
143                assertTrue("Location.Estd item must always contain its own centre",
144                           l.containsCentre(l));
145                assertTrue("Location.Estd item must always contain its own area",
146                           l.containsArea(l));
147                assertFalse("Location.Estd item must not strictly contain its own area",
148                           l.strictlyContainsArea(l));
149                assertTrue("All item centres must be contained by WHOLE_EARTH",
150                           Location.Estd.WHOLE_EARTH.containsCentre(l));
151                assertTrue("All item areas must be contained by WHOLE_EARTH",
152                           Location.Estd.WHOLE_EARTH.containsArea(l));
153                assertFalse("No item area may strictly contain WHOLE_EARTH",
154                            l.strictlyContainsArea(Location.Estd.WHOLE_EARTH));
155                assertFalse("No item area may be larger than WHOLE_EARTH",
156                            l.isLargerThan(Location.Estd.WHOLE_EARTH));
157                }
158    
159            // Test (some) expected pair-wise relationships.
160            assertTrue("0E0N,+/-0 centre should be contained by bigger zero-centred item",
161                       zeroCentre.containsCentre(allZeros));
162            assertTrue("0E0N,+/-0 area should be contained by bigger zero-centred item",
163                       zeroCentre.containsArea(allZeros));
164            assertTrue("0E0N,+/-0 area should be strictly contained by bigger zero-centred item",
165                       zeroCentre.strictlyContainsArea(allZeros));
166            assertTrue("0E0N,+/-0 should contain centre of (bigger) zero-centred item",
167                       allZeros.containsCentre(zeroCentre));
168            assertFalse("0E0N,+/-0 should not contain area of (bigger) zero-centred item",
169                        allZeros.containsArea(zeroCentre));
170            assertFalse("0E0N,+/-0 should not strictly contain area of (bigger) zero-centred item",
171                        allZeros.strictlyContainsArea(zeroCentre));
172            assertFalse("0E0N,+/-0 should not contain centre of non-zero-centred item",
173                        allZeros.containsCentre(allOnes));
174            assertFalse("0E0N,+/-0 should not contain area of non-zero-centred item",
175                        allZeros.containsArea(allOnes));
176            assertTrue("1E1N,+/-1 should contain centre of (big) zero-centred item",
177                       allOnes.containsCentre(zeroCentre));
178            assertFalse("1E1N,+/-1 should not contain area of (big) zero-centred item",
179                        allOnes.containsArea(zeroCentre));
180            assertTrue("1E1N,+/-1 should contain centre of (point) zero-centred item",
181                       allOnes.containsCentre(allZeros));
182            assertTrue("1E1N,+/-1 should contain area of (point) zero-centred item",
183                       allOnes.containsArea(allZeros));
184            assertTrue("1E1N,+/-1 should strictly contain area of (point) zero-centred item",
185                       allOnes.strictlyContainsArea(allZeros));
186            }
187    
188    
189    
190    
191        /**Test that we can look up exhibits by name in the sample auxInfo file.
192         * FIXME: Also needs stand-alone tests that do not depend on the
193         * vast config file.
194         * <p>
195         * We skip these tests if we do not have full/normal access
196         * to the filesystem.
197         */
198        public void testLocationLookup()
199            throws Exception
200            {
201            if(!Main.isAccessToFilesystem())
202                {
203                System.err.println("No access to filesystem: skipping tests: " + getName());
204                return;
205                }
206    
207            final ExhibitPropsGlobalImmutable epgi = ExhibitPropsGlobalImmutable.loadFromDataDir(new File(LocalProps.getDataDir()));
208            final LocationMap lm = epgi.getLocationMap();
209            assertFalse("Loaded LocationMap must not be empty", lm.isEmpty());
210    
211    
212            // Looking up this data item...
213    //    location.places-and-sights/New-Zealand-.type=Estd
214    //    location.places-and-sights/New-Zealand-.E=172.5
215    //    location.places-and-sights/New-Zealand-.EErr=6.5
216    //    location.places-and-sights/New-Zealand-.N=-41
217    //    location.places-and-sights/New-Zealand-.NErr=6.5
218            // List of exhibits all of which we ought to be able to
219            // get general (non-specific) location info for,
220            // either forwards or reverse.
221            // (We assume no attribute words for this test.)
222            final String[] testExhibits =
223                {
224                   "places-and-sights/New-Zealand-random-DHD.gif",
225                   "places-and-sights/_more2003/_more04/New-Zealand-South-Island-west-coast-Milford-Sound-fjord-taken-20001213-waterfall-tumbling-down-steep-vegetated-bank-rotated-MB.jpg",
226                   "places-and-sights/Earth-DHD.gif",
227    //               "places-and-sights/Earth-whole-planet-DHD.gif", // Is specific!
228                   "places-and-sights/England-London-made-up-name-Z.wav",
229                   "natural-science/_more2005/_more07/bee-bumblebee-bumble-bee-pollinating-pollination-of-lavender-purple-flowers-from-bush-flowering-in-July-in-London-England-8-DHD.jpg",
230                   "natural-science/_more2005/_more07/bee-bumblebee-bumble-bee-pollinating-pollination-of-lavender-purple-flowers-from-bush-flowering-in-July-in-London-England-closeup-9-DHD.jpg",
231                };
232            // Create enough attribute words to cover our examples.
233            final Set<String> attrWords = new HashSet<String>(Arrays.asList(new String[]{
234                "closeup", "rotated"
235                }));
236            for(int i = testExhibits.length; --i >= 0; )
237                {
238                final Name.ExhibitFull name = Name.ExhibitFull.create(testExhibits[i]);
239                final Location.Base te1 = lm.locLookup(name, attrWords);
240                assertNotNull("Result of LocationMap lookup (te1) must not be null: " + name,
241                              te1);
242                assertFalse("Result of te1 lookup should not be specific for " + name + ": " + te1,
243                            te1.specific);
244                assertTrue("Result of te1 lookup should be Estd for " + name + ", got: " + te1 + " class "+ te1.getClass(),
245                           te1 instanceof Location.Estd);
246                final Location.Estd te1E = (Location.Estd) te1;
247                assertTrue("Result of (te1) lookup should be on full Aloha Earth map",
248                           (new AEParams()).getLocation(true).containsCentre(te1E));
249                assertFalse("No looked-up item should be larger than WHOLE_EARTH",
250                            te1E.isLargerThan(Location.Estd.WHOLE_EARTH));
251                }
252            }
253    
254    
255    
256        /**Test class immutability/safety. */
257        public static void testCCTLDImmutability()
258            {
259            //assertImmutable(GeoUtils.CCTLD.class);
260            MutabilityAssert.assertInstancesOf(GeoUtils.CCTLD.class, MutabilityMatchers.areImmutable(), AllowedReason.provided(String.class).isAlsoImmutable());
261            }
262    
263        /**Test that we can use the geographical-proximity-by-ccTLD data.
264         * This tests that we can find close-by countries (in connectivity terms).
265         */
266        public static void testGeoProximity()
267            {
268            // Look for "gb"/"uk" to be in the same group.
269            // This also establishes that we can load at least the default set.
270            final GeoUtils.CCTLD uk = new GeoUtils.CCTLD("uk");
271            final GeoUtils.CCTLD us = new GeoUtils.CCTLD("us");
272            final List<GeoUtils.CCTLD> group = Arrays.asList(new GeoUtils.CCTLD[]{
273                uk, new GeoUtils.CCTLD("gb")});
274            final Set<GeoUtils.CCTLD> groupS = new HashSet<GeoUtils.CCTLD>(group);
275    
276            for(final Iterator<GeoUtils.CCTLD> it = groupS.iterator(); it.hasNext(); )
277                {
278                final GeoUtils.CCTLD cc = it.next();
279                assertTrue("Must be able to find all of our group "+group+" when we look up " + cc,
280                           GeoUtils.getCloseCCTLDs(cc).containsAll(groupS));
281                }
282    
283            // Check that "uk" is in the "RIPE" region.
284            assertTrue("Expect to find uk in the RIPE region",
285                       GeoUtils.getCountriesInRegion("RIPE").contains(uk));
286    
287            // Check a few expected proximities.
288            assertEquals("The same country should have COUNTRY proximity to itself",
289                         GeoProximity.COUNTRY, GeoUtils.computeProximity("uk", uk));
290            assertEquals("The same country should have COUNTRY proximity to itself",
291                         GeoProximity.COUNTRY, GeoUtils.computeProximity("us", us));
292            assertEquals("Well-connected countries should have COUNTRY proximity",
293                         GeoProximity.COUNTRYGROUP, GeoUtils.computeProximity("uk", new GeoUtils.CCTLD("fr")));
294            assertEquals("Well-connected countries should have COUNTRY proximity",
295                         GeoProximity.COUNTRYGROUP, GeoUtils.computeProximity("nl", new GeoUtils.CCTLD("de")));
296            assertEquals("Well-connected countries should have COUNTRY proximity",
297                         GeoProximity.COUNTRYGROUP, GeoUtils.computeProximity("ca", us));
298            assertEquals("A region/registry should have CONTINENT proximity to a country in it",
299                         GeoProximity.CONTINENT, GeoUtils.computeProximity("RIPE", uk));
300            assertEquals("A region/registry should have CONTINENT proximity to a country in it",
301                         GeoProximity.CONTINENT, GeoUtils.computeProximity("ARIN", us));
302            assertEquals("A region/registry should have NONE proximity to a country not in it",
303                         GeoProximity.NONE, GeoUtils.computeProximity("ARIN", uk));
304            assertEquals("A region/registry should have NONE proximity to a country not in it",
305                         GeoProximity.NONE, GeoUtils.computeProximity("RIPE", us));
306            assertEquals("A region/registry should have NONE proximity to a (non-existent) country not in it",
307                         GeoProximity.NONE, GeoUtils.computeProximity("RIPE", new GeoUtils.CCTLD("xx")));
308            assertEquals("A (non-existent) region/registry should have NONE proximity to a country not in it",
309                         GeoProximity.NONE, GeoUtils.computeProximity("NONESUCH", us));
310            assertEquals("A (non-existent) region/registry should have NONE proximity to a (non-existent) country not in it",
311                         GeoProximity.NONE, GeoUtils.computeProximity("NONESUCH", new GeoUtils.CCTLD("xx")));
312            }
313    
314        /**Check lookup of geographic location of IP address.
315         * This checks that basic lookup of location by IP address
316         * (ie in which country is the machine/interface with that address)
317         * works for some simple basic cases.
318         * <p>
319         * TODO: whitebox check that mappings in source data are actually visible.
320         */
321        public static void testRegionLookup()
322            throws Exception
323            {
324            final long t0 = System.currentTimeMillis();
325    
326            // Do quick region/ccTLD lookups for a couple of addresses whose location is known
327            // and should be pretty stable...
328            final String reg1 = GeoUtils.getRegionByAddress(InetAddress.getByName("195.137.27.135"), true);
329            assertTrue("Must be able to look up known-UK address quickly",
330                       ("RIPE".equals(reg1) || ("uk".equals(reg1))));
331            final String reg2 = GeoUtils.getRegionByAddress(InetAddress.getByName("72.9.239.218"), true);
332            assertTrue("Must be able to look up known-US address quickly",
333                       ("ARIN".equals(reg2) || ("us".equals(reg2))));
334    //        final String reg3 = GeoUtils.getRegionByAddress(InetAddress.getByName("193.178.223.10"), true);
335    //        assertTrue("Must be able to look up known-UK address quickly",
336    //                   ("RIPE".equals(reg3) || ("uk".equals(reg3))));
337    
338            final long t1 = System.currentTimeMillis();
339            System.out.println("[Initial lookups: "+(t1-t0)+"ms.]"); // Can help with performance tuning.
340    
341    
342            // Do region/ccTLD lookups for the
343            // loop-back and other "private" addresses
344            // which should never have a geographical association.
345    
346            // Handle the loop-back on its own for now.
347            final InetAddress loopback = InetAddress.getByName("127.0.0.1");
348    
349            // Try to look up the region.
350            // This should simply return some numeric prefix of the address
351            // since the whole of the 127/8 block is non-geographic private space.
352            // We try the quick flag randomly either way as it should not matter.
353            final boolean quickLoopbackLookup = rnd.nextBoolean();
354            final String region = GeoUtils.getRegionByAddress(loopback, quickLoopbackLookup);
355            assertTrue("Region lookup should be 127 or start 127.",
356                       "127".equals(region) || region.startsWith("127."));
357    
358            // Try to look up the ccTLD; the result should be null.
359            // We try the quick flag randomly either way as it should not matter.
360            final GeoUtils.CCTLD ccTLD = GeoUtils.getCCTLDByAddress(loopback, !quickLoopbackLookup);
361            assertNull("We should not find a country code associated with the loopback address",
362                       ccTLD);
363    
364            // Handle generic private/non-routable/non-geographic addresses.
365            // These should in principle include all the RFC1597 addresses,
366            // though in practice we may not be precise enough to handle
367            // all of them correctly...
368            final String nonGeoAddrs[] =
369                { "127.0.0.1", "10.0.0.0", "172.16.0.0", "192.168.0.0" };
370            final int indexOfFullLookup = rnd.nextInt(nonGeoAddrs.length);
371            for(int i = nonGeoAddrs.length; --i >= 0; )
372                {
373                final InetAddress addr = InetAddress.getByName(nonGeoAddrs[i]);
374                assertNull("ccTLD (quick) lookup for private address ("+addr+") should be null",
375                           GeoUtils.getCCTLDByAddress(addr, true));
376                // Do one of these at random as a full lookup to make sure it does not matter.
377                final String regionByAddress = GeoUtils.getRegionByAddress(addr,
378                                           (i != indexOfFullLookup));
379                assertTrue("Region lookup for private address ("+addr+") should not be ccTLD nor registry/region name: " + regionByAddress,
380                           !GeoUtils.CCTLD.isSyntaticallyValidCcTLD(regionByAddress) &&
381                           !GeoUtils.isSyntaticallyValidRegistryName(regionByAddress));
382                }
383    
384            // Check that at least one single-octet IPv4 prefix
385            // returns a non-numeric region and/or a non-null ccTLD
386            // even when doing a "quick" lookup with internal data only
387            // (or at least not going to the Net for further data).
388            boolean foundCcTLD = false;
389            boolean foundRegion = false;
390            for(int i = 256; --i >= 0; )
391                {
392                final String dottedQuad = String.valueOf(i) + ".0.0.1";
393                final InetAddress ia = InetAddress.getByName(dottedQuad);
394    
395                // We do a quick lookup of ccTLD, ie by internal data only.
396                final GeoUtils.CCTLD cc = GeoUtils.getCCTLDByAddress(ia, true);
397                if(null != cc)
398                    { foundCcTLD = true; }
399    
400                // We do a quick lookup of region, ie by internal data only.
401                final String reg = GeoUtils.getRegionByAddress(ia, true);
402                assertNotNull("region must not be null", reg);
403                assertTrue("region must not be empty String", reg.length() > 0);
404                for(int r = reg.length(); --r >= 0; )
405                    {
406                    final char rc = reg.charAt(r);
407                    assertTrue("all region characters must be printable ASCII",
408                               (rc > 32) && (rc < 127));
409                    }
410                final char regFirstChar = reg.charAt(0);
411                assertTrue("region must start with a digit or letter",
412                           Character.isDigit(regFirstChar) ||
413                           Character.isLetter(regFirstChar));
414                if(!Character.isDigit(regFirstChar))
415                    { foundRegion = true; }
416    //Main.getOut().println("[cc/reg="+cc+"/"+reg+"]");
417                }
418            assertTrue("Must find at least one non-null ccTLD by IP address", foundCcTLD);
419            assertTrue("Must find at least one non-numeric region by IP address", foundRegion);
420    
421    //        // Try doing sample lookups on the "local" address...
422    //        // This is no more of a test than to ensure we don't blow up!
423    //        Main.getOut().println("Quick lookup on local address gives: "
424    //            + GeoUtils.getRegionByAddress(InetAddress.getLocalHost(), true));
425    //        Main.getOut().println("Full lookup on local address gives: "
426    //            + GeoUtils.getRegionByAddress(InetAddress.getLocalHost(), false));
427            }
428    
429        /**Check DNS lookup.
430         * If DNS lookup is working at all,
431         * check that forward and reverse lookup is working as expected.
432         */
433        public static void testDNSLookup()
434            {
435            // Set of names to look up addresses for,
436            // then lookup in reverse to get back the names for.
437            // We pick addresses that are pretty likely to work correctly for this case!
438            final String testNames[] = {
439                //"www.ibm.com.", // Failed 20111115.
440                "mirror-uk-rb1.gallery.hd.org.",
441                //"www.parliament.uk.", // Failed 20090705!
442                };
443            try
444                {
445                for(final String n : testNames)
446                    {
447    //                final InetAddress addrs[] = InetAddress.getAllByName(n);
448                    final InetAddress addr = InetAddress.getByName(n);
449                    final String rlName = AddrTools.doReverseLookup(addr, false);
450                    if(rlName == null)
451                            {
452                            Main.getOut().println("Reverse lookup currently not possible...");
453                            continue;
454                            }
455                    assertEquals("Reverse lookup should return orginal name", n, rlName);
456                    final String rlName2 = AddrTools.doReverseLookup(addr, true);
457                    if(rlName2 == null)
458                            {
459                            Main.getOut().println("Reverse lookup currently not possible (2)...");
460                            continue;
461                            }
462                    assertEquals("Verifying reverse lookup should return orginal name", n, rlName2);
463                    }
464                }
465            catch(final UnknownHostException e)
466                {
467                e.printStackTrace(Main.getErr());
468                Main.getErr().println("WARNING: lookup failed: aborting all DNS lookup tests since DNS may be unavailable...");
469                }
470            }
471    
472        /**Test AddrPrefix class briefly.
473         */
474        public static void testAddrPrefix()
475            {
476            assertEquals("Should create correct padded dotted form", "001", new AddrTools.AddrPrefix(new byte[]{1}).toPaddedDottedPrefix());
477            assertEquals("Should create correct padded dotted form", "213.092", new AddrTools.AddrPrefix(new byte[]{(byte)213, 92}).toPaddedDottedPrefix());
478            assertEquals("Should create correct padded dotted form", "004.000.255", new AddrTools.AddrPrefix(new byte[]{4, 0, (byte)255}).toPaddedDottedPrefix());
479            }
480    
481        /**Test some simple cases for AddrPrefix dotted-quad parsing. */
482        public static void testAddrPrefixParsing()
483            {
484            assertTrue("Should be able to parse prefix correctly", Arrays.equals((new AddrTools.AddrPrefix("127")).toByteArray(), new byte[]{127}));
485            assertTrue("Should be able to parse prefix correctly", Arrays.equals((new AddrTools.AddrPrefix("127.000")).toByteArray(), new byte[]{127,0}));
486            assertTrue("Should be able to parse prefix correctly", Arrays.equals((new AddrTools.AddrPrefix("127.00")).toByteArray(), new byte[]{127,0}));
487            assertTrue("Should be able to parse prefix correctly", Arrays.equals((new AddrTools.AddrPrefix("127.0")).toByteArray(), new byte[]{127,0}));
488            assertTrue("Should be able to parse prefix correctly", Arrays.equals((new AddrTools.AddrPrefix("127.0.0")).toByteArray(), new byte[]{127,0,0}));
489            assertTrue("Should be able to parse prefix correctly", Arrays.equals((new AddrTools.AddrPrefix("127.0.0.1")).toByteArray(), new byte[]{127,0,0,1}));
490    
491            // Test a few random prefixes/addresses of various length,
492            // with sub-3-digit octets randomly padded or not.
493            final StringBuilder sb = new StringBuilder();
494            final StringBuilder o = new StringBuilder();
495            for(int i = 20000; --i >= 0; )
496                {
497                final int length = 1 + rnd.nextInt(4);
498                final byte[] data = new byte[length];
499                rnd.nextBytes(data); // Fill in random bytes.
500    
501                sb.setLength(0);
502                for(int j = 0; j < length; ++j)
503                    {
504                    o.setLength(0);
505                    o.append(data[j] & 0xff);
506                    while((o.length() < 3) && rnd.nextBoolean())
507                        { o.insert(0, '0'); }
508                    if(sb.length() != 0) { sb.append('.'); }
509                    sb.append(o);
510                    }
511    
512                final String s = sb.toString();
513                assertTrue("Should be able to parse prefix correctly: "+s, Arrays.equals((new AddrTools.AddrPrefix(s)).toByteArray(), data));
514                }
515            }
516    
517        /**Check AddrPrefix.hashCode() effectiveness, as this may be important for performance.
518         * We test this against any real data we have to hand
519         * plus a few choice manually-selected values.
520         * <p>
521         * The aim is to ensure that there are not excessive collisions
522         * for real data values, as that would make HashSet and related containers
523         * behave badly.
524         */
525        public static void testAddrPrefixHashCode()
526            {
527            // Maximum collisions that we will permit on any one hash value.
528            final int MAX_COLLISIONS = 3;
529    
530            // Get addresses from built-in IP-->ccTLD map first.
531            final SortedSet<AddrTools.AddrPrefix> testPrefixes =
532                new TreeSet<AddrTools.AddrPrefix>(GeoUtils.getCCTLDFromIPPrefix().keySet());
533            // Add a few more potentially-important values by hand.
534            testPrefixes.add(new AddrTools.AddrPrefix("127.0.0.1"));
535            testPrefixes.add(new AddrTools.AddrPrefix("127.0.0"));
536            testPrefixes.add(new AddrTools.AddrPrefix("127.0"));
537            testPrefixes.add(new AddrTools.AddrPrefix("127"));
538            testPrefixes.add(new AddrTools.AddrPrefix("0.0.0.0"));
539            testPrefixes.add(new AddrTools.AddrPrefix("0.0.0"));
540            testPrefixes.add(new AddrTools.AddrPrefix("0.0"));
541            testPrefixes.add(new AddrTools.AddrPrefix("0"));
542            testPrefixes.add(new AddrTools.AddrPrefix("10.0.0.0"));
543            testPrefixes.add(new AddrTools.AddrPrefix("10.0.0"));
544            testPrefixes.add(new AddrTools.AddrPrefix("10.0"));
545            testPrefixes.add(new AddrTools.AddrPrefix("10"));
546            testPrefixes.add(new AddrTools.AddrPrefix("192.168"));
547            testPrefixes.add(new AddrTools.AddrPrefix("192.168.0"));
548            testPrefixes.add(new AddrTools.AddrPrefix("192.168.1"));
549            testPrefixes.add(new AddrTools.AddrPrefix("192.168.100"));
550            testPrefixes.add(new AddrTools.AddrPrefix("192.168.101"));
551            testPrefixes.add(new AddrTools.AddrPrefix("172.16"));
552            final int tps = testPrefixes.size();
553            Main.getOut().println("[AddrPrefix sample size: " + tps + ".]");
554    
555            // All hash values seen so far, and what they are for.
556            // Test hash quality across full Integer range.
557            final HashMap<Integer,List<AddrTools.AddrPrefix>> hashes = new HashMap<Integer, List<AddrTools.AddrPrefix>>(2*tps);
558            int collisionCount = 0;
559            // Test hash quality across minimum-possible range (ie the set size).
560            final BitSet constrainedHashes = new BitSet(tps);
561            int collisionCountConstrained = 0;
562            for(final AddrTools.AddrPrefix ap : testPrefixes)
563                {
564                final Integer h = new Integer(ap.hashCode());
565    
566                List<AddrTools.AddrPrefix> collisions = hashes.get(h);
567                if(collisions == null)
568                    {
569                    collisions = new ArrayList<AddrTools.AddrPrefix>(1);
570                    hashes.put(h, collisions);
571                    }
572                else
573                    {
574                    ++collisionCount;
575                    Main.getOut().println("Hash collision between "+ap+" and "+collisions);
576                    if(collisions.size() >= MAX_COLLISIONS-1)
577                        { fail("TOO MANY COLLISIONS"); }
578                    }
579                collisions.add(ap);
580    
581                // Test very harsh constrained conditions.
582                final int hc = Math.abs(ap.hashCode() % tps);
583                if(constrainedHashes.get(hc)) { ++collisionCountConstrained; }
584                else { constrainedHashes.set(hc); }
585                }
586    
587            Main.getOut().println("[Hash collisions: " + collisionCount + " = " + ((100.0f * collisionCount) / tps) + "%.]");
588            Main.getOut().println("[Maximally constrained hash collisions: " + collisionCountConstrained + " = " + ((100.0f * collisionCountConstrained) / tps) + "%.]");
589            }
590    
591    
592        /**Private source of OK pseudo-random numbers. */
593        private static final Random rnd = new Random();
594        }