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}