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; 018 019import java.io.Serializable; 020import java.util.ArrayDeque; 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.Comparator; 024import java.util.Deque; 025import java.util.Iterator; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Locale; 029import java.util.Map; 030import java.util.Spliterators; 031import java.util.stream.Stream; 032import java.util.stream.StreamSupport; 033 034import org.apache.commons.collections4.map.LinkedMap; 035import org.apache.wicket.core.util.string.ComponentStrings; 036import org.apache.wicket.markup.ComponentTag; 037import org.apache.wicket.markup.ComponentTag.IAutoComponentFactory; 038import org.apache.wicket.markup.IMarkupFragment; 039import org.apache.wicket.markup.Markup; 040import org.apache.wicket.markup.MarkupElement; 041import org.apache.wicket.markup.MarkupException; 042import org.apache.wicket.markup.MarkupFactory; 043import org.apache.wicket.markup.MarkupNotFoundException; 044import org.apache.wicket.markup.MarkupStream; 045import org.apache.wicket.markup.MarkupType; 046import org.apache.wicket.markup.WicketTag; 047import org.apache.wicket.markup.html.border.Border; 048import org.apache.wicket.markup.html.form.AutoLabelResolver; 049import org.apache.wicket.markup.resolver.ComponentResolvers; 050import org.apache.wicket.model.IComponentInheritedModel; 051import org.apache.wicket.model.IModel; 052import org.apache.wicket.model.IWrapModel; 053import org.apache.wicket.settings.DebugSettings; 054import org.apache.wicket.util.lang.Args; 055import org.apache.wicket.util.lang.Classes; 056import org.apache.wicket.util.lang.Generics; 057import org.apache.wicket.util.string.Strings; 058import org.apache.wicket.util.visit.ClassVisitFilter; 059import org.apache.wicket.util.visit.IVisit; 060import org.apache.wicket.util.visit.IVisitor; 061import org.apache.wicket.util.visit.Visits; 062import org.slf4j.Logger; 063import org.slf4j.LoggerFactory; 064 065/** 066 * A MarkupContainer holds a map of child components. 067 * <ul> 068 * <li><b>Children </b>- Children can be added by calling the {@link #add(Component...)} method, and 069 * they can be looked up using a colon separated path. For example, if a container called "a" held a 070 * nested container "b" which held a nested component "c", then a.get("b:c") would return the 071 * Component with id "c". The number of children in a MarkupContainer can be determined by calling 072 * size(), and the whole hierarchy of children held by a MarkupContainer can be traversed by calling 073 * visitChildren(), passing in an implementation of IVisitor.</li> 074 * 075 * <li><b>Markup Rendering </b>- A MarkupContainer also holds/references associated markup which is 076 * used to render the container. As the markup stream for a container is rendered, component 077 * references in the markup are resolved by using the container to look up Components in the 078 * container's component map by id. Each component referenced by the markup stream is given an 079 * opportunity to render itself using the markup stream.</li> 080 * </ul> 081 * <p> 082 * Components may alter their referring tag, replace the tag's body or insert markup after the tag. 083 * But components cannot remove tags from the markup stream. This is an important guarantee because 084 * graphic designers may be setting attributes on component tags that affect visual presentation. 085 * <p> 086 * The type of markup held in a given container subclass can be determined by calling 087 * {@link #getMarkupType()}. Markup is accessed via a MarkupStream object which allows a component 088 * to traverse ComponentTag and RawMarkup MarkupElements while rendering a response. Markup in the 089 * stream may be HTML or some other kind of markup, such as VXML, as determined by the specific 090 * container subclass. 091 * <p> 092 * A markup stream may be directly associated with a container via setMarkupStream. However, a 093 * container which does not have a markup stream (its getMarkupStream() returns null) may inherit a 094 * markup stream from a container above it in the component hierarchy. The 095 * {@link #findMarkupStream()} method will locate the first container at or above this container 096 * which has a markup stream. 097 * <p> 098 * All Page containers set a markup stream before rendering by calling the method 099 * {@link #getAssociatedMarkupStream(boolean)} to load the markup associated with the page. Since 100 * Page is at the top of the container hierarchy, it is guaranteed that {@link #findMarkupStream()} 101 * will always return a valid markup stream. 102 * 103 * @see MarkupStream 104 * @author Jonathan Locke 105 */ 106public abstract class MarkupContainer extends Component implements Iterable<Component> 107{ 108 private static final long serialVersionUID = 1L; 109 110 private static final int INITIAL_CHILD_LIST_CAPACITY = 12; 111 112 /** 113 * The threshold where we start using a Map to store children in, replacing a List. Adding 114 * components to a list is O(n), and to a map O(1). The magic number is 24, due to a Map using 115 * more memory to store its elements and below 24 children there's no discernible difference 116 * between adding to a Map or a List. 117 * 118 * We have focused on adding elements to a list, instead of indexed lookups because adding is an 119 * action that is performed very often, and lookups often are done by component IDs, not index. 120 */ 121 static final int MAPIFY_THRESHOLD = 24; // 32 * 0.75 122 123 /** Log for reporting. */ 124 private static final Logger log = LoggerFactory.getLogger(MarkupContainer.class); 125 126 /** 127 * Metadata key for looking up the list of removed children necessary for tracking modifications 128 * during iteration of the children of this markup container. 129 * 130 * This is stored in meta data because it only is necessary when a child is removed, and this 131 * saves the memory necessary for a field on a widely used class. 132 */ 133 private static final MetaDataKey<LinkedList<RemovedChild>> REMOVALS_KEY = new MetaDataKey<>() 134 { 135 private static final long serialVersionUID = 1L; 136 }; 137 138 /** 139 * Administrative class for detecting removed children during child iteration. Not intended to 140 * be serializable but for e.g. determining the size of the component it has to be serializable. 141 */ 142 private static class RemovedChild implements Serializable 143 { 144 private static final long serialVersionUID = 1L; 145 146 private transient final Component removedChild; 147 private transient final Component previousSibling; 148 149 private RemovedChild(Component removedChild, Component previousSibling) 150 { 151 this.removedChild = removedChild; 152 this.previousSibling = previousSibling; 153 } 154 } 155 156 /** 157 * Administrative counter to keep track of modifications to the list of children during 158 * iteration. 159 * 160 * When the {@link #children_size()} changes due to an addition or removal of a child component, 161 * the modCounter is increased. This way iterators that iterate over the children of this 162 * container can keep track when they need to change their iteration strategy. 163 */ 164 private transient int modCounter = 0; 165 166 /** 167 * The children of this markup container, if any. Can be a Component when there's only one 168 * child, a List when the number of children is fewer than {@link #MAPIFY_THRESHOLD} or a Map 169 * when there are more children. 170 */ 171 private Object children; 172 173 public MarkupContainer(final String id) 174 { 175 this(id, null); 176 } 177 178 public MarkupContainer(final String id, IModel<?> model) 179 { 180 super(id, model); 181 } 182 183 /** 184 * Adds the child component(s) to this container. 185 * 186 * @param children 187 * The child(ren) to add. 188 * @throws IllegalArgumentException 189 * Thrown if a child with the same id is replaced by the add operation. 190 * @return This 191 */ 192 public MarkupContainer add(final Component... children) 193 { 194 for (Component child : children) 195 { 196 Args.notNull(child, "child"); 197 198 if (this == child) 199 { 200 throw new IllegalArgumentException( 201 exceptionMessage("Trying to add this component to itself.")); 202 } 203 204 MarkupContainer parent = getParent(); 205 while (parent != null) 206 { 207 if (child == parent) 208 { 209 String msg = "You can not add a component's parent as child to the component (loop): Component: " + 210 this.toString(false) + "; parent == child: " + parent.toString(false); 211 212 if (child instanceof Border.BorderBodyContainer) 213 { 214 msg += ". Please consider using Border.addToBorder(new " + 215 Classes.simpleName(this.getClass()) + "(\"" + this.getId() + 216 "\", ...) instead of add(...)"; 217 } 218 219 throw new WicketRuntimeException(msg); 220 } 221 222 parent = parent.getParent(); 223 } 224 225 checkHierarchyChange(child); 226 227 if (log.isDebugEnabled()) 228 { 229 log.debug("Add " + child.getId() + " to " + this); 230 } 231 232 // Add the child to my children 233 Component previousChild = children_put(child); 234 if (previousChild != null && previousChild != child) 235 { 236 throw new IllegalArgumentException( 237 exceptionMessage("A child '" + previousChild.getClass().getSimpleName() + 238 "' with id '" + child.getId() + "' already exists")); 239 } 240 241 addedComponent(child); 242 243 } 244 return this; 245 } 246 247 /** 248 * Replaces a child component of this container with another or just adds it in case no child 249 * with the same id existed yet. 250 * 251 * @param children 252 * The child(ren) to be added or replaced 253 * @return this markup container 254 */ 255 public MarkupContainer addOrReplace(final Component... children) 256 { 257 for (Component child : children) 258 { 259 Args.notNull(child, "child"); 260 261 checkHierarchyChange(child); 262 263 if (get(child.getId()) == null) 264 { 265 add(child); 266 } 267 else 268 { 269 replace(child); 270 } 271 } 272 273 return this; 274 } 275 276 /** 277 * This method allows a component to be added by an auto-resolver such as AutoLinkResolver. 278 * While the component is being added, the component's FLAG_AUTO boolean is set. The isAuto() 279 * method of Component returns true if a component or any of its parents has this bit set. When 280 * a component is added via autoAdd(), the logic in Page that normally (a) checks for 281 * modifications during the rendering process, and (b) versions components, is bypassed if 282 * Component.isAuto() returns true. 283 * <p> 284 * The result of all this is that components added with autoAdd() are free from versioning and 285 * can add their own children without the usual exception that would normally be thrown when the 286 * component hierarchy is modified during rendering. 287 * 288 * @param component 289 * The component to add 290 * @param markupStream 291 * Null, if the parent container is able to provide the markup. Else the markup 292 * stream to be used to render the component. 293 * @return True, if component has been added 294 */ 295 public final boolean autoAdd(final Component component, MarkupStream markupStream) 296 { 297 Args.notNull(component, "component"); 298 299 // Replace strategy 300 component.setAuto(true); 301 302 if (markupStream != null) 303 { 304 component.setMarkup(markupStream.getMarkupFragment()); 305 } 306 307 // Add the child to the parent. 308 309 // Arguably child.setParent() can be used as well. It connects the child to the parent and 310 // that's all what most auto-components need. Unfortunately child.onDetach() will not / can 311 // not be invoked, since the parent doesn't known its one of his children. Hence we need to 312 // properly add it. 313 children_remove(component.getId()); 314 add(component); 315 316 return true; 317 } 318 319 /** 320 * @param component 321 * The component to check 322 * @param recurse 323 * True if all descendents should be considered 324 * @return True if the component is contained in this container 325 */ 326 public boolean contains(final Component component, final boolean recurse) 327 { 328 Args.notNull(component, "component"); 329 330 if (recurse) 331 { 332 // Start at component and continue while we're not out of parents 333 for (Component current = component; current != null;) 334 { 335 // Get parent 336 final MarkupContainer parent = current.getParent(); 337 338 // If this container is the parent, then the component is 339 // recursively contained by this container 340 if (parent == this) 341 { 342 // Found it! 343 return true; 344 } 345 346 // Move up the chain to the next parent 347 current = parent; 348 } 349 350 // Failed to find this container in component's ancestry 351 return false; 352 } 353 else 354 { 355 // Is the component contained in this container? 356 return component.getParent() == this; 357 } 358 } 359 360 /** 361 * Get a child component by looking it up with the given path. 362 * <p> 363 * A component path consists of component ids separated by colons, e.g. "b:c" identifies a 364 * component "c" inside container "b" inside this container. 365 * 366 * @param path 367 * path to component 368 * @return The component at the path 369 */ 370 @Override 371 public final Component get(String path) 372 { 373 // Reference to this container 374 if (Strings.isEmpty(path)) 375 { 376 return this; 377 } 378 379 // process parent .. references 380 381 MarkupContainer container = this; 382 383 String id = Strings.firstPathComponent(path, Component.PATH_SEPARATOR); 384 385 while (Component.PARENT_PATH.equals(id)) 386 { 387 container = container.getParent(); 388 if (container == null) 389 { 390 return null; 391 } 392 path = path.length() == id.length() ? "" : path.substring(id.length() + 1); 393 id = Strings.firstPathComponent(path, Component.PATH_SEPARATOR); 394 } 395 396 if (Strings.isEmpty(id)) 397 { 398 return container; 399 } 400 401 // Get child by id 402 Component child = container.children_get(id); 403 404 // Found child? 405 if (child != null) 406 { 407 String path2 = Strings.afterFirstPathComponent(path, Component.PATH_SEPARATOR); 408 409 // Recurse on latter part of path 410 return child.get(path2); 411 } 412 413 return null; 414 } 415 416 /** 417 * Gets a fresh markup stream that contains the (immutable) markup resource for this class. 418 * 419 * @param throwException 420 * If true, throw an exception, if markup could not be found 421 * @return A stream of MarkupElement elements 422 */ 423 public MarkupStream getAssociatedMarkupStream(final boolean throwException) 424 { 425 IMarkupFragment markup = getAssociatedMarkup(); 426 427 // If we found markup for this container 428 if (markup != null) 429 { 430 return new MarkupStream(markup); 431 } 432 433 if (throwException == true) 434 { 435 // throw exception since there is no associated markup 436 throw new MarkupNotFoundException( 437 "Markup of type '" + 438 getMarkupType().getExtension() + 439 "' for component '" + 440 getClass().getName() + 441 "' not found." + 442 " Enable debug messages for org.apache.wicket.util.resource to get a list of all filenames tried.: " + 443 toString()); 444 } 445 446 return null; 447 } 448 449 /** 450 * Gets a fresh markup stream that contains the (immutable) markup resource for this class. 451 * 452 * @return A stream of MarkupElement elements. Null if not found. 453 */ 454 public Markup getAssociatedMarkup() 455 { 456 try 457 { 458 Markup markup = MarkupFactory.get().getMarkup(this, false); 459 460 // If we found markup for this container 461 if ((markup != null) && (markup != Markup.NO_MARKUP)) 462 { 463 return markup; 464 } 465 466 return null; 467 } 468 catch (MarkupException ex) 469 { 470 // re-throw it. The exception contains already all the information 471 // required. 472 throw ex; 473 } 474 catch (MarkupNotFoundException ex) 475 { 476 // re-throw it. The exception contains already all the information 477 // required. 478 throw ex; 479 } 480 catch (WicketRuntimeException ex) 481 { 482 // throw exception since there is no associated markup 483 throw new MarkupNotFoundException( 484 exceptionMessage("Markup of type '" + getMarkupType().getExtension() + 485 "' for component '" + getClass().getName() + "' not found." + 486 " Enable debug messages for org.apache.wicket.util.resource to get a list of all filenames tried"), 487 ex); 488 } 489 } 490 491 /** 492 * Get the markup of the child. 493 * 494 * @see Component#getMarkup() 495 * 496 * @param child 497 * The child component. If null, the container's markup will be returned. See Border, 498 * Panel or Enclosure where getMarkup(null) != getMarkup(). 499 * @return The child's markup 500 */ 501 public IMarkupFragment getMarkup(final Component child) 502 { 503 // Delegate request to attached markup sourcing strategy. 504 return getMarkupSourcingStrategy().getMarkup(this, child); 505 } 506 507 /** 508 * Get the type of associated markup for this component. The markup type for a component is 509 * independent of whether or not the component actually has an associated markup resource file 510 * (which is determined at runtime). 511 * 512 * @return The type of associated markup for this component (for example, "html", "wml" or 513 * "vxml"). If there is no markup type for a component, null may be returned, but this 514 * means that no markup can be loaded for the class. Null is also returned if the 515 * component, or any of its parents, has not been added to a Page. 516 */ 517 public MarkupType getMarkupType() 518 { 519 MarkupContainer parent = getParent(); 520 if (parent != null) 521 { 522 return parent.getMarkupType(); 523 } 524 return null; 525 } 526 527 /** 528 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT. 529 * 530 * Adds a child component to this container. 531 * 532 * @param child 533 * The child 534 * @throws IllegalArgumentException 535 * Thrown if a child with the same id is replaced by the add operation. 536 */ 537 public void internalAdd(final Component child) 538 { 539 if (log.isDebugEnabled()) 540 { 541 log.debug("internalAdd " + child.getId() + " to " + this); 542 } 543 544 // Add to map 545 children_put(child); 546 addedComponent(child); 547 } 548 549 /** 550 * Gives an iterator that allow you to iterate through the children of this markup container in 551 * the order the children were added. The iterator supports additions and removals from the list 552 * of children during iteration. 553 * 554 * @return Iterator that iterates through children in the order they were added 555 */ 556 @Override 557 public Iterator<Component> iterator() 558 { 559 /** 560 * Iterator that knows how to change between a single child, list of children and map of 561 * children. Keeps track when the iterator was last sync'd with the markup container's 562 * tracking of changes to the list of children. 563 */ 564 class MarkupChildIterator implements Iterator<Component> 565 { 566 private int indexInRemovalsSinceLastUpdate; 567 private int expectedModCounter = -1; 568 private Component currentComponent = null; 569 private Iterator<Component> internalIterator = null; 570 571 @Override 572 public boolean hasNext() 573 { 574 refreshInternalIteratorIfNeeded(); 575 return internalIterator.hasNext(); 576 } 577 578 @Override 579 public Component next() 580 { 581 refreshInternalIteratorIfNeeded(); 582 return currentComponent = internalIterator.next(); 583 } 584 585 @Override 586 public void remove() 587 { 588 MarkupContainer.this.remove(currentComponent); 589 refreshInternalIteratorIfNeeded(); 590 } 591 592 private void refreshInternalIteratorIfNeeded() 593 { 594 if (expectedModCounter >= modCounter) { 595 // no new modifications 596 return; 597 } 598 599 if (children == null) 600 { 601 internalIterator = Collections.emptyIterator(); 602 } 603 else if (children instanceof Component) 604 { 605 internalIterator = Collections.singleton((Component)children).iterator(); 606 } 607 else if (children instanceof List) 608 { 609 List<Component> childrenList = children(); 610 internalIterator = childrenList.iterator(); 611 } 612 else 613 { 614 Map<String, Component> childrenMap = children(); 615 internalIterator = childrenMap.values().iterator(); 616 } 617 618 // since we now have a new iterator, we need to set it to the last known position 619 currentComponent = findLastExistingChildAlreadyReturned(currentComponent); 620 expectedModCounter = modCounter; 621 622 if (currentComponent != null) 623 { 624 // move the new internal iterator to the place of the last processed component 625 while (internalIterator.hasNext() && 626 internalIterator.next() != currentComponent) 627 // noop 628 ; 629 } 630 } 631 632 private Component findLastExistingChildAlreadyReturned(Component current) 633 { 634 if (current == null) { 635 indexInRemovalsSinceLastUpdate = 0; 636 } else { 637 LinkedList<RemovedChild> removals = removals_get(); 638 if (removals != null) { 639 check_removed: 640 while (current != null) 641 { 642 for (int i = indexInRemovalsSinceLastUpdate; i < removals.size(); i++) 643 { 644 RemovedChild removal = removals.get(i); 645 if (removal.removedChild == current || 646 removal.removedChild == null) 647 { 648 current = removal.previousSibling; 649 650 // current was removed, use its sibling instead 651 continue check_removed; 652 } 653 } 654 655 // current wasn't removed, keep it 656 break; 657 } 658 659 indexInRemovalsSinceLastUpdate = removals.size(); 660 } 661 } 662 663 return current; 664 } 665 }; 666 return new MarkupChildIterator(); 667 } 668 669 /** 670 * Creates an iterator that iterates over children in the order specified by comparator. This 671 * works on a copy of the children list. 672 * 673 * @param comparator 674 * The comparator 675 * @return Iterator that iterates over children in the order specified by comparator 676 */ 677 public final Iterator<Component> iterator(Comparator<Component> comparator) 678 { 679 final List<Component> sorted = copyChildren(); 680 Collections.sort(sorted, comparator); 681 return sorted.iterator(); 682 } 683 684 /** 685 * Removes a component from the children identified by the {@code component.getId()} 686 * 687 * @param component 688 * Component to remove from this container 689 * @return {@code this} for chaining 690 */ 691 public MarkupContainer remove(final Component component) 692 { 693 checkHierarchyChange(component); 694 695 Args.notNull(component, "component"); 696 697 children_remove(component.getId()); 698 removedComponent(component); 699 700 return this; 701 } 702 703 /** 704 * Removes the given component 705 * 706 * @param id 707 * The id of the component to remove 708 * @return {@code this} for chaining 709 */ 710 public MarkupContainer remove(final String id) 711 { 712 Args.notNull(id, "id"); 713 714 final Component component = get(id); 715 if (component != null) 716 { 717 remove(component); 718 } 719 else 720 { 721 throw new WicketRuntimeException("Unable to find a component with id '" + id + 722 "' to remove"); 723 } 724 725 return this; 726 } 727 728 /** 729 * Removes all children from this container. 730 * <p> 731 * Note: implementation does not call {@link MarkupContainer#remove(Component) } for each 732 * component. 733 * 734 * @return {@code this} for method chaining 735 */ 736 public MarkupContainer removeAll() 737 { 738 if (children != null) 739 { 740 addStateChange(); 741 742 for (Component child : this) 743 { 744 // Do not call remove() because the state change would then be 745 // recorded twice. 746 child.internalOnRemove(); 747 child.detach(); 748 child.setParent(null); 749 } 750 751 children = null; 752 removals_add(null, null); 753 } 754 755 return this; 756 } 757 758 /** 759 * Renders the entire associated markup for a container such as a Border or Panel. Any leading 760 * or trailing raw markup in the associated markup is skipped. 761 * 762 * @param openTagName 763 * the tag to render the associated markup for 764 * @param exceptionMessage 765 * ignored 766 * @deprecated 767 * Use {@link #renderAssociatedMarkup(String)}. The {@code exceptionMessage} 768 * parameter is ignored. 769 */ 770 @Deprecated(since = "9.10.0", forRemoval = true) 771 public final void renderAssociatedMarkup(final String openTagName, final String exceptionMessage) 772 { 773 renderAssociatedMarkup(openTagName); 774 } 775 776 /** 777 * Renders the entire associated markup for a container such as a Border or Panel. Any leading 778 * or trailing raw markup in the associated markup is skipped. 779 * 780 * @param openTagName 781 * the tag to render the associated markup for 782 */ 783 public final void renderAssociatedMarkup(final String openTagName) 784 { 785 // Get associated markup file for the Border or Panel component 786 final MarkupStream associatedMarkupStream = new MarkupStream(getMarkup(null)); 787 788 // Get open tag in associated markup of border component 789 MarkupElement elem = associatedMarkupStream.get(); 790 if ((elem instanceof ComponentTag) == false) 791 { 792 associatedMarkupStream.throwMarkupException("Expected the open tag. Markup for a " 793 + openTagName + " component must begin a tag like '<wicket:" + openTagName + ">'"); 794 } 795 796 // Check for required open tag name 797 ComponentTag associatedMarkupOpenTag = (ComponentTag)elem; 798 if (!(associatedMarkupOpenTag.isOpen() && (associatedMarkupOpenTag instanceof WicketTag))) 799 { 800 associatedMarkupStream.throwMarkupException("Markup for a " + openTagName 801 + " component must begin a tag like '<wicket:" + openTagName + ">'"); 802 } 803 804 try 805 { 806 setIgnoreAttributeModifier(true); 807 renderComponentTag(associatedMarkupOpenTag); 808 associatedMarkupStream.next(); 809 810 String className = null; 811 812 final boolean outputClassName = getApplication().getDebugSettings() 813 .isOutputMarkupContainerClassName(); 814 if (outputClassName) 815 { 816 className = Classes.name(getClass()); 817 getResponse().write("<!-- MARKUP FOR "); 818 getResponse().write(className); 819 getResponse().write(" BEGIN -->"); 820 } 821 822 renderComponentTagBody(associatedMarkupStream, associatedMarkupOpenTag); 823 824 if (outputClassName) 825 { 826 getResponse().write("<!-- MARKUP FOR "); 827 getResponse().write(className); 828 getResponse().write(" END -->"); 829 } 830 831 renderClosingComponentTag(associatedMarkupStream, associatedMarkupOpenTag, false); 832 } 833 finally 834 { 835 setIgnoreAttributeModifier(false); 836 } 837 } 838 839 /** 840 * Replaces a child component of this container with another 841 * 842 * @param child 843 * The child 844 * @throws IllegalArgumentException 845 * Thrown if there was no child with the same id. 846 * @return This 847 */ 848 public MarkupContainer replace(final Component child) 849 { 850 Args.notNull(child, "child"); 851 852 checkHierarchyChange(child); 853 854 if (log.isDebugEnabled()) 855 { 856 log.debug("Replacing " + child.getId() + " in " + this); 857 } 858 859 if (child.getParent() != this) 860 { 861 final Component replaced = children_put(child); 862 863 // Look up to make sure it was already in the map 864 if (replaced == null) 865 { 866 throw new WicketRuntimeException( 867 exceptionMessage("Cannot replace a component which has not been added: id='" + 868 child.getId() + "', component=" + child)); 869 } 870 871 // first remove the component. 872 removedComponent(replaced); 873 874 // The generated markup id remains the same 875 child.setMarkupId(replaced); 876 877 // then add the other one. 878 addedComponent(child); 879 } 880 881 return this; 882 } 883 884 @Override 885 public MarkupContainer setDefaultModel(final IModel<?> model) 886 { 887 final IModel<?> previous = getModelImpl(); 888 super.setDefaultModel(model); 889 if (previous instanceof IComponentInheritedModel) 890 { 891 visitChildren(new IVisitor<Component, Void>() 892 { 893 @Override 894 public void component(final Component component, final IVisit<Void> visit) 895 { 896 IModel<?> compModel = component.getDefaultModel(); 897 if (compModel instanceof IWrapModel) 898 { 899 compModel = ((IWrapModel<?>)compModel).getWrappedModel(); 900 } 901 if (compModel == previous) 902 { 903 component.setDefaultModel(null); 904 } 905 else if (compModel == model) 906 { 907 component.modelChanged(); 908 } 909 } 910 911 }); 912 } 913 return this; 914 } 915 916 /** 917 * Get the number of children in this container. 918 * 919 * @return Number of children in this container 920 */ 921 public int size() 922 { 923 return children_size(); 924 } 925 926 @Override 927 public String toString() 928 { 929 return toString(false); 930 } 931 932 /** 933 * @param detailed 934 * True if a detailed string is desired 935 * @return String representation of this container 936 */ 937 @Override 938 public String toString(final boolean detailed) 939 { 940 final StringBuilder buffer = new StringBuilder(); 941 buffer.append('[').append(Classes.simpleName(this.getClass())).append(' '); 942 buffer.append(super.toString(detailed)); 943 if (detailed && children_size() != 0) 944 { 945 946 buffer.append(", children = "); 947 948 // Loop through child components 949 boolean first = true; 950 for (Component child : this) 951 { 952 if (first) 953 { 954 buffer.append(' '); 955 first = false; 956 } 957 buffer.append(child.toString()); 958 } 959 960 } 961 buffer.append(']'); 962 return buffer.toString(); 963 } 964 965 /** 966 * Traverses all child components of the given class in this container, calling the visitor's 967 * visit method at each one. 968 * 969 * Make sure that if you give a type S that the clazz parameter will only resolve to those 970 * types. Else a class cast exception will occur. 971 * 972 * @param <S> 973 * The type that goes into the Visitor.component() method. 974 * @param <R> 975 * @param clazz 976 * The class of child to visit 977 * @param visitor 978 * The visitor to call back to 979 * @return The return value from a visitor which halted the traversal, or null if the entire 980 * traversal occurred 981 */ 982 public final <S extends Component, R> R visitChildren(final Class<?> clazz, 983 final IVisitor<S, R> visitor) 984 { 985 return Visits.visitChildren(this, visitor, new ClassVisitFilter(clazz)); 986 } 987 988 /** 989 * Traverses all child components in this container, calling the visitor's visit method at each 990 * one. 991 * 992 * @param <R> 993 * @param visitor 994 * The visitor to call back to 995 * @return The return value from a visitor which halted the traversal, or null if the entire 996 * traversal occurred 997 */ 998 public final <R> R visitChildren(final IVisitor<Component, R> visitor) 999 { 1000 return Visits.visitChildren(this, visitor); 1001 } 1002 1003 /** 1004 * @param child 1005 * Component being added 1006 */ 1007 private void addedComponent(final Component child) 1008 { 1009 // Check for degenerate case 1010 Args.notNull(child, "child"); 1011 1012 MarkupContainer parent = child.getParent(); 1013 if (parent != null && parent != this) 1014 { 1015 parent.remove(child); 1016 } 1017 1018 // Set child's parent 1019 child.setParent(this); 1020 1021 final DebugSettings debugSettings = Application.get().getDebugSettings(); 1022 if (debugSettings.isLinePreciseReportingOnAddComponentEnabled() 1023 && debugSettings.getComponentUseCheck()) 1024 { 1025 child.setMetaData(ADDED_AT_KEY, 1026 ComponentStrings.toString(child, new MarkupException("added"))); 1027 } 1028 1029 Page page = findPage(); 1030 1031 if (page != null) 1032 { 1033 // tell the page a component has been added first, to allow it to initialize 1034 page.componentAdded(child); 1035 1036 // initialize the component 1037 if (page.isInitialized()) 1038 { 1039 child.internalInitialize(); 1040 } 1041 } 1042 1043 // if the PREPARED_FOR_RENDER flag is set, we have already called 1044 // beforeRender on this component's children. So we need to initialize the newly added one 1045 if (isPreparedForRender()) 1046 { 1047 child.beforeRender(); 1048 } 1049 } 1050 1051 /** 1052 * THIS METHOD IS NOT PART OF THE PUBLIC API, DO NOT CALL IT 1053 * 1054 * Overrides {@link Component#internalInitialize()} to call {@link Component#fireInitialize()} 1055 * for itself and for all its children. 1056 * 1057 * @see org.apache.wicket.Component#fireInitialize() 1058 */ 1059 @Override 1060 public final void internalInitialize() 1061 { 1062 super.fireInitialize(); 1063 visitChildren(new IVisitor<Component, Void>() 1064 { 1065 @Override 1066 public void component(final Component component, final IVisit<Void> visit) 1067 { 1068 component.fireInitialize(); 1069 } 1070 }); 1071 } 1072 1073 /* 1074 * === Internal management for keeping track of child components === 1075 * 1076 * A markup container is the base component for containing child objects. It is one of the most 1077 * heavily used components so we should keep it's (memory and CPU) footprint small. 1078 * 1079 * The goals for the internal management of the list of child components are: 1080 * 1081 * - as low big-O complexity as possible, preferrably O(1) 1082 * 1083 * - as low memory consumption as possible (don't use more memory than strictly necessary) 1084 * 1085 * - ensure that iterating through the (list of) children be as consistent as possible 1086 * 1087 * - retain the order of addition in the iteration 1088 * 1089 * These goals are attained by storing the children in a single field that is implemented using: 1090 * 1091 * - a component when there's only one child 1092 * 1093 * - a list of components when there are more than 1 children 1094 * 1095 * - a map of components when the number of children makes looking up children by id more costly 1096 * than an indexed search (see MAPIFY_THRESHOLD) 1097 * 1098 * To ensure that iterating through the list of children keeps working even when children are 1099 * added, replaced and removed without throwing a ConcurrentModificationException a special 1100 * iterator is used. The markup container tracks removals from and additions to the children 1101 * during the request, enabling the iterator to skip over those items and adjust to changing 1102 * internal data structures. 1103 */ 1104 1105 /** 1106 * A type washing accessor method for getting the children without having to cast the field 1107 * explicitly. 1108 * 1109 * @return the children as a T 1110 */ 1111 @SuppressWarnings("unchecked") 1112 private <T> T children() 1113 { 1114 return (T)children; 1115 } 1116 1117 /** 1118 * Gets the child with the given {@code childId} 1119 * 1120 * @param childId 1121 * the component identifier 1122 * @return The child component or {@code null} when no child with the given identifier exists 1123 */ 1124 private Component children_get(final String childId) 1125 { 1126 if (children == null) 1127 { 1128 return null; 1129 } 1130 if (children instanceof Component) 1131 { 1132 Component child = children(); 1133 return child.getId().equals(childId) ? child : null; 1134 } 1135 if (children instanceof List) 1136 { 1137 List<Component> kids = children(); 1138 for (Component child : kids) 1139 { 1140 if (child.getId().equals(childId)) 1141 { 1142 return child; 1143 } 1144 } 1145 return null; 1146 } 1147 Map<String, Component> kids = children(); 1148 return kids.get(childId); 1149 } 1150 1151 /** 1152 * Removes the child component identified by {@code childId} from the list of children. 1153 * 1154 * Will change the internal list or map to a single component when the number of children hits 1155 * 1, but not change the internal map to a list when the threshold is reached (the memory was 1156 * already claimed, so there's little to be gained other than wasting CPU cycles for the 1157 * conversion). 1158 * 1159 * @param childId 1160 * the id of the child component to remove 1161 */ 1162 private void children_remove(String childId) 1163 { 1164 if (children instanceof Component) 1165 { 1166 Component oldChild = children(); 1167 if (oldChild.getId().equals(childId)) 1168 { 1169 children = null; 1170 removals_add(oldChild, null); 1171 } 1172 } 1173 else if (children instanceof List) 1174 { 1175 List<Component> childrenList = children(); 1176 Iterator<Component> it = childrenList.iterator(); 1177 Component prevChild = null; 1178 while (it.hasNext()) 1179 { 1180 Component child = it.next(); 1181 if (child.getId().equals(childId)) 1182 { 1183 it.remove(); 1184 removals_add(child, prevChild); 1185 if (childrenList.size() == 1) 1186 { 1187 children = childrenList.get(0); 1188 } 1189 return; 1190 } 1191 prevChild = child; 1192 } 1193 } 1194 else if (children instanceof LinkedMap) 1195 { 1196 LinkedMap<String, Component> childrenMap = children(); 1197 if (childrenMap.containsKey(childId)) 1198 { 1199 String prevSiblingId = childrenMap.previousKey(childId); 1200 Component oldChild = childrenMap.remove(childId); 1201 removals_add(oldChild, childrenMap.get(prevSiblingId)); 1202 if (childrenMap.size() == 1) 1203 { 1204 children = childrenMap.values().iterator().next(); 1205 } 1206 } 1207 } 1208 } 1209 1210 /** 1211 * Gets the number of child components of this markup container. 1212 * 1213 * @return The number of children 1214 */ 1215 private int children_size() 1216 { 1217 if (children == null) 1218 { 1219 return 0; 1220 } 1221 if (children instanceof Component) 1222 { 1223 return 1; 1224 } 1225 if (children instanceof List) 1226 { 1227 List<?> kids = children(); 1228 return kids.size(); 1229 } 1230 return ((Map<?, ?>)children).size(); 1231 } 1232 1233 /** 1234 * Puts the {@code child} component into the list of children of this markup container. If a 1235 * component existed with the same {@code child.getId()} it is replaced and the old component is 1236 * returned. 1237 * 1238 * When a component is replaced, the internal structure of the children is not modified, so we 1239 * don't have to update the internal {@link #modCounter} in those circumstances. When a 1240 * component is added, we do have to increase the {@link #modCounter} to notify iterators of 1241 * this change. 1242 * 1243 * @param child 1244 * The child 1245 * @return Any component that was replaced 1246 */ 1247 private Component children_put(final Component child) 1248 { 1249 if (children == null) 1250 { 1251 children = child; 1252 1253 // it is an addtion, so we need to notify the iterators of this change. 1254 modCounter++; 1255 1256 return null; 1257 } 1258 1259 if (children instanceof Component) 1260 { 1261 /* first see if the child replaces the existing child */ 1262 Component oldChild = children(); 1263 if (oldChild.getId().equals(child.getId())) 1264 { 1265 children = child; 1266 return oldChild; 1267 } 1268 else 1269 { 1270 /* 1271 * the put doesn't replace the existing child, so we need to increase the children 1272 * storage to a list and add the existing and new child to it 1273 */ 1274 Component originalChild = children(); 1275 List<Component> newChildren = new ArrayList<>(INITIAL_CHILD_LIST_CAPACITY); 1276 newChildren.add(originalChild); 1277 newChildren.add(child); 1278 children = newChildren; 1279 1280 // it is an addtion, so we need to notify the iterators of this change. 1281 modCounter++; 1282 return null; 1283 } 1284 } 1285 1286 if (children instanceof List) 1287 { 1288 List<Component> childrenList = children(); 1289 1290 // first see if the child replaces an existing child 1291 for (int i = 0; i < childrenList.size(); i++) 1292 { 1293 Component curChild = childrenList.get(i); 1294 if (curChild.getId().equals(child.getId())) 1295 { 1296 return childrenList.set(i, child); 1297 } 1298 } 1299 1300 // it is an addtion, so we need to notify the iterators of this change. 1301 modCounter++; 1302 1303 /* 1304 * If it still fits in the allotted number of items of a List, just add it, otherwise 1305 * change the internal data structure to a Map for speedier lookups. 1306 */ 1307 if (childrenList.size() < MAPIFY_THRESHOLD) 1308 { 1309 childrenList.add(child); 1310 } 1311 else 1312 { 1313 Map<String, Component> newChildren = new LinkedMap<>(MAPIFY_THRESHOLD * 2); 1314 for (Component curChild : childrenList) 1315 { 1316 newChildren.put(curChild.getId(), curChild); 1317 } 1318 newChildren.put(child.getId(), child); 1319 children = newChildren; 1320 } 1321 return null; 1322 } 1323 1324 Map<String, Component> childrenMap = children(); 1325 Component oldChild = childrenMap.put(child.getId(), child); 1326 1327 if (oldChild == null) 1328 { 1329 // it is an addtion, so we need to notify the iterators of this change. 1330 modCounter++; 1331 } 1332 return oldChild; 1333 } 1334 1335 /** 1336 * Retrieves the during the request removed children. These are stored in the metadata and 1337 * cleared at the end of the request {@link #onDetach()} 1338 * 1339 * @return the list of removed children, may be {@code null} 1340 */ 1341 private LinkedList<RemovedChild> removals_get() 1342 { 1343 return getRequestFlag(RFLAG_CONTAINER_HAS_REMOVALS) ? getMetaData(REMOVALS_KEY) : null; 1344 } 1345 1346 /** 1347 * Sets the during the request removed children. These are stored in the metadata and cleared at 1348 * the end of the request, see {@link #onDetach()}. 1349 * 1350 * @param removals 1351 * the new list of removals 1352 */ 1353 private void removals_set(LinkedList<RemovedChild> removals) 1354 { 1355 setRequestFlag(RFLAG_CONTAINER_HAS_REMOVALS, removals != null); 1356 setMetaData(REMOVALS_KEY, removals); 1357 } 1358 1359 /** 1360 * Removes the list of removals from the metadata. 1361 */ 1362 private void removals_clear() 1363 { 1364 if (getRequestFlag(RFLAG_CONTAINER_HAS_REMOVALS)) 1365 { 1366 removals_set(null); 1367 } 1368 } 1369 1370 /** 1371 * Adds the {@code removedChild} to the list of removals and links it to the 1372 * {@code previousSibling} 1373 * 1374 * @param removedChild 1375 * the child that was removed 1376 * @param prevSibling 1377 * the child that was the previous sibling of the removed child 1378 */ 1379 private void removals_add(Component removedChild, Component prevSibling) 1380 { 1381 modCounter++; 1382 1383 LinkedList<RemovedChild> removals = removals_get(); 1384 if (removals == null) 1385 { 1386 removals = new LinkedList<>(); 1387 removals_set(removals); 1388 } 1389 removals.add(new RemovedChild(removedChild, prevSibling)); 1390 } 1391 1392 /** 1393 * @param component 1394 * Component being removed 1395 */ 1396 private void removedComponent(final Component component) 1397 { 1398 // Notify Page that component is being removed 1399 final Page page = component.findPage(); 1400 if (page != null) 1401 { 1402 page.componentRemoved(component); 1403 } 1404 1405 component.detach(); 1406 1407 component.internalOnRemove(); 1408 1409 // Component is removed 1410 component.setParent(null); 1411 } 1412 1413 /** 1414 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE OR OVERWRITE IT. 1415 * 1416 * Renders the next element of markup in the given markup stream. 1417 * 1418 * @param markupStream 1419 * The markup stream 1420 * @return true, if element was rendered as RawMarkup 1421 */ 1422 protected boolean renderNext(final MarkupStream markupStream) 1423 { 1424 // Get the current markup element 1425 final MarkupElement element = markupStream.get(); 1426 1427 // If it's a tag like <wicket..> or <span wicket:id="..." > 1428 if ((element instanceof ComponentTag) && !markupStream.atCloseTag()) 1429 { 1430 // Get element as tag 1431 final ComponentTag tag = (ComponentTag)element; 1432 1433 if (tag instanceof WicketTag && ((WicketTag)tag).isFragmentTag()){ 1434 return false; 1435 } 1436 1437 // Get component id 1438 final String id = tag.getId(); 1439 1440 // Get the component for the id from the given container 1441 Component component = get(id); 1442 1443 if (component == null) 1444 { 1445 component = ComponentResolvers.resolve(this, markupStream, tag, null); 1446 if ((component != null) && (component.getParent() == null)) 1447 { 1448 autoAdd(component, markupStream); 1449 } 1450 else if (component != null) 1451 { 1452 component.setMarkup(markupStream.getMarkupFragment()); 1453 } 1454 } 1455 1456 // Failed to find it? 1457 if (component != null) 1458 { 1459 component.render(); 1460 } 1461 else if (tag.getFlag(ComponentTag.RENDER_RAW)) 1462 { 1463 // No component found, but "render as raw markup" flag found 1464 if (canRenderRawTag(tag)) 1465 { 1466 getResponse().write(element.toCharSequence()); 1467 } 1468 return true; 1469 } 1470 else 1471 { 1472 throwException(markupStream, tag); 1473 } 1474 } 1475 else 1476 { 1477 // Render as raw markup 1478 if (canRenderRawTag(element)) 1479 { 1480 getResponse().write(element.toCharSequence()); 1481 } 1482 return true; 1483 } 1484 1485 return false; 1486 } 1487 1488 /** 1489 * Says if the given tag can be handled as a raw markup. 1490 * 1491 * @param tag 1492 * the current tag. 1493 * @return true if the tag can be handled as raw markup, false otherwise. 1494 */ 1495 private boolean canRenderRawTag(MarkupElement tag) 1496 { 1497 boolean isWicketTag = tag instanceof WicketTag; 1498 1499 boolean stripTag = isWicketTag ? Application.get().getMarkupSettings().getStripWicketTags() : false; 1500 1501 return !stripTag; 1502 } 1503 1504 /** 1505 * Throws a {@code org.apache.wicket.markup.MarkupException} when the 1506 * component markup is not consistent. 1507 * 1508 * @param markupStream 1509 * the source stream for the component markup. 1510 * @param tag 1511 * the tag that can not be handled. 1512 */ 1513 private void throwException(final MarkupStream markupStream, final ComponentTag tag) 1514 { 1515 final String id = tag.getId(); 1516 1517 if (tag instanceof WicketTag) 1518 { 1519 if (((WicketTag)tag).isChildTag()) 1520 { 1521 markupStream.throwMarkupException("Found " + tag.toString() + 1522 " but no <wicket:extend>. Container: " + toString()); 1523 } 1524 else 1525 { 1526 markupStream.throwMarkupException("Failed to handle: " + 1527 tag.toString() + 1528 ". It might be that no resolver has been registered to handle this special tag. " + 1529 " But it also could be that you declared wicket:id=" + id + 1530 " in your markup, but that you either did not add the " + 1531 "component to your page at all, or that the hierarchy does not match. " + 1532 "Container: " + toString()); 1533 } 1534 } 1535 1536 List<String> names = findSimilarComponents(id); 1537 1538 // No one was able to handle the component id 1539 StringBuilder msg = new StringBuilder(500); 1540 msg.append("Unable to find component with id '"); 1541 msg.append(id); 1542 msg.append("' in "); 1543 msg.append(this.toString()); 1544 msg.append("\n\tExpected: '"); 1545 msg.append(getPageRelativePath()); 1546 msg.append(PATH_SEPARATOR); 1547 msg.append(id); 1548 msg.append("'.\n\tFound with similar names: '"); 1549 msg.append(Strings.join("', ", names)); 1550 msg.append('\''); 1551 1552 log.error(msg.toString()); 1553 markupStream.throwMarkupException(msg.toString()); 1554 } 1555 1556 private List<String> findSimilarComponents(final String id) 1557 { 1558 final List<String> names = Generics.newArrayList(); 1559 1560 Page page = findPage(); 1561 if (page != null) 1562 { 1563 page.visitChildren(new IVisitor<Component, Void>() 1564 { 1565 @Override 1566 public void component(Component component, IVisit<Void> visit) 1567 { 1568 if (Strings.getLevenshteinDistance(id.toLowerCase(Locale.ROOT), component.getId() 1569 .toLowerCase(Locale.ROOT)) < 3) 1570 { 1571 names.add(component.getPageRelativePath()); 1572 } 1573 } 1574 }); 1575 } 1576 1577 return names; 1578 } 1579 1580 /** 1581 * Handle the container's body. If your override of this method does not advance the markup 1582 * stream to the close tag for the openTag, a runtime exception will be thrown by the framework. 1583 * 1584 * @param markupStream 1585 * The markup stream 1586 * @param openTag 1587 * The open tag for the body 1588 */ 1589 @Override 1590 public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) 1591 { 1592 renderComponentTagBody(markupStream, openTag); 1593 } 1594 1595 @Override 1596 protected void onRender() 1597 { 1598 internalRenderComponent(); 1599 } 1600 1601 /** 1602 * Renders markup for the body of a ComponentTag from the current position in the given markup 1603 * stream. If the open tag passed in does not require a close tag, nothing happens. Markup is 1604 * rendered until the closing tag for openTag is reached. 1605 * 1606 * @param markupStream 1607 * The markup stream 1608 * @param openTag 1609 * The open tag 1610 */ 1611 private void renderComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) 1612 { 1613 if ((markupStream != null) && (markupStream.getCurrentIndex() > 0)) 1614 { 1615 // If the original tag has been changed from open-close to open-body-close, than we are 1616 // done. Other components, e.g. BorderBody, rely on this method being called. 1617 ComponentTag origOpenTag = (ComponentTag)markupStream.get(markupStream.getCurrentIndex() - 1); 1618 if (origOpenTag.isOpenClose()) 1619 { 1620 return; 1621 } 1622 } 1623 1624 // If the open tag requires a close tag 1625 boolean render = openTag.requiresCloseTag(); 1626 if (render == false) 1627 { 1628 // Tags like <p> do not require a close tag, but they may have. 1629 render = !openTag.hasNoCloseTag(); 1630 } 1631 1632 if (render) 1633 { 1634 renderAll(markupStream, openTag); 1635 } 1636 } 1637 1638 /** 1639 * Loop through the markup in this container 1640 * 1641 * @param markupStream 1642 * @param openTag 1643 */ 1644 protected final void renderAll(final MarkupStream markupStream, final ComponentTag openTag) 1645 { 1646 while (markupStream.isCurrentIndexInsideTheStream()) 1647 { 1648 // In case of Page we need to render the whole file. For all other components just what 1649 // is in between the open and the close tag. 1650 if ((openTag != null) && markupStream.get().closes(openTag)) 1651 { 1652 break; 1653 } 1654 1655 // Remember where we are 1656 final int index = markupStream.getCurrentIndex(); 1657 1658 // Render the markup element 1659 boolean rawMarkup = renderNext(markupStream); 1660 1661 // Go back to where we were and move the markup stream forward to whatever the next 1662 // element is. 1663 markupStream.setCurrentIndex(index); 1664 1665 if (rawMarkup) 1666 { 1667 markupStream.next(); 1668 } 1669 else if (!markupStream.getTag().isClose()) 1670 { 1671 markupStream.skipComponent(); 1672 } 1673 else 1674 { 1675 throw new WicketRuntimeException("Ups. This should never happen. " + 1676 markupStream.toString()); 1677 } 1678 } 1679 } 1680 1681 @Override 1682 void removeChildren() 1683 { 1684 super.removeChildren(); 1685 1686 for (Component component : this) 1687 { 1688 component.internalOnRemove(); 1689 } 1690 } 1691 1692 @Override 1693 void detachChildren() 1694 { 1695 super.detachChildren(); 1696 1697 for (Component component : this) 1698 { 1699 component.detach(); 1700 } 1701 } 1702 1703 /** 1704 * 1705 * @see org.apache.wicket.Component#internalMarkRendering(boolean) 1706 */ 1707 @Override 1708 void internalMarkRendering(boolean setRenderingFlag) 1709 { 1710 super.internalMarkRendering(setRenderingFlag); 1711 1712 for (Component child : this) 1713 { 1714 child.internalMarkRendering(setRenderingFlag); 1715 } 1716 } 1717 1718 /** 1719 * @return a copy of the children array. 1720 */ 1721 @SuppressWarnings("unchecked") 1722 private List<Component> copyChildren() 1723 { 1724 if (children == null) 1725 { 1726 return Collections.emptyList(); 1727 } 1728 else if (children instanceof Component) 1729 { 1730 return Collections.singletonList((Component)children); 1731 } 1732 else if (children instanceof List) 1733 { 1734 return new ArrayList<>((List<Component>)children); 1735 } 1736 else 1737 { 1738 return new ArrayList<>(((Map<String, Component>)children).values()); 1739 } 1740 } 1741 1742 @Override 1743 void onBeforeRenderChildren() 1744 { 1745 super.onBeforeRenderChildren(); 1746 1747 try 1748 { 1749 // Loop through child components 1750 for (final Component child : this) 1751 { 1752 // Get next child 1753 // Call begin request on the child 1754 // We need to check whether the child's wasn't removed from the 1755 // component in the meanwhile (e.g. from another's child 1756 // onBeforeRender) 1757 if (child.getParent() == this) 1758 { 1759 child.beforeRender(); 1760 } 1761 } 1762 } 1763 catch (RuntimeException ex) 1764 { 1765 if (ex instanceof WicketRuntimeException) 1766 { 1767 throw ex; 1768 } 1769 else 1770 { 1771 throw new WicketRuntimeException("Error attaching this container for rendering: " + 1772 this, ex); 1773 } 1774 } 1775 } 1776 1777 @Override 1778 void onEnabledStateChanged() 1779 { 1780 super.onEnabledStateChanged(); 1781 visitChildren(new IVisitor<Component, Void>() 1782 { 1783 @Override 1784 public void component(Component component, IVisit<Void> visit) 1785 { 1786 component.clearEnabledInHierarchyCache(); 1787 } 1788 }); 1789 } 1790 1791 @Override 1792 void onVisibleStateChanged() 1793 { 1794 super.onVisibleStateChanged(); 1795 visitChildren(new IVisitor<Component, Void>() 1796 { 1797 @Override 1798 public void component(Component component, IVisit<Void> visit) 1799 { 1800 component.clearVisibleInHierarchyCache(); 1801 } 1802 }); 1803 } 1804 1805 @Override 1806 protected void onDetach() 1807 { 1808 super.onDetach(); 1809 1810 modCounter++; 1811 removals_clear(); 1812 1813 if (queue != null && !queue.isEmpty() && hasBeenRendered()) 1814 { 1815 throw new WicketRuntimeException( 1816 String.format("Detach called on component with id '%s' while it had a non-empty queue: %s", 1817 getId(), queue)); 1818 } 1819 } 1820 1821 private transient ComponentQueue queue; 1822 1823 /** 1824 * Queues one or more components to be dequeued later. The advantage of this method over the 1825 * {@link #add(Component...)} method is that the component does not have to be added to its 1826 * direct parent, only to a parent upstream; it will be dequeued into the correct parent using 1827 * the hierarchy defined in the markup. This allows the component hierarchy to be maintained only 1828 * in markup instead of in markup and in java code; affording designers and developers more 1829 * freedom when moving components in markup. 1830 * 1831 * @param components 1832 * the components to queue 1833 * @return {@code this} for method chaining 1834 */ 1835 public MarkupContainer queue(Component... components) 1836 { 1837 if (queue == null) 1838 { 1839 queue = new ComponentQueue(); 1840 } 1841 queue.add(components); 1842 1843 Page page = findPage(); 1844 1845 if (page != null) 1846 { 1847 dequeue(); 1848 } 1849 1850 return this; 1851 } 1852 1853 /** 1854 * @see IQueueRegion#dequeue() 1855 */ 1856 public void dequeue() 1857 { 1858 if (this instanceof IQueueRegion) 1859 { 1860 DequeueContext dequeue = newDequeueContext(); 1861 dequeuePreamble(dequeue); 1862 } 1863 else 1864 { 1865 MarkupContainer queueRegion = (MarkupContainer)findParent(IQueueRegion.class); 1866 1867 if (queueRegion == null) 1868 { 1869 return; 1870 } 1871 1872 MarkupContainer anchestor = this; 1873 boolean hasQueuedChildren = !isQueueEmpty(); 1874 1875 while (!hasQueuedChildren && anchestor != queueRegion) 1876 { 1877 anchestor = anchestor.getParent(); 1878 hasQueuedChildren = !anchestor.isQueueEmpty(); 1879 } 1880 1881 if (hasQueuedChildren && !queueRegion.getRequestFlag(RFLAG_CONTAINER_DEQUEING)) 1882 { 1883 queueRegion.dequeue(); 1884 } 1885 } 1886 } 1887 1888 @Override 1889 protected void onInitialize() 1890 { 1891 super.onInitialize(); 1892 dequeue(); 1893 } 1894 /** 1895 * @return {@code true} when one or more components are queued 1896 */ 1897 private boolean isQueueEmpty() 1898 { 1899 return queue == null || queue.isEmpty(); 1900 } 1901 1902 /** 1903 * @return {@code true} when this markup container is a queue region 1904 */ 1905 private boolean isQueueRegion() 1906 { 1907 return IQueueRegion.class.isInstance(this); 1908 } 1909 1910 /** 1911 * Run preliminary operations before running {@link #dequeue(DequeueContext)}. More in detail it 1912 * throws an exception if the container is already dequeuing, and it also takes care of setting 1913 * flag {@code RFLAG_CONTAINER_DEQUEING} to true before running {@link #dequeue(DequeueContext)} 1914 * and setting it back to false after dequeuing is completed. 1915 * 1916 * @param dequeue 1917 * the dequeue context to use 1918 */ 1919 protected void dequeuePreamble(DequeueContext dequeue) 1920 { 1921 if (getRequestFlag(RFLAG_CONTAINER_DEQUEING)) 1922 { 1923 throw new IllegalStateException("This container is already dequeing: " + this); 1924 } 1925 1926 setRequestFlag(RFLAG_CONTAINER_DEQUEING, true); 1927 try 1928 { 1929 if (dequeue == null) 1930 { 1931 return; 1932 } 1933 1934 if (dequeue.peekTag() != null) 1935 { 1936 dequeue(dequeue); 1937 } 1938 } 1939 finally 1940 { 1941 setRequestFlag(RFLAG_CONTAINER_DEQUEING, false); 1942 } 1943 } 1944 1945 /** 1946 * Dequeues components. The default implementation iterates direct children of this container 1947 * found in its markup and tries to find matching 1948 * components in queues filled by a call to {@link #queue(Component...)}. It then delegates the 1949 * dequeueing to these children. 1950 * 1951 * 1952 * Certain components that implement custom markup behaviors (such as repeaters and borders) 1953 * override this method to bring dequeueing in line with their custom markup handling. 1954 * 1955 * @param dequeue 1956 * the dequeue context to use 1957 */ 1958 public void dequeue(DequeueContext dequeue) 1959 { 1960 while (dequeue.isAtOpenOrOpenCloseTag()) 1961 { 1962 ComponentTag tag = dequeue.takeTag(); 1963 1964 // see if child is already added to parent 1965 Component child = findChildComponent(tag); 1966 1967 if (child == null) 1968 { 1969 // the container does not yet have a child with this id, see if we can 1970 // dequeue 1971 child = dequeue.findComponentToDequeue(tag); 1972 1973 //if tag has an autocomponent factory let's use it 1974 if (child == null && tag.getAutoComponentFactory() != null) 1975 { 1976 IAutoComponentFactory autoComponentFactory = tag.getAutoComponentFactory(); 1977 child = autoComponentFactory.newComponent(this, tag); 1978 } 1979 1980 if (child != null) 1981 { 1982 addDequeuedComponent(child, tag); 1983 } 1984 } 1985 1986 if (tag.isOpen() && !tag.hasNoCloseTag()) 1987 { 1988 dequeueChild(child, tag, dequeue); 1989 } 1990 } 1991 1992 } 1993 1994 /** 1995 * Search the child component for the given tag. 1996 * 1997 * @param tag 1998 * the component tag 1999 * @return the child component for the given tag or null if no child can not be found. 2000 */ 2001 protected Component findChildComponent(ComponentTag tag) 2002 { 2003 return get(tag.getId()); 2004 } 2005 2006 /** 2007 * Propagates dequeuing to child component. 2008 * 2009 * @param child 2010 * the child component 2011 * @param tag 2012 * the child tag 2013 * @param dequeue 2014 * the dequeue context to use 2015 */ 2016 private void dequeueChild(Component child, ComponentTag tag, DequeueContext dequeue) 2017 { 2018 ChildToDequeueType childType = ChildToDequeueType.fromChild(child); 2019 2020 if (childType == ChildToDequeueType.QUEUE_REGION || 2021 childType == ChildToDequeueType.BORDER) 2022 { 2023 ((IQueueRegion)child).dequeue(); 2024 } 2025 2026 if (childType == ChildToDequeueType.BORDER) 2027 { 2028 Border childContainer = (Border)child; 2029 // propagate dequeuing to border's body 2030 MarkupContainer body = childContainer.getBodyContainer(); 2031 2032 dequeueChildrenContainer(dequeue, body); 2033 } 2034 2035 if (childType == ChildToDequeueType.MARKUP_CONTAINER) 2036 { 2037 // propagate dequeuing to containers 2038 MarkupContainer childContainer = (MarkupContainer)child; 2039 2040 dequeueChildrenContainer(dequeue, childContainer); 2041 } 2042 2043 if (childType == ChildToDequeueType.NULL || 2044 childType == ChildToDequeueType.QUEUE_REGION) 2045 { 2046 dequeue.skipToCloseTag(); 2047 } 2048 2049 // pull the close tag off 2050 ComponentTag close = dequeue.takeTag(); 2051 do 2052 { 2053 if (close != null && close.closes(tag)) 2054 { 2055 return; 2056 } 2057 } while ((close = dequeue.takeTag()) != null); 2058 2059 throw new IllegalStateException(String.format("Could not find the closing tag for '%s'", tag)); 2060 } 2061 2062 private void dequeueChildrenContainer(DequeueContext dequeue, MarkupContainer child) 2063 { 2064 dequeue.pushContainer(child); 2065 child.dequeue(dequeue); 2066 dequeue.popContainer(); 2067 } 2068 2069 /** @see IQueueRegion#newDequeueContext() */ 2070 public DequeueContext newDequeueContext() 2071 { 2072 IMarkupFragment markup = getRegionMarkup(); 2073 if (markup == null) 2074 { 2075 return null; 2076 } 2077 2078 return new DequeueContext(markup, this, false); 2079 } 2080 2081 /** @see IQueueRegion#getRegionMarkup() */ 2082 public IMarkupFragment getRegionMarkup() 2083 { 2084 return getAssociatedMarkup(); 2085 } 2086 2087 /** 2088 * Checks if this container can dequeue a child represented by the specified tag. This method 2089 * should be overridden when containers can dequeue components represented by non-standard tags. 2090 * For example, borders override this method and dequeue their body container when processing 2091 * the body tag. 2092 * 2093 * By default all {@link ComponentTag}s are supported as well as {@link WicketTag}s that return 2094 * a non-null value from {@link WicketTag#getAutoComponentFactory()} method. 2095 * 2096 * @param tag 2097 */ 2098 protected DequeueTagAction canDequeueTag(ComponentTag tag) 2099 { 2100 if (tag instanceof WicketTag) 2101 { 2102 WicketTag wicketTag = (WicketTag)tag; 2103 if (wicketTag.isContainerTag()) 2104 { 2105 return DequeueTagAction.DEQUEUE; 2106 } 2107 else if (wicketTag.getAutoComponentFactory() != null) 2108 { 2109 return DequeueTagAction.DEQUEUE; 2110 } 2111 else if (wicketTag.isFragmentTag()) 2112 { 2113 return DequeueTagAction.SKIP; 2114 } 2115 else if (wicketTag.isChildTag()) 2116 { 2117 return DequeueTagAction.IGNORE; 2118 } 2119 else if (wicketTag.isHeadTag()) 2120 { 2121 return DequeueTagAction.SKIP; 2122 } 2123 else if (wicketTag.isLinkTag()) 2124 { 2125 return DequeueTagAction.DEQUEUE; 2126 } 2127 else 2128 { 2129 return null; // don't know 2130 } 2131 } 2132 2133 //if is a label tag, ignore it 2134 if (tag.isAutoComponentTag() 2135 && tag.getId().startsWith(AutoLabelResolver.LABEL_ATTR)) 2136 { 2137 return DequeueTagAction.IGNORE; 2138 } 2139 2140 return DequeueTagAction.DEQUEUE; 2141 } 2142 2143 /** 2144 * Queries this container to find a child that can be dequeued that matches the specified tag. 2145 * The default implementation will check if there is a component in the queue that has the same 2146 * id as a tag, but sometimes custom tags can be dequeued and in those situations this method 2147 * should be overridden. 2148 * 2149 * @param tag 2150 * @return 2151 */ 2152 public Component findComponentToDequeue(ComponentTag tag) 2153 { 2154 return queue == null ? null : queue.remove(tag.getId()); 2155 } 2156 2157 /** 2158 * Adds a dequeued component to this container. This method should rarely be overridden because 2159 * the common case of simply forwarding the component to 2160 * {@link MarkupContainer#add(Component...)} method should cover most cases. Components that 2161 * implement a custom hierarchy, such as borders, may wish to override it to support edge-case 2162 * non-standard behavior. 2163 * 2164 * @param component 2165 * @param tag 2166 */ 2167 protected void addDequeuedComponent(Component component, ComponentTag tag) 2168 { 2169 add(component); 2170 } 2171 2172 /** 2173 * Returns a sequential {@code Stream} with the direct children of this markup container as its 2174 * source. This stream doesn't traverse the component tree. 2175 * 2176 * @return a sequential {@code Stream} over the direct children of this markup container 2177 * @since 8.0 2178 */ 2179 public Stream<Component> stream() 2180 { 2181 return StreamSupport.stream(spliterator(), false); 2182 } 2183 2184 /** 2185 * Returns a sequential {@code Stream} with the all children of this markup container as its 2186 * source. This stream does traverse the component tree. 2187 * 2188 * @return a sequential {@code Stream} over the all children of this markup container 2189 * @since 8.0 2190 */ 2191 @SuppressWarnings("unchecked") 2192 public Stream<Component> streamChildren() 2193 { 2194 class ChildrenIterator<C> implements Iterator<C> 2195 { 2196 private Iterator<C> currentIterator; 2197 2198 private Deque<Iterator<C>> iteratorStack = new ArrayDeque<>(); 2199 2200 private ChildrenIterator(Iterator<C> iterator) 2201 { 2202 currentIterator = iterator; 2203 } 2204 2205 @Override 2206 public boolean hasNext() 2207 { 2208 while (!currentIterator.hasNext() && !iteratorStack.isEmpty()) 2209 { 2210 currentIterator = iteratorStack.pop(); 2211 } 2212 return currentIterator.hasNext(); 2213 } 2214 2215 @Override 2216 public C next() 2217 { 2218 C child = currentIterator.next(); 2219 if (child instanceof Iterable) 2220 { 2221 iteratorStack.push(currentIterator); 2222 currentIterator = ((Iterable<C>)child).iterator(); 2223 } 2224 return child; 2225 } 2226 } 2227 return StreamSupport.stream( 2228 Spliterators.spliteratorUnknownSize(new ChildrenIterator<>(iterator()), 0), false); 2229 } 2230}