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.webSvr.catalogue;
031    
032    import java.util.Arrays;
033    import java.util.Collections;
034    import java.util.List;
035    
036    import org.hd.d.pg2k.webSvr.util.WebConsts;
037    
038    /**
039     * Created by IntelliJ IDEA.
040     * User: Damon Hart-Davis
041     * Date: 03-Jun-2003
042     * Time: 17:40:27
043     */
044    
045    /**Bean to help with the pagination for otherwise large pages; this generates numeric labels.
046     * This can parse an input argument indicating the desired page,
047     * or find it directly in an incoming HttpRequest,
048     * and work out which page is meant and return it as an int for the page logic to use.
049     * <p>
050     * This can also generate the labels/values for one or more HTML form submit buttons
051     * to help generate the HTML page.
052     * <p>
053     * This implementation generates sequential page-numbering for buttons in its simplest form,
054     * but if there are too many buttons to show this then generates a sparser set
055     * that ensures that a user can navigate in few clicks to any page and especially so
056     * to nearby pages as they home in on a target.
057     * <p>
058     * This class is designed to be extended by classes that can put arbitraty labels on
059     * the buttons, for example words to help the user go directly to the desired page.
060     * <p>
061     * If given an unparseable page number as input, this and deriving implementations
062     * should always return a valid page number,
063     * returning the "safe" page number of 1 if they cannot deduce the correct page or a near one.
064     * <p>
065     * This bases its pagination on the properties in WebConsts dictating the maximum number
066     * of items to show on a page and the maximum number of page-choice buttons to show to a user.
067     * <p>
068     * This needs to be told the number of entries to be paginated;
069     * deriving classes may accept other or extra information in lieu.
070     * <p>
071     * This does its basic pagination based on the number of items to be paginated
072     * and the WebConsts that dictate maximum items per page and maximum pages
073     * or choices of pages to go to at once.
074     * In principle these might be user-settable rather than constants.
075     * <p>
076     * This is designed to be used as a bean at page or request scope in a JSP,
077     * though may be usable outside that environment.
078     * It may not be usefully serialisable.
079     * <p>
080     * <strong>THIS CLASS IS NOT THREAD SAFE</strong> since
081     * its usual enviroment and indeed lifetime
082     * is within the thread of a single servlet/JSP page service
083     * and thread-safety is an unnecessary overhead.
084     * Its methods are not synchronized,
085     * and if it is necessary to share one instance between threads
086     * they must hold a lock on the instance.
087     */
088    public final class PaginationBeanNumeric extends PaginationBeanBase
089        {
090        /**Get the page number as a from the String label; in case of bad input return 1.
091         * This parses using Integer.parseInt("string", 10).
092         * <p>
093         * A deriving class may override this for more sophisticated or specialised parses.
094         */
095        @Override
096        public int getPageNumber(final String s)
097            {
098            try { return(Integer.parseInt(s, 10)); }
099            catch(final NumberFormatException e) { return(1); }
100            }
101    
102        /**Gets ordered (unmodifiable) List of String labels/tokens for page buttons.
103         * This implementation makes the labels numeric
104         * and optimises the case of returning contigious labels starting at "1"
105         * to minimise object churn.
106         * <p>
107         * Computes a set of pages and returns String values to use as the values (and legends)
108         * for submit buttons for this class to parse with the setPg(String) routine.
109         * <p>
110         * These labels have to be accepted by setPg(String).
111         * <p>
112         * The labels are unique and identify pages in monotonically-increasing order
113         * as you iterate through the List.
114         * <p>
115         * The labels generated must be deterministic and depend only on the attributes set for the bean.
116         * <p>
117         * Usually the first and last items in the List a labels for the first and last pages in the range.
118         * <p>
119         * Usually labels for the current page and those immediately before and after it are in the
120         * result list.
121         * <p>
122         * The base implementation returns numeric labels,
123         * but this calls getPageLabel() to generate the individual labels,
124         * so it may often be sufficient to override getPageLabel() only in a derived class.
125         * <p>
126         * This will commonly be overridden in deriving classes but the general contract
127         * must be maintained.
128         */
129        @Override
130        public List<String> getPageLabels()
131            {
132            final int _totalPages_ = this.getNumberOfPages();
133    
134            // As an optimisation...
135            // If there few enough pages to show all of them as a contigious sequence from "1",
136            // do so in a way which requires minimal object creation/churn,
137            // since it a very common case.
138            if(_totalPages_ <= WebConsts.MAX_RESULTS_PAGES)
139                { return(generateNumericPageLabels1Upwards(_totalPages_)); }
140    
141            return(super.getPageLabels());
142            }
143    
144        /**Return the page label for the given page (1 upwards), never null; the page must be in range else the result is undefined.
145         * This will commonly be overridden in deriving classes.
146         * <p>
147         * The label generated must be deterministic and depend only on the attributes set for the bean,
148         * and the same as getPageLabels() would have generated for this given page.
149         */
150        @Override
151        public String getPageLabel(final int pageNum)
152            {
153            if((pageNum < 1) || (pageNum > getNumberOfPages())) { return("1"); } // Safe response.
154            return(String.valueOf(pageNum));
155            }
156    
157        /**Return an immutable List of contigious String (int-valued) labels for page buttons from 1 upwards.
158         * In the case where the top value is no more than WebConsts.MAX_RESULTS_PAGES
159         * we return a sub-list of a static singleton immutable List covering this range
160         * without synchronsiation or (significant) object creation for efficiency.
161         *
162         * @param nPages  requests list of String labels for buttons for the decimal integers
163         *     from "1" upwards so a value of 3 would return the list "1", "2", "3" for example;
164         *     the argument must be non-negative
165         */
166        private static List<String> generateNumericPageLabels1Upwards(final int nPages)
167            {
168            if(nPages <= WebConsts.MAX_RESULTS_PAGES)
169                {
170                // Return a completely unwrapped reference where we want the whole lot
171                // as this is faster and saves a object allocation.
172                // The case is quite common, when a search returns lots of results
173                // and is limited to the maximum number of pages.
174                if(nPages == pageLabels1Upwards.size())
175                    { return(pageLabels1Upwards); }
176    
177                // Return sub-list view of early portion.
178                return(pageLabels1Upwards.subList(0, nPages));
179                }
180    
181            // OK, list is too long, so manufacture it from scratch.
182            return(generateNewNumericPageLabels1Upwards(nPages));
183            }
184    
185        /**Generate a new immutable List of pageLabels from "1" upwards each time; argument must be non-negative.
186         * We String.intern() the first few labels for maximum memory efficiency!
187         */
188        private static final List<String> generateNewNumericPageLabels1Upwards(final int nPages)
189            {
190            final String s[] = new String[nPages];
191            for(int i = nPages; --i >= 0; )
192                {
193                String t = String.valueOf(i + 1);
194                if(i < WebConsts.MAX_RESULTS_PAGES) { t = t.intern(); } // Intern commonly-used values.
195                s[i] = t;
196                }
197            return(Collections.unmodifiableList(Arrays.asList(s)));
198            }
199    
200        /**Immutable List of String labels containing decimal integers from 1 to WebConsts.MAX_RESULTS_PAGES.
201         * Note that the label at index i is the value of i+1, eg at index 0 is "1".
202         * <p>
203         * Used to cover the most common cases without needing to generate lots of new objects each time.
204         */
205        private static final List<String> pageLabels1Upwards =
206            generateNewNumericPageLabels1Upwards(WebConsts.MAX_RESULTS_PAGES);
207    
208        /**Unique Serialisation class ID generated by http://random&#46;hd&#46;org/. */
209        private static final long serialVersionUID = -5376824526954786928L;
210        }