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 String fragment;
059
060        private Locale locale = Locale.getDefault(Locale.Category.DISPLAY);
061
062        /**
063         * Constructor.
064         */
065        public PageParameters()
066        {
067        }
068
069        /**
070         * Copy constructor.
071         *
072         * @param copy
073         *          The parameters to copy from
074         */
075        public PageParameters(final PageParameters copy)
076        {
077                if (copy != null)
078                {
079                        mergeWith(copy);
080                        setLocale(copy.locale);
081                }
082        }
083
084        /**
085         * @return count of indexed parameters
086         */
087        public int getIndexedCount()
088        {
089                return indexedParameters != null ? indexedParameters.size() : 0;
090        }
091
092        /**
093         * @return count of named parameters
094         */
095        public int getNamedCount()
096        {
097                return namedParameters != null ? namedParameters.size() : 0;
098        }
099
100        /**
101         * @see org.apache.wicket.request.mapper.parameter.IIndexedParameters#set(int, java.lang.Object)
102         */
103        @Override
104        public PageParameters set(final int index, final Object object)
105        {
106                if (indexedParameters == null)
107                {
108                        indexedParameters = new ArrayList<>(index);
109                }
110
111                for (int i = indexedParameters.size(); i <= index; ++i)
112                {
113                        indexedParameters.add(null);
114                }
115
116                indexedParameters.set(index, Strings.toString(object));
117                return this;
118        }
119
120        @Override
121        public StringValue get(final int index)
122        {
123                if (indexedParameters != null)
124                {
125                        if ((index >= 0) && (index < indexedParameters.size()))
126                        {
127                                return StringValue.valueOf(indexedParameters.get(index), locale);
128                        }
129                }
130                return StringValue.valueOf((String)null);
131        }
132
133        @Override
134        public PageParameters remove(final int index)
135        {
136                if (indexedParameters != null)
137                {
138                        if ((index >= 0) && (index < indexedParameters.size()))
139                        {
140                                indexedParameters.remove(index);
141                        }
142                }
143                return this;
144        }
145
146        @Override
147        public Set<String> getNamedKeys()
148        {
149                if ((namedParameters == null) || namedParameters.isEmpty())
150                {
151                        return Collections.emptySet();
152                }
153                Set<String> set = new TreeSet<>();
154                for (NamedPair entry : namedParameters)
155                {
156                        set.add(entry.getKey());
157                }
158                return Collections.unmodifiableSet(set);
159        }
160
161        /**
162         * Checks if the parameter with the given name exists
163         *
164         * @param name the parameter name
165         * @return {@code true} if the parameter exists, {@code false} otherwise
166         */
167        public boolean contains(final String name)
168        {
169                Args.notNull(name, "name");
170
171                if (namedParameters != null)
172                {
173                        for (NamedPair entry : namedParameters)
174                        {
175                                if (entry.getKey().equals(name))
176                                {
177                                        return true;
178                                }
179                        }
180                }
181                return false;
182        }
183
184        @Override
185        public StringValue get(final String name)
186        {
187                Args.notNull(name, "name");
188
189                if (namedParameters != null)
190                {
191                        for (NamedPair entry : namedParameters)
192                        {
193                                if (entry.getKey().equals(name))
194                                {
195                                        return StringValue.valueOf(entry.getValue(), locale);
196                                }
197                        }
198                }
199                return StringValue.valueOf((String)null);
200        }
201
202        @Override
203        public List<StringValue> getValues(final String name)
204        {
205                Args.notNull(name, "name");
206
207                if (namedParameters != null)
208                {
209                        List<StringValue> result = new ArrayList<>();
210                        for (NamedPair entry : namedParameters)
211                        {
212                                if (entry.getKey().equals(name))
213                                {
214                                        result.add(StringValue.valueOf(entry.getValue(), locale));
215                                }
216                        }
217                        return Collections.unmodifiableList(result);
218                }
219                else
220                {
221                        return Collections.emptyList();
222                }
223        }
224
225        @Override
226        public List<NamedPair> getAllNamed()
227        {
228                return namedParameters != null ? Collections.unmodifiableList(namedParameters) : Collections.<NamedPair>emptyList();
229        }
230
231        @Override
232        public List<NamedPair> getAllNamedByType(Type type)
233        {
234                List<NamedPair> allNamed = getAllNamed();
235                if (type == null || allNamed.isEmpty())
236                {
237                        return allNamed;
238                }
239
240                List<NamedPair> parametersByType = new ArrayList<>();
241                for (NamedPair pair : allNamed) {
242                        if (type == pair.getType()) {
243                                parametersByType.add(pair);
244                        }
245                }
246                return Collections.unmodifiableList(parametersByType);
247        }
248
249        @Override
250        public int getPosition(final String name)
251        {
252                int index = -1;
253                if (namedParameters != null)
254                {
255                        for (int i = 0; i < namedParameters.size(); i++)
256                        {
257                                NamedPair entry = namedParameters.get(i);
258                                if (entry.getKey().equals(name))
259                                {
260                                        index = i;
261                                        break;
262                                }
263                        }
264                }
265                return index;
266        }
267
268        @Override
269        public PageParameters remove(final String name, final String... values)
270        {
271                Args.notNull(name, "name");
272
273                if (namedParameters != null)
274                {
275                        for (Iterator<NamedPair> i = namedParameters.iterator(); i.hasNext();)
276                        {
277                                NamedPair e = i.next();
278                                if (e.getKey().equals(name))
279                                {
280                                        if (values != null && values.length > 0)
281                                        {
282                                                for (String value : values)
283                                                {
284                                                        if (e.getValue().equals(value))
285                                                        {
286                                                                i.remove();
287                                                                break;
288                                                        }
289                                                }
290                                        }
291                                        else
292                                        {
293                                                i.remove();
294                                        }
295                                }
296                        }
297                }
298                return this;
299        }
300
301        /**
302         * Adds a page parameter to these with {@code name} and {@code value}
303         *
304         * @param name
305         * @param value
306         * @return these
307         */
308        public PageParameters add(final String name, final Object value)
309        {
310                return add(name, value, Type.MANUAL);
311        }
312
313        @Override
314        public PageParameters add(final String name, final Object value, Type type)
315        {
316                return add(name, value, -1, type);
317        }
318
319        @Override
320        public PageParameters add(final String name, final Object value, final int index, Type type)
321        {
322                Args.notEmpty(name, "name");
323                Args.notNull(value, "value");
324
325                if (value instanceof String[])
326                {
327                        addNamed(name, (String[]) value, index, type);
328                }
329                else
330                {
331                        addNamed(name, value.toString(), index, type);
332                }
333
334                return this;
335        }
336
337        private void addNamed(String name, String[] values, int index, Type type)
338        {
339                if (namedParameters == null && values.length > 0)
340                {
341                        namedParameters = new ArrayList<>(values.length);
342                }
343
344                for (String val : values)
345                {
346                        addNamed(name, val, index, type);
347                }
348        }
349
350        private void addNamed(String name, String value, int index, Type type)
351        {
352                if (namedParameters == null)
353                {
354                        namedParameters = new ArrayList<>(1);
355                }
356
357                NamedPair entry = new NamedPair(name, value, type);
358
359                if (index < 0 || index > namedParameters.size())
360                {
361                        namedParameters.add(entry);
362                }
363                else
364                {
365                        namedParameters.add(index, entry);
366                }
367        }
368
369        /**
370         * Sets the page parameter with {@code name} and {@code value} at the given {@code index}
371         *
372         * @param name
373         * @param value
374         * @param index
375         * @return this
376         */
377        public PageParameters set(final String name, final Object value, final int index)
378        {
379                return set(name, value, index, Type.MANUAL);
380        }
381
382        @Override
383        public PageParameters set(final String name, final Object value, final int index, Type type)
384        {
385                remove(name);
386
387                if (value != null)
388                {
389                        add(name, value, index, type);
390                }
391                return this;
392        }
393
394        /**
395         * Sets the page parameter with {@code name} and {@code value}
396         *
397         * @param name
398         * @param value
399         * @return this
400         */
401        public PageParameters set(final String name, final Object value)
402        {
403                return set(name, value, Type.MANUAL);
404        }
405
406        @Override
407        public PageParameters set(final String name, final Object value, Type type)
408        {
409                int position = getPosition(name);
410                set(name, value, position, type);
411                return this;
412        }
413
414        @Override
415        public PageParameters clearIndexed()
416        {
417                indexedParameters = null;
418                return this;
419        }
420
421        @Override
422        public PageParameters clearNamed()
423        {
424                namedParameters = null;
425                return this;
426        }
427
428        /**
429         * Copy the page parameters
430         *
431         * @param other
432         *          The new parameters
433         * @return this instance, for chaining
434         */
435        public PageParameters overwriteWith(final PageParameters other)
436        {
437                if (this != other)
438                {
439                        indexedParameters = other.indexedParameters;
440                        namedParameters = other.namedParameters;
441                        locale = other.locale;
442                        fragment = other.fragment;
443                }
444                return this;
445        }
446
447        /**
448         * Merges the page parameters into this, overwriting existing values
449         *
450         * @param other
451         *          The parameters to merge
452         * @return this instance, for chaining
453         */
454        public PageParameters mergeWith(final PageParameters other)
455        {
456                if (other != null && this != other)
457                {
458                        mergeIndexed(other);
459                        mergeNamed(other);
460
461                        fragment = Objects.defaultIfNull(other.fragment, fragment);
462                }
463                return this;
464        }
465
466        private void mergeIndexed(PageParameters other)
467        {
468                final int otherIndexedCount = other.getIndexedCount();
469                for (int index = 0; index < otherIndexedCount; index++)
470                {
471                        final StringValue value = other.get(index);
472                        if (!value.isNull())
473                        {
474                                set(index, value);
475                        }
476                }
477        }
478
479        private void mergeNamed(PageParameters other)
480        {
481                final List<NamedPair> otherNamed = other.namedParameters;
482                if (otherNamed == null || otherNamed.isEmpty())
483                {
484                        return;
485                }
486
487                for (NamedPair curNamed : otherNamed)
488                {
489                        remove(curNamed.getKey());
490                }
491
492                if (this.namedParameters == null)
493                {
494                        this.namedParameters = new ArrayList<>(otherNamed.size());
495                }
496
497                for (NamedPair curNamed : otherNamed)
498                {
499                        add(curNamed.getKey(), curNamed.getValue(),  curNamed.getType());
500                }
501        }
502
503        public String getFragment()
504        {
505                return fragment;
506        }
507
508        public void setFragment(String fragment)
509        {
510                this.fragment = fragment;
511        }
512
513        @Override
514        public int hashCode()
515        {
516                final int prime = 31;
517                int result = 1;
518                result = prime * result + ((indexedParameters == null) ? 0 : indexedParameters.hashCode());
519                result = prime * result + ((namedParameters == null) ? 0 : namedParameters.hashCode());
520                result = prime * result + ((fragment == null) ? 0 : fragment.hashCode());
521                return result;
522        }
523
524        @Override
525        public boolean equals(Object obj)
526        {
527                if (this == obj)
528                        return true;
529                if (obj == null)
530                        return false;
531                if (getClass() != obj.getClass())
532                        return false;
533                PageParameters other = (PageParameters)obj;
534                if (indexedParameters == null)
535                {
536                        if (other.indexedParameters != null)
537                                return false;
538                }
539                else if (!indexedParameters.equals(other.indexedParameters))
540                        return false;
541                if (namedParameters == null)
542                {
543                        if (other.namedParameters != null)
544                                return false;
545                }
546                else if (other.namedParameters == null)
547                        return false;
548                else if (!CollectionUtils.isEqualCollection(namedParameters, other.namedParameters))
549                        return false;
550                if(!Strings.isEqual(other.fragment, fragment))
551                        return false;
552                return true;
553        }
554
555        /**
556         * Compares two {@link PageParameters} objects.
557         *
558         * @param p1
559         *          The first parameters
560         * @param p2
561         *          The second parameters
562         * @return <code>true</code> if the objects are equal, <code>false</code> otherwise.
563         */
564        public static boolean equals(final PageParameters p1, final PageParameters p2)
565        {
566                if (Objects.equal(p1, p2))
567                {
568                        return true;
569                }
570                if ((p1 == null) && (p2.getIndexedCount() == 0) && p2.getNamedCount() == 0 && p2.fragment == null)
571                {
572                        return true;
573                }
574                if ((p2 == null) && (p1.getIndexedCount() == 0) && p1.getNamedCount() == 0 && p1.fragment == null)
575                {
576                        return true;
577                }
578                return false;
579        }
580
581        /**
582         * @return <code>true</code> if the parameters are empty and fragment is null, <code>false</code> otherwise.
583         */
584        public boolean isEmpty()
585        {
586                return getIndexedCount() == 0 && getNamedCount() == 0 && fragment == null;
587        }
588
589        public PageParameters setLocale(Locale locale)
590        {
591                this.locale = locale != null ? locale : Locale.getDefault(Locale.Category.DISPLAY);
592                return this;
593        }
594
595        @Override
596        public String toString()
597        {
598                StringBuilder str = new StringBuilder();
599
600                if (indexedParameters != null)
601                {
602                        for (int i = 0; i < indexedParameters.size(); i++)
603                        {
604                                if (i > 0)
605                                {
606                                        str.append(", ");
607                                }
608
609                                str.append(i);
610                                str.append('=');
611                                str.append('[').append(indexedParameters.get(i)).append(']');
612                        }
613                }
614
615                if (str.length() > 0)
616                {
617                        str.append(", ");
618                }
619
620                if (namedParameters != null)
621                {
622                        for (int i = 0; i < namedParameters.size(); i++)
623                        {
624                                NamedPair entry = namedParameters.get(i);
625
626                                if (i > 0)
627                                {
628                                        str.append(", ");
629                                }
630
631                                str.append(entry.getKey());
632                                str.append('=');
633                                str.append('[').append(entry.getValue()).append(']');
634                        }
635                }
636
637                if (fragment != null)
638                {
639                        if (str.length() > 0)
640                        { 
641                                str.append(", ");
642                        }
643                        
644                        str.append("fragment=").append('\'').append(fragment).append('\'');
645                }
646
647                return str.toString();
648        }
649}