001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.request.mapper.parameter;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Locale;
024import java.util.Set;
025import java.util.TreeSet;
026
027import org.apache.commons.collections4.CollectionUtils;
028import org.apache.wicket.request.IRequestMapper;
029import org.apache.wicket.util.io.IClusterable;
030import org.apache.wicket.util.lang.Args;
031import org.apache.wicket.util.lang.Objects;
032import org.apache.wicket.util.string.StringValue;
033import org.apache.wicket.util.string.Strings;
034
035/**
036 * Mutable class that holds parameters of a Page. Page parameters consist of indexed parameters and
037 * named parameters. Indexed parameters are URL segments before the query string. Named parameters
038 * are usually represented as query string params (i.e. ?arg1=var1&arg2=val)
039 * <p>
040 * <strong>Indexed vs Named Parameters</strong>: Suppose we mounted a page on {@code /user} and the
041 * following url was accessed {@code /user/profile/bob?action=view&redirect=false}. In this example
042 * {@code profile} and {@code bob} are indexed parameters with respective indexes 0 and 1.
043 * {@code action} and {@code redirect} are named parameters.
044 * </p>
045 * <p>
046 * How those parameters are populated depends on the {@link IRequestMapper}s
047 * 
048 * @author Matej Knopp
049 */
050public class PageParameters implements IClusterable, IIndexedParameters, INamedParameters
051{
052        private static final long serialVersionUID = 1L;
053
054        private List<String> indexedParameters;
055
056        private List<NamedPair> namedParameters;
057
058        private Locale locale = Locale.getDefault(Locale.Category.DISPLAY);
059
060        /**
061         * Constructor.
062         */
063        public PageParameters()
064        {
065        }
066
067        /**
068         * Copy constructor.
069         * 
070         * @param copy
071         *          The parameters to copy from
072         */
073        public PageParameters(final PageParameters copy)
074        {
075                if (copy != null)
076                {
077                        mergeWith(copy);
078                        setLocale(copy.locale);
079                }
080        }
081
082        /**
083         * @return count of indexed parameters
084         */
085        public int getIndexedCount()
086        {
087                return indexedParameters != null ? indexedParameters.size() : 0;
088        }
089
090        /**
091         * @see org.apache.wicket.request.mapper.parameter.IIndexedParameters#set(int, java.lang.Object)
092         */
093        @Override
094        public PageParameters set(final int index, final Object object)
095        {
096                if (indexedParameters == null)
097                {
098                        indexedParameters = new ArrayList<>(index);
099                }
100
101                for (int i = indexedParameters.size(); i <= index; ++i)
102                {
103                        indexedParameters.add(null);
104                }
105
106                indexedParameters.set(index, Strings.toString(object));
107                return this;
108        }
109
110        @Override
111        public StringValue get(final int index)
112        {
113                if (indexedParameters != null)
114                {
115                        if ((index >= 0) && (index < indexedParameters.size()))
116                        {
117                                return StringValue.valueOf(indexedParameters.get(index), locale);
118                        }
119                }
120                return StringValue.valueOf((String)null);
121        }
122
123        @Override
124        public PageParameters remove(final int index)
125        {
126                if (indexedParameters != null)
127                {
128                        if ((index >= 0) && (index < indexedParameters.size()))
129                        {
130                                indexedParameters.remove(index);
131                        }
132                }
133                return this;
134        }
135
136        @Override
137        public Set<String> getNamedKeys()
138        {
139                if ((namedParameters == null) || namedParameters.isEmpty())
140                {
141                        return Collections.emptySet();
142                }
143                Set<String> set = new TreeSet<>();
144                for (NamedPair entry : namedParameters)
145                {
146                        set.add(entry.getKey());
147                }
148                return Collections.unmodifiableSet(set);
149        }
150
151        @Override
152        public StringValue get(final String name)
153        {
154                Args.notNull(name, "name");
155
156                if (namedParameters != null)
157                {
158                        for (NamedPair entry : namedParameters)
159                        {
160                                if (entry.getKey().equals(name))
161                                {
162                                        return StringValue.valueOf(entry.getValue(), locale);
163                                }
164                        }
165                }
166                return StringValue.valueOf((String)null);
167        }
168
169        @Override
170        public List<StringValue> getValues(final String name)
171        {
172                Args.notNull(name, "name");
173
174                if (namedParameters != null)
175                {
176                        List<StringValue> result = new ArrayList<>();
177                        for (NamedPair entry : namedParameters)
178                        {
179                                if (entry.getKey().equals(name))
180                                {
181                                        result.add(StringValue.valueOf(entry.getValue(), locale));
182                                }
183                        }
184                        return Collections.unmodifiableList(result);
185                }
186                else
187                {
188                        return Collections.emptyList();
189                }
190        }
191
192        @Override
193        public List<NamedPair> getAllNamed()
194        {
195                return namedParameters != null ? Collections.unmodifiableList(namedParameters) : Collections.<NamedPair>emptyList();
196        }
197
198        @Override
199        public List<NamedPair> getAllNamedByType(Type type)
200        {
201                List<NamedPair> allNamed = getAllNamed();
202                if (type == null || allNamed.isEmpty())
203                {
204                        return allNamed;
205                }
206
207                List<NamedPair> parametersByType = new ArrayList<>();
208                Iterator<NamedPair> iterator = allNamed.iterator();
209                while (iterator.hasNext())
210                {
211                        NamedPair pair = iterator.next();
212                        if (type == pair.getType())
213                        {
214                                parametersByType.add(pair);
215                        }
216                }
217                return Collections.unmodifiableList(parametersByType);
218        }
219
220        @Override
221        public int getPosition(final String name)
222        {
223                int index = -1;
224                if (namedParameters != null)
225                {
226                        for (int i = 0; i < namedParameters.size(); i++)
227                        {
228                                NamedPair entry = namedParameters.get(i);
229                                if (entry.getKey().equals(name))
230                                {
231                                        index = i;
232                                        break;
233                                }
234                        }
235                }
236                return index;
237        }
238
239        @Override
240        public PageParameters remove(final String name, final String... values)
241        {
242                Args.notNull(name, "name");
243
244                if (namedParameters != null)
245                {
246                        for (Iterator<NamedPair> i = namedParameters.iterator(); i.hasNext();)
247                        {
248                                NamedPair e = i.next();
249                                if (e.getKey().equals(name))
250                                {
251                                        if (values != null && values.length > 0)
252                                        {
253                                                for (String value : values)
254                                                {
255                                                        if (e.getValue().equals(value))
256                                                        {
257                                                                i.remove();
258                                                                break;
259                                                        }
260                                                }
261                                        }
262                                        else
263                                        {
264                                                i.remove();
265                                        }
266                                }
267                        }
268                }
269                return this;
270        }
271
272        /**
273         * Adds a page parameter to these with {@code name} and {@code value}
274         * 
275         * @param name
276         * @param value
277         * @return these
278         */
279        public PageParameters add(final String name, final Object value)
280        {
281                return add(name, value, Type.MANUAL);
282        }
283
284        @Override
285        public PageParameters add(final String name, final Object value, Type type)
286        {
287                return add(name, value, -1, type);
288        }
289
290        @Override
291        public PageParameters add(final String name, final Object value, final int index, Type type)
292        {
293                Args.notEmpty(name, "name");
294                Args.notNull(value, "value");
295
296                if (value instanceof String[])
297                {
298                        addNamed(name, (String[]) value, index, type);
299                }
300                else
301                {
302                        addNamed(name, value.toString(), index, type);
303                }
304
305                return this;
306        }
307
308        private void addNamed(String name, String[] values, int index, Type type) 
309        {
310                if (namedParameters == null && values.length > 0)
311                {
312                        namedParameters = new ArrayList<>(values.length);
313                }
314
315                for (String val : values)
316                {
317                        addNamed(name, val, index, type);
318                }
319        }
320
321        private void addNamed(String name, String value, int index, Type type) 
322        {
323                if (namedParameters == null)
324                {
325                        namedParameters = new ArrayList<>(1);
326                }
327
328                NamedPair entry = new NamedPair(name, value, type);
329
330                if (index < 0 || index > namedParameters.size())
331                {
332                        namedParameters.add(entry);
333                }
334                else
335                {
336                        namedParameters.add(index, entry);
337                }
338        }
339
340        /**
341         * Sets the page parameter with {@code name} and {@code value} at the given {@code index}
342         * 
343         * @param name
344         * @param value
345         * @param index
346         * @return this
347         */
348        public PageParameters set(final String name, final Object value, final int index)
349        {
350                return set(name, value, index, Type.MANUAL);
351        }
352
353        @Override
354        public PageParameters set(final String name, final Object value, final int index, Type type)
355        {
356                remove(name);
357
358                if (value != null)
359                {
360                        add(name, value, index, type);
361                }
362                return this;
363        }
364
365        /**
366         * Sets the page parameter with {@code name} and {@code value}
367         * 
368         * @param name
369         * @param value
370         * @return this
371         */
372        public PageParameters set(final String name, final Object value)
373        {
374                return set(name, value, Type.MANUAL);
375        }
376
377        @Override
378        public PageParameters set(final String name, final Object value, Type type)
379        {
380                int position = getPosition(name);
381                set(name, value, position, type);
382                return this;
383        }
384
385        @Override
386        public PageParameters clearIndexed()
387        {
388                indexedParameters = null;
389                return this;
390        }
391
392        @Override
393        public PageParameters clearNamed()
394        {
395                namedParameters = null;
396                return this;
397        }
398
399        /**
400         * Copy the page parameters
401         * 
402         * @param other
403         *          The new parameters
404         * @return this instance, for chaining
405         */
406        public PageParameters overwriteWith(final PageParameters other)
407        {
408                if (this != other)
409                {
410                        indexedParameters = other.indexedParameters;
411                        namedParameters = other.namedParameters;
412                        locale = other.locale;
413                }
414                return this;
415        }
416
417        /**
418         * Merges the page parameters into this, overwriting existing values
419         * 
420         * @param other
421         *          The parameters to merge
422         * @return this instance, for chaining
423         */
424        public PageParameters mergeWith(final PageParameters other)
425        {
426                if (other != null && this != other)
427                {
428                        mergeIndexed(other);
429                        mergeNamed(other);
430                }
431                return this;
432        }
433
434        private void mergeIndexed(PageParameters other)
435        {
436                final int otherIndexedCount = other.getIndexedCount();
437                for (int index = 0; index < otherIndexedCount; index++)
438                {
439                        final StringValue value = other.get(index);
440                        if (!value.isNull())
441                        {
442                                set(index, value);
443                        }
444                }
445        }
446
447        private void mergeNamed(PageParameters other) 
448        {
449                final List<NamedPair> otherNamed = other.namedParameters;
450                if (otherNamed == null || otherNamed.isEmpty())
451                {
452                        return;
453                }
454
455                for (NamedPair curNamed : otherNamed)
456                {
457                        remove(curNamed.getKey());
458                }
459
460                if (this.namedParameters == null)
461                {
462                        this.namedParameters = new ArrayList<>(otherNamed.size());
463                }
464
465                for (NamedPair curNamed : otherNamed)
466                {
467                        add(curNamed.getKey(), curNamed.getValue(),  curNamed.getType());
468                }
469        }
470
471        @Override
472        public int hashCode()
473        {
474                final int prime = 31;
475                int result = 1;
476                result = prime * result + ((indexedParameters == null) ? 0 : indexedParameters.hashCode());
477                result = prime * result + ((namedParameters == null) ? 0 : namedParameters.hashCode());
478                return result;
479        }
480
481        @Override
482        public boolean equals(Object obj)
483        {
484                if (this == obj)
485                        return true;
486                if (obj == null)
487                        return false;
488                if (getClass() != obj.getClass())
489                        return false;
490                PageParameters other = (PageParameters)obj;
491                if (indexedParameters == null)
492                {
493                        if (other.indexedParameters != null)
494                                return false;
495                }
496                else if (!indexedParameters.equals(other.indexedParameters))
497                        return false;
498                if (namedParameters == null)
499                {
500                        if (other.namedParameters != null)
501                                return false;
502                }
503                else if (other.namedParameters == null)
504                        return false;
505                else if (!CollectionUtils.isEqualCollection(namedParameters, other.namedParameters))
506                        return false;
507                return true;
508        }
509
510        /**
511         * Compares two {@link PageParameters} objects.
512         * 
513         * @param p1
514         *          The first parameters
515         * @param p2
516         *          The second parameters
517         * @return <code>true</code> if the objects are equal, <code>false</code> otherwise.
518         */
519        public static boolean equals(final PageParameters p1, final PageParameters p2)
520        {
521                if (Objects.equal(p1, p2))
522                {
523                        return true;
524                }
525                if ((p1 == null) && (p2.getIndexedCount() == 0) && p2.getNamedKeys().isEmpty())
526                {
527                        return true;
528                }
529                if ((p2 == null) && (p1.getIndexedCount() == 0) && p1.getNamedKeys().isEmpty())
530                {
531                        return true;
532                }
533                return false;
534        }
535
536        /**
537         * @return <code>true</code> if the parameters are empty, <code>false</code> otherwise.
538         */
539        public boolean isEmpty()
540        {
541                return (getIndexedCount() == 0) && getNamedKeys().isEmpty();
542        }
543
544        public PageParameters setLocale(Locale locale)
545        {
546                this.locale = locale != null ? locale : Locale.getDefault(Locale.Category.DISPLAY);
547                return this;
548        }
549
550        @Override
551        public String toString()
552        {
553                StringBuilder str = new StringBuilder();
554
555                if (indexedParameters != null)
556                {
557                        for (int i = 0; i < indexedParameters.size(); i++)
558                        {
559                                if (i > 0)
560                                {
561                                        str.append(", ");
562                                }
563
564                                str.append(i);
565                                str.append('=');
566                                str.append('[').append(indexedParameters.get(i)).append(']');
567                        }
568                }
569
570                if (str.length() > 0)
571                {
572                        str.append(", ");
573                }
574
575                if (namedParameters != null)
576                {
577                        for (int i = 0; i < namedParameters.size(); i++)
578                        {
579                                NamedPair entry = namedParameters.get(i);
580
581                                if (i > 0)
582                                {
583                                        str.append(", ");
584                                }
585
586                                str.append(entry.getKey());
587                                str.append('=');
588                                str.append('[').append(entry.getValue()).append(']');
589                        }
590                }
591                return str.toString();
592        }
593}