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