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.test.dev;
031    
032    import java.util.ArrayList;
033    import java.util.Collection;
034    import java.util.Collections;
035    import java.util.Iterator;
036    import java.util.List;
037    import java.util.Map;
038    import java.util.SortedSet;
039    
040    import junit.framework.TestCase;
041    
042    import org.hd.d.pg2k.svrCore.Name;
043    import org.hd.d.pg2k.svrCore.TextUtils;
044    import org.hd.d.pg2k.webSvr.exhibit.TreeFilterBean;
045    
046    /**Tests aspects of the the TreeFilterBean and related functionality.
047     */
048    public final class TreeFilterBeanTest extends TestCase
049        {
050        public TreeFilterBeanTest(final String name)
051            {
052            super(name);
053            }
054    
055        /**Test conversion of partial URIs to prefix word sequences. */
056        public void testConvertTrailingURIToWordSequence()
057            {
058            assertEquals("Simple conversion 1a: one-word partial URI with leading /", "word1-", TreeFilterBean.convertTrailingURIToWordSequence("/word1/index.html").toString());
059            assertEquals("Simple conversion 1b: one-word partial URI without leading /", "word1-", TreeFilterBean.convertTrailingURIToWordSequence("word1/index.html").toString());
060            assertEquals("Simple conversion 1c: one-word partial URI with repeated /", "word1-", TreeFilterBean.convertTrailingURIToWordSequence("////word1/////index.html").toString());
061    
062            assertEquals("Simple conversion 2: multiple words", "the-cat-sat-", TreeFilterBean.convertTrailingURIToWordSequence("/the/cat/sat/index.jsp?page=9").toString());
063    
064            assertEquals("Simple conversion 3: conversion to lowercase", "the-cat-sat-", TreeFilterBean.convertTrailingURIToWordSequence("/The/caT/sat/index.jsp?").toString());
065    
066            assertEquals("Simple conversion 4a: final component with dot is discarded", "word-second-", TreeFilterBean.convertTrailingURIToWordSequence("/word/second/x.y").toString());
067            assertEquals("Simple conversion 4b: final component before trailing slash is kept", "word-second-", TreeFilterBean.convertTrailingURIToWordSequence("/word/second/").toString());
068            assertEquals("Simple conversion 4c: final component without dot is treated as dir/word, not discarded", "word-second-", TreeFilterBean.convertTrailingURIToWordSequence("/word/second").toString());
069    
070            try {
071                TreeFilterBean.convertTrailingURIToWordSequence("/the-cat/index.html");
072                fail("Embedded word separator in URI should be rejected.");
073                }
074            catch(final IllegalArgumentException e) { } // Good: we want an exception to have been thrown...
075    
076            try {
077                TreeFilterBean.convertTrailingURIToWordSequence("/the*cat/index.html");
078                fail("Invalid word character in URI should be rejected.");
079                }
080            catch(final IllegalArgumentException e) { } // Good: we want an exception to have been thrown...
081            }
082    
083        /**Test the generation of a browsable tree of exhibits from an input set...
084         * This tests if the tree construction (and generated data set)
085         * behaves as expected.
086         */
087        public void testBrowsableTree()
088            {
089            // Check that looking for uninteresting entries with a root node
090            // unconditionally returns an empty list.
091            final Map<Name,Collection<CharSequence>> emptyMap = Collections.emptyMap();
092            assertTrue("Inspection at root node unconditionally should return an empty list",
093                       TreeFilterBean.getUninterestingNodesFromMap(emptyMap, Name.EMPTY).length == 0);
094    
095            // Short names of the exhibits.
096            final String SHNAME1 = "cat-sat-on-mat-ANON.htxt";
097            final String SHNAME2 = "CAT-fat-DHD.jpg"; // Note different case of main word.
098            final String SHNAME3 = "dog-in-manger-1-ANON.gif";
099            final String SHNAME4 = "dog-in-manger-2-ANON.gif";
100            final String SHNAME5 = "dog-in-manger-11-ANON.gif";
101    
102            // Full names of exhibits.
103            final String EXNAME1 = "memes/" + SHNAME1;
104            final String EXNAME2 = "memes/" + SHNAME2;
105            final String EXNAME3 = "other/" + SHNAME3;
106            final String EXNAME4 = "another/" + SHNAME4;
107            final String EXNAME5 = "another/" + SHNAME5;
108    
109            // For now, ignore the effects of attribute words...
110    //        final long now = System.currentTimeMillis();
111    
112            final List<Name.ExhibitFull> exhibits1 = new ArrayList<Name.ExhibitFull>();
113            exhibits1.add(Name.ExhibitFull.create(EXNAME1));
114            exhibits1.add(Name.ExhibitFull.create(EXNAME2));
115            exhibits1.add(Name.ExhibitFull.create(EXNAME3));
116            exhibits1.add(Name.ExhibitFull.create(EXNAME4));
117            exhibits1.add(Name.ExhibitFull.create(EXNAME5));
118    
119            // Build this Map non-optimised and then optimised,
120            // checking that it gives the right results in both cases.
121            for(final boolean optimised : new boolean[]{ false, true} )
122                {
123                // Build this map first with SortedSet nodes then with List nodes.
124                for(final boolean asSet : new boolean[]{ false, true} )
125                    {
126                    final Map<Name,Collection<CharSequence>> m1 = TreeFilterBean.computePrefixMap(Collections.unmodifiableList(exhibits1), asSet, optimised);
127    
128                    // Check that all node entries are in correct sorted order.
129                    for(final Iterator<Collection<CharSequence>> it = m1.values().iterator(); it.hasNext(); )
130                        {
131                        final Collection<CharSequence> nodeEntries = (it.next());
132                        if(nodeEntries.size() > 1)
133                            {
134                            final Iterator<CharSequence> eit = nodeEntries.iterator();
135                            final CharSequence prev = eit.next();
136                            while(eit.hasNext())
137                                {
138                                final CharSequence cur = eit.next();
139                                assertTrue("all node elements must be correctly ordered",
140                                           TextUtils.CASE_INSENSITIVE_ORDER.compare(prev, cur) <= 0);
141                                }
142                            }
143                        }
144    
145                    // Root node should contain just the entire flattened tree
146                    // if it is small enough when we are optimising.
147                    // Otherwise we expect to find the "cat-" and "dog-...-" subtrees.
148                    // (And should be the right type.)
149                    final Collection<CharSequence> rn1 = m1.get(Name.EMPTY);
150                    assertTrue("expect nodes to be of right type (List/SortedSet)",
151                               (asSet ? (rn1 instanceof SortedSet) : (rn1 instanceof List)));
152                    final SortedSet<CharSequence> rootNode1 = TextUtils.createCharSequenceSortedSet(rn1);
153                    if(!optimised)
154                        {
155                        assertEquals("expected exactly two subtrees (for cat and dog) at root", 2, rootNode1.size());
156                        assertTrue("expected to find the cat and dog subtrees at root (optimised="+optimised+"): " + rootNode1,
157                                        (rootNode1.contains("cat-")) && rootNode1.contains("dog-"));
158                        }
159                    else
160                        {
161                        assertEquals("expect full exhibit set (flattened tree) at root", exhibits1.size(), rootNode1.size());
162                        }
163    
164                    // Look at "cat-" sub-tree.
165                    // We expect to find the entries merged together ignoring case,
166                    // and exactly the two subtrees.
167                    final SortedSet<CharSequence> catNode1 = TextUtils.createCharSequenceSortedSet(m1.get(Name.create("cat-")));
168                    assertTrue("expect exactly two subtrees [or exhibits in optimised case] (for sat and fat) below cat", catNode1.size() == 2);
169                    if(!optimised)
170                        {
171                        assertTrue("expected to find the sat and fat subtrees below cat",
172                            (catNode1.contains("cat-fat-")) && (catNode1.contains("cat-sat-")));
173                        }
174                    else
175                        {
176                        assertTrue("expected to find the sat and fat exhibits below cat",
177                            (catNode1.contains(Name.ExhibitFull.create(EXNAME1).getShortName())) && (catNode1.contains(Name.ExhibitFull.create(EXNAME2).getShortName())));
178                        }
179    
180                    // Look at "cat-fat-" sub-tree.
181                    // We expect to find one actual exhibit entry..
182                    final Collection<CharSequence> catFatNode1 = m1.get(Name.create("cat-fat-"));
183                    assertTrue("expect one exhibit entry in beneath cat-fat", catFatNode1.size() == 1);
184                    assertEquals("expected to find the one exhibit-name entry", catFatNode1.iterator().next(), Name.ExhibitFull.create(EXNAME2).getShortName());
185                    assertTrue("expected to find the one entry for " + SHNAME2, catFatNode1.contains(Name.ExhibitFull.create(EXNAME2).getShortName()));
186    
187                    // Look at "dog-in-manger-" sub-tree.
188                    // We expect to find the two exhibit entries.
189                    final Collection<CharSequence> dogInMangerNode1 = m1.get(Name.create("dog-in-manger-"));
190                    assertTrue("expect three exhibit entries below dog-in-manger", dogInMangerNode1.size() == 3);
191                    assertTrue("expected to find the three dog-in-manger entries",
192                        (dogInMangerNode1.contains(Name.ExhibitFull.create(EXNAME3).getShortName())) &&
193                        (dogInMangerNode1.contains(Name.ExhibitFull.create(EXNAME4).getShortName())) &&
194                        (dogInMangerNode1.contains(Name.ExhibitFull.create(EXNAME5).getShortName())));
195                    // Check uninteresting parents.
196                    final boolean dogInMangerBoring[] = TreeFilterBean.getUninterestingNodesFromMap(m1, "dog-in-manger-");
197                    assertTrue("dog-in-manger- should have two parents noted",
198                               dogInMangerBoring.length == 2);
199                    assertTrue("dog-in-manger- parent nodes should both be marked uninteresting if not optimised",
200                               dogInMangerBoring[0] && dogInMangerBoring[1]);
201    
202                    // Now in the non-optimised case the "dog-" node should point to the "dog-in-" node,
203                    // but in the optimised case it should point straight at the less dull "dog-in-manger-" node.
204                    final Collection<CharSequence> dogNode1 = m1.get(Name.create("dog-"));
205                    if(!optimised)
206                        {
207                        assertTrue("expect one sub-tree below dog", dogNode1.size() == 1);
208                        assertTrue("expected one subnode: " + dogNode1.iterator().next(), dogNode1.contains(Name.create("dog-in-")));
209                        }
210    
211                    // Check that looking for uninteresting entries with a single-word node
212                    // returns an empty list.
213                    assertTrue("Inspection at single-word node unconditionally should an empty list",
214                               TreeFilterBean.getUninterestingNodesFromMap(m1, "cat-").length == 0);
215                    assertTrue("Inspection at single-word node unconditionally should an empty list",
216                               TreeFilterBean.getUninterestingNodesFromMap(m1, "dog-").length == 0);
217    
218                    final boolean catSatOnMatBoring[] = TreeFilterBean.getUninterestingNodesFromMap(m1, "cat-sat-on-mat-");
219                    assertTrue("cat-sat-on-mat- should have three (un)interesting parents noted: " + catSatOnMatBoring.length,
220                               catSatOnMatBoring.length == 3);
221                    assertTrue("cat-sat-on-mat- parent nodes should both be marked uninteresting except first: " +
222                               catSatOnMatBoring[0]+','+catSatOnMatBoring[1]+','+catSatOnMatBoring[2],
223                               (!catSatOnMatBoring[0]) && catSatOnMatBoring[1] && catSatOnMatBoring[2]);
224    
225                    }
226                }
227            }
228        }