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}