| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.wicket; |
| |
| import java.io.Serializable; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Deque; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Spliterators; |
| import java.util.stream.Stream; |
| import java.util.stream.StreamSupport; |
| |
| import org.apache.commons.collections4.map.LinkedMap; |
| import org.apache.wicket.behavior.OutputMarkupContainerClassNameBehavior; |
| import org.apache.wicket.core.util.string.ComponentStrings; |
| import org.apache.wicket.markup.ComponentTag; |
| import org.apache.wicket.markup.IMarkupFragment; |
| import org.apache.wicket.markup.Markup; |
| import org.apache.wicket.markup.MarkupElement; |
| import org.apache.wicket.markup.MarkupException; |
| import org.apache.wicket.markup.MarkupFactory; |
| import org.apache.wicket.markup.MarkupNotFoundException; |
| import org.apache.wicket.markup.MarkupStream; |
| import org.apache.wicket.markup.MarkupType; |
| import org.apache.wicket.markup.WicketTag; |
| import org.apache.wicket.markup.html.border.Border; |
| import org.apache.wicket.markup.resolver.ComponentResolvers; |
| import org.apache.wicket.model.IComponentInheritedModel; |
| import org.apache.wicket.model.IModel; |
| import org.apache.wicket.model.IWrapModel; |
| import org.apache.wicket.settings.DebugSettings; |
| import org.apache.wicket.util.lang.Args; |
| import org.apache.wicket.util.lang.Classes; |
| import org.apache.wicket.util.lang.Generics; |
| import org.apache.wicket.util.string.Strings; |
| import org.apache.wicket.util.visit.ClassVisitFilter; |
| import org.apache.wicket.util.visit.IVisit; |
| import org.apache.wicket.util.visit.IVisitor; |
| import org.apache.wicket.util.visit.Visits; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * A MarkupContainer holds a map of child components. |
| * <ul> |
| * <li><b>Children </b>- Children can be added by calling the {@link #add(Component...)} method, and |
| * they can be looked up using a colon separated path. For example, if a container called "a" held a |
| * nested container "b" which held a nested component "c", then a.get("b:c") would return the |
| * Component with id "c". The number of children in a MarkupContainer can be determined by calling |
| * size(), and the whole hierarchy of children held by a MarkupContainer can be traversed by calling |
| * visitChildren(), passing in an implementation of IVisitor.</li> |
| * |
| * <li><b>Markup Rendering </b>- A MarkupContainer also holds/references associated markup which is |
| * used to render the container. As the markup stream for a container is rendered, component |
| * references in the markup are resolved by using the container to look up Components in the |
| * container's component map by id. Each component referenced by the markup stream is given an |
| * opportunity to render itself using the markup stream.</li> |
| * </ul> |
| * <p> |
| * Components may alter their referring tag, replace the tag's body or insert markup after the tag. |
| * But components cannot remove tags from the markup stream. This is an important guarantee because |
| * graphic designers may be setting attributes on component tags that affect visual presentation. |
| * <p> |
| * The type of markup held in a given container subclass can be determined by calling |
| * {@link #getMarkupType()}. Markup is accessed via a MarkupStream object which allows a component |
| * to traverse ComponentTag and RawMarkup MarkupElements while rendering a response. Markup in the |
| * stream may be HTML or some other kind of markup, such as VXML, as determined by the specific |
| * container subclass. |
| * <p> |
| * A markup stream may be directly associated with a container via setMarkupStream. However, a |
| * container which does not have a markup stream (its getMarkupStream() returns null) may inherit a |
| * markup stream from a container above it in the component hierarchy. The |
| * {@link #findMarkupStream()} method will locate the first container at or above this container |
| * which has a markup stream. |
| * <p> |
| * All Page containers set a markup stream before rendering by calling the method |
| * {@link #getAssociatedMarkupStream(boolean)} to load the markup associated with the page. Since |
| * Page is at the top of the container hierarchy, it is guaranteed that {@link #findMarkupStream()} |
| * will always return a valid markup stream. |
| * |
| * @see MarkupStream |
| * @author Jonathan Locke |
| */ |
| public abstract class MarkupContainer extends Component implements Iterable<Component> |
| { |
| private static final long serialVersionUID = 1L; |
| |
| private static final int INITIAL_CHILD_LIST_CAPACITY = 12; |
| |
| /** |
| * The threshold where we start using a Map to store children in, replacing a List. Adding |
| * components to a list is O(n), and to a map O(1). The magic number is 24, due to a Map using |
| * more memory to store its elements and below 24 children there's no discernible difference |
| * between adding to a Map or a List. |
| * |
| * We have focused on adding elements to a list, instead of indexed lookups because adding is an |
| * action that is performed very often, and lookups often are done by component IDs, not index. |
| */ |
| static final int MAPIFY_THRESHOLD = 24; // 32 * 0.75 |
| |
| /** Log for reporting. */ |
| private static final Logger log = LoggerFactory.getLogger(MarkupContainer.class); |
| |
| /** |
| * Metadata key for looking up the list of removed children necessary for tracking modifications |
| * during iteration of the children of this markup container. |
| * |
| * This is stored in meta data because it only is necessary when a child is removed, and this |
| * saves the memory necessary for a field on a widely used class. |
| */ |
| private static final MetaDataKey<LinkedList<RemovedChild>> REMOVALS_KEY = new MetaDataKey<>() |
| { |
| private static final long serialVersionUID = 1L; |
| }; |
| |
| /** |
| * Administrative class for detecting removed children during child iteration. Not intended to |
| * be serializable but for e.g. determining the size of the component it has to be serializable. |
| */ |
| private static class RemovedChild implements Serializable |
| { |
| private static final long serialVersionUID = 1L; |
| |
| private transient final Component removedChild; |
| private transient final Component previousSibling; |
| |
| private RemovedChild(Component removedChild, Component previousSibling) |
| { |
| this.removedChild = removedChild; |
| this.previousSibling = previousSibling; |
| } |
| } |
| |
| /** |
| * Administrative counter to keep track of modifications to the list of children during |
| * iteration. |
| * |
| * When the {@link #children_size()} changes due to an addition or removal of a child component, |
| * the modCounter is increased. This way iterators that iterate over the children of this |
| * container can keep track when they need to change their iteration strategy. |
| */ |
| private transient int modCounter = 0; |
| |
| /** |
| * The children of this markup container, if any. Can be a Component when there's only one |
| * child, a List when the number of children is fewer than {@link #MAPIFY_THRESHOLD} or a Map |
| * when there are more children. |
| */ |
| private Object children; |
| |
| public MarkupContainer(final String id) |
| { |
| this(id, null); |
| } |
| |
| public MarkupContainer(final String id, IModel<?> model) |
| { |
| super(id, model); |
| } |
| |
| /** |
| * Adds the child component(s) to this container. |
| * |
| * @param children |
| * The child(ren) to add. |
| * @throws IllegalArgumentException |
| * Thrown if a child with the same id is replaced by the add operation. |
| * @return This |
| */ |
| public MarkupContainer add(final Component... children) |
| { |
| for (Component child : children) |
| { |
| Args.notNull(child, "child"); |
| |
| if (this == child) |
| { |
| throw new IllegalArgumentException( |
| exceptionMessage("Trying to add this component to itself.")); |
| } |
| |
| MarkupContainer parent = getParent(); |
| while (parent != null) |
| { |
| if (child == parent) |
| { |
| String msg = "You can not add a component's parent as child to the component (loop): Component: " + |
| this.toString(false) + "; parent == child: " + parent.toString(false); |
| |
| if (child instanceof Border.BorderBodyContainer) |
| { |
| msg += ". Please consider using Border.addToBorder(new " + |
| Classes.simpleName(this.getClass()) + "(\"" + this.getId() + |
| "\", ...) instead of add(...)"; |
| } |
| |
| throw new WicketRuntimeException(msg); |
| } |
| |
| parent = parent.getParent(); |
| } |
| |
| checkHierarchyChange(child); |
| |
| if (log.isDebugEnabled()) |
| { |
| log.debug("Add " + child.getId() + " to " + this); |
| } |
| |
| // Add the child to my children |
| Component previousChild = children_put(child); |
| if (previousChild != null && previousChild != child) |
| { |
| throw new IllegalArgumentException( |
| exceptionMessage("A child '" + previousChild.getClass().getSimpleName() + |
| "' with id '" + child.getId() + "' already exists")); |
| } |
| |
| addedComponent(child); |
| |
| } |
| return this; |
| } |
| |
| /** |
| * Replaces a child component of this container with another or just adds it in case no child |
| * with the same id existed yet. |
| * |
| * @param children |
| * The child(ren) to be added or replaced |
| * @return this markup container |
| */ |
| public MarkupContainer addOrReplace(final Component... children) |
| { |
| for (Component child : children) |
| { |
| Args.notNull(child, "child"); |
| |
| checkHierarchyChange(child); |
| |
| if (get(child.getId()) == null) |
| { |
| add(child); |
| } |
| else |
| { |
| replace(child); |
| } |
| } |
| |
| return this; |
| } |
| |
| /** |
| * This method allows a component to be added by an auto-resolver such as AutoLinkResolver. |
| * While the component is being added, the component's FLAG_AUTO boolean is set. The isAuto() |
| * method of Component returns true if a component or any of its parents has this bit set. When |
| * a component is added via autoAdd(), the logic in Page that normally (a) checks for |
| * modifications during the rendering process, and (b) versions components, is bypassed if |
| * Component.isAuto() returns true. |
| * <p> |
| * The result of all this is that components added with autoAdd() are free from versioning and |
| * can add their own children without the usual exception that would normally be thrown when the |
| * component hierarchy is modified during rendering. |
| * |
| * @param component |
| * The component to add |
| * @param markupStream |
| * Null, if the parent container is able to provide the markup. Else the markup |
| * stream to be used to render the component. |
| * @return True, if component has been added |
| */ |
| public final boolean autoAdd(final Component component, MarkupStream markupStream) |
| { |
| Args.notNull(component, "component"); |
| |
| // Replace strategy |
| component.setAuto(true); |
| |
| if (markupStream != null) |
| { |
| component.setMarkup(markupStream.getMarkupFragment()); |
| } |
| |
| // Add the child to the parent. |
| |
| // Arguably child.setParent() can be used as well. It connects the child to the parent and |
| // that's all what most auto-components need. Unfortunately child.onDetach() will not / can |
| // not be invoked, since the parent doesn't known its one of his children. Hence we need to |
| // properly add it. |
| children_remove(component.getId()); |
| add(component); |
| |
| return true; |
| } |
| |
| /** |
| * @param component |
| * The component to check |
| * @param recurse |
| * True if all descendents should be considered |
| * @return True if the component is contained in this container |
| */ |
| public boolean contains(final Component component, final boolean recurse) |
| { |
| Args.notNull(component, "component"); |
| |
| if (recurse) |
| { |
| // Start at component and continue while we're not out of parents |
| for (Component current = component; current != null;) |
| { |
| // Get parent |
| final MarkupContainer parent = current.getParent(); |
| |
| // If this container is the parent, then the component is |
| // recursively contained by this container |
| if (parent == this) |
| { |
| // Found it! |
| return true; |
| } |
| |
| // Move up the chain to the next parent |
| current = parent; |
| } |
| |
| // Failed to find this container in component's ancestry |
| return false; |
| } |
| else |
| { |
| // Is the component contained in this container? |
| return component.getParent() == this; |
| } |
| } |
| |
| /** |
| * Get a child component by looking it up with the given path. |
| * <p> |
| * A component path consists of component ids separated by colons, e.g. "b:c" identifies a |
| * component "c" inside container "b" inside this container. |
| * |
| * @param path |
| * path to component |
| * @return The component at the path |
| */ |
| @Override |
| public final Component get(String path) |
| { |
| // Reference to this container |
| if (Strings.isEmpty(path)) |
| { |
| return this; |
| } |
| |
| // process parent .. references |
| |
| MarkupContainer container = this; |
| |
| String id = Strings.firstPathComponent(path, Component.PATH_SEPARATOR); |
| |
| while (Component.PARENT_PATH.equals(id)) |
| { |
| container = container.getParent(); |
| if (container == null) |
| { |
| return null; |
| } |
| path = path.length() == id.length() ? "" : path.substring(id.length() + 1); |
| id = Strings.firstPathComponent(path, Component.PATH_SEPARATOR); |
| } |
| |
| if (Strings.isEmpty(id)) |
| { |
| return container; |
| } |
| |
| // Get child by id |
| Component child = container.children_get(id); |
| |
| // Found child? |
| if (child != null) |
| { |
| String path2 = Strings.afterFirstPathComponent(path, Component.PATH_SEPARATOR); |
| |
| // Recurse on latter part of path |
| return child.get(path2); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Gets a fresh markup stream that contains the (immutable) markup resource for this class. |
| * |
| * @param throwException |
| * If true, throw an exception, if markup could not be found |
| * @return A stream of MarkupElement elements |
| */ |
| public MarkupStream getAssociatedMarkupStream(final boolean throwException) |
| { |
| IMarkupFragment markup = getAssociatedMarkup(); |
| |
| // If we found markup for this container |
| if (markup != null) |
| { |
| return new MarkupStream(markup); |
| } |
| |
| if (throwException == true) |
| { |
| // throw exception since there is no associated markup |
| throw new MarkupNotFoundException( |
| "Markup of type '" + |
| getMarkupType().getExtension() + |
| "' for component '" + |
| getClass().getName() + |
| "' not found." + |
| " Enable debug messages for org.apache.wicket.util.resource to get a list of all filenames tried.: " + |
| toString()); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Gets a fresh markup stream that contains the (immutable) markup resource for this class. |
| * |
| * @return A stream of MarkupElement elements. Null if not found. |
| */ |
| public Markup getAssociatedMarkup() |
| { |
| try |
| { |
| Markup markup = MarkupFactory.get().getMarkup(this, false); |
| |
| // If we found markup for this container |
| if ((markup != null) && (markup != Markup.NO_MARKUP)) |
| { |
| return markup; |
| } |
| |
| return null; |
| } |
| catch (MarkupException ex) |
| { |
| // re-throw it. The exception contains already all the information |
| // required. |
| throw ex; |
| } |
| catch (MarkupNotFoundException ex) |
| { |
| // re-throw it. The exception contains already all the information |
| // required. |
| throw ex; |
| } |
| catch (WicketRuntimeException ex) |
| { |
| // throw exception since there is no associated markup |
| throw new MarkupNotFoundException( |
| exceptionMessage("Markup of type '" + getMarkupType().getExtension() + |
| "' for component '" + getClass().getName() + "' not found." + |
| " Enable debug messages for org.apache.wicket.util.resource to get a list of all filenames tried"), |
| ex); |
| } |
| } |
| |
| /** |
| * Get the markup of the child. |
| * |
| * @see Component#getMarkup() |
| * |
| * @param child |
| * The child component. If null, the container's markup will be returned. See Border, |
| * Panel or Enclosure where getMarkup(null) != getMarkup(). |
| * @return The child's markup |
| */ |
| public IMarkupFragment getMarkup(final Component child) |
| { |
| // Delegate request to attached markup sourcing strategy. |
| return getMarkupSourcingStrategy().getMarkup(this, child); |
| } |
| |
| /** |
| * Get the type of associated markup for this component. The markup type for a component is |
| * independent of whether or not the component actually has an associated markup resource file |
| * (which is determined at runtime). |
| * |
| * @return The type of associated markup for this component (for example, "html", "wml" or |
| * "vxml"). If there is no markup type for a component, null may be returned, but this |
| * means that no markup can be loaded for the class. Null is also returned if the |
| * component, or any of its parents, has not been added to a Page. |
| */ |
| public MarkupType getMarkupType() |
| { |
| MarkupContainer parent = getParent(); |
| if (parent != null) |
| { |
| return parent.getMarkupType(); |
| } |
| return null; |
| } |
| |
| /** |
| * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT. |
| * |
| * Adds a child component to this container. |
| * |
| * @param child |
| * The child |
| * @throws IllegalArgumentException |
| * Thrown if a child with the same id is replaced by the add operation. |
| */ |
| public void internalAdd(final Component child) |
| { |
| if (log.isDebugEnabled()) |
| { |
| log.debug("internalAdd " + child.getId() + " to " + this); |
| } |
| |
| // Add to map |
| children_put(child); |
| addedComponent(child); |
| } |
| |
| /** |
| * Gives an iterator that allow you to iterate through the children of this markup container in |
| * the order the children were added. The iterator supports additions and removals from the list |
| * of children during iteration. |
| * |
| * @return Iterator that iterates through children in the order they were added |
| */ |
| @Override |
| public Iterator<Component> iterator() |
| { |
| /** |
| * Iterator that knows how to change between a single child, list of children and map of |
| * children. Keeps track when the iterator was last sync'd with the markup container's |
| * tracking of changes to the list of children. |
| */ |
| class MarkupChildIterator implements Iterator<Component> |
| { |
| private int indexInRemovalsSinceLastUpdate; |
| private int expectedModCounter = -1; |
| private Component currentComponent = null; |
| private Iterator<Component> internalIterator = null; |
| |
| @Override |
| public boolean hasNext() |
| { |
| refreshInternalIteratorIfNeeded(); |
| return internalIterator.hasNext(); |
| } |
| |
| @Override |
| public Component next() |
| { |
| refreshInternalIteratorIfNeeded(); |
| return currentComponent = internalIterator.next(); |
| } |
| |
| @Override |
| public void remove() |
| { |
| MarkupContainer.this.remove(currentComponent); |
| refreshInternalIteratorIfNeeded(); |
| } |
| |
| private void refreshInternalIteratorIfNeeded() |
| { |
| if (expectedModCounter >= modCounter) { |
| // no new modifications |
| return; |
| } |
| |
| if (children == null) |
| { |
| internalIterator = Collections.emptyIterator(); |
| } |
| else if (children instanceof Component) |
| { |
| internalIterator = Collections.singleton((Component)children).iterator(); |
| } |
| else if (children instanceof List) |
| { |
| List<Component> childrenList = children(); |
| internalIterator = childrenList.iterator(); |
| } |
| else |
| { |
| Map<String, Component> childrenMap = children(); |
| internalIterator = childrenMap.values().iterator(); |
| } |
| |
| // since we now have a new iterator, we need to set it to the last known position |
| currentComponent = findLastExistingChildAlreadyReturned(currentComponent); |
| expectedModCounter = modCounter; |
| |
| if (currentComponent != null) |
| { |
| // move the new internal iterator to the place of the last processed component |
| while (internalIterator.hasNext() && |
| internalIterator.next() != currentComponent) |
| // noop |
| ; |
| } |
| } |
| |
| private Component findLastExistingChildAlreadyReturned(Component current) |
| { |
| if (current == null) { |
| indexInRemovalsSinceLastUpdate = 0; |
| } else { |
| LinkedList<RemovedChild> removals = removals_get(); |
| if (removals != null) { |
| check_removed: |
| while (current != null) |
| { |
| for (int i = indexInRemovalsSinceLastUpdate; i < removals.size(); i++) |
| { |
| RemovedChild removal = removals.get(i); |
| if (removal.removedChild == current || |
| removal.removedChild == null) |
| { |
| current = removal.previousSibling; |
| |
| // current was removed, use its sibling instead |
| continue check_removed; |
| } |
| } |
| |
| // current wasn't removed, keep it |
| break; |
| } |
| |
| indexInRemovalsSinceLastUpdate = removals.size(); |
| } |
| } |
| |
| return current; |
| } |
| }; |
| return new MarkupChildIterator(); |
| } |
| |
| /** |
| * Creates an iterator that iterates over children in the order specified by comparator. This |
| * works on a copy of the children list. |
| * |
| * @param comparator |
| * The comparator |
| * @return Iterator that iterates over children in the order specified by comparator |
| */ |
| public final Iterator<Component> iterator(Comparator<Component> comparator) |
| { |
| final List<Component> sorted = copyChildren(); |
| Collections.sort(sorted, comparator); |
| return sorted.iterator(); |
| } |
| |
| /** |
| * Removes a component from the children identified by the {@code component.getId()} |
| * |
| * @param component |
| * Component to remove from this container |
| * @return {@code this} for chaining |
| */ |
| public MarkupContainer remove(final Component component) |
| { |
| checkHierarchyChange(component); |
| |
| Args.notNull(component, "component"); |
| |
| children_remove(component.getId()); |
| removedComponent(component); |
| |
| return this; |
| } |
| |
| /** |
| * Removes the given component |
| * |
| * @param id |
| * The id of the component to remove |
| * @return {@code this} for chaining |
| */ |
| public MarkupContainer remove(final String id) |
| { |
| Args.notNull(id, "id"); |
| |
| final Component component = get(id); |
| if (component != null) |
| { |
| remove(component); |
| } |
| else |
| { |
| throw new WicketRuntimeException("Unable to find a component with id '" + id + |
| "' to remove"); |
| } |
| |
| return this; |
| } |
| |
| /** |
| * Removes all children from this container. |
| * <p> |
| * Note: implementation does not call {@link MarkupContainer#remove(Component) } for each |
| * component. |
| * |
| * @return {@code this} for method chaining |
| */ |
| public MarkupContainer removeAll() |
| { |
| if (children != null) |
| { |
| addStateChange(); |
| |
| for (Component child : this) |
| { |
| // Do not call remove() because the state change would then be |
| // recorded twice. |
| child.internalOnRemove(); |
| child.detach(); |
| child.setParent(null); |
| } |
| |
| children = null; |
| removals_add(null, null); |
| } |
| |
| return this; |
| } |
| |
| /** |
| * Renders the entire associated markup for a container such as a Border or Panel. Any leading |
| * or trailing raw markup in the associated markup is skipped. |
| * |
| * @param openTagName |
| * the tag to render the associated markup for |
| */ |
| public final void renderAssociatedMarkup(final String openTagName) |
| { |
| // Get associated markup file for the Border or Panel component |
| final MarkupStream associatedMarkupStream = new MarkupStream(getMarkup(null)); |
| |
| // Get open tag in associated markup of border component |
| MarkupElement elem = associatedMarkupStream.get(); |
| if ((elem instanceof ComponentTag) == false) |
| { |
| associatedMarkupStream.throwMarkupException("Expected the open tag. Markup for a " |
| + openTagName + " component must begin a tag like '<wicket:" + openTagName + ">'"); |
| } |
| |
| // Check for required open tag name |
| ComponentTag associatedMarkupOpenTag = (ComponentTag)elem; |
| if (!(associatedMarkupOpenTag.isOpen() && (associatedMarkupOpenTag instanceof WicketTag))) |
| { |
| associatedMarkupStream.throwMarkupException("Markup for a " + openTagName |
| + " component must begin a tag like '<wicket:" + openTagName + ">'"); |
| } |
| |
| try |
| { |
| setIgnoreAttributeModifier(true); |
| final boolean outputClassName = getApplication().getDebugSettings() |
| .isOutputMarkupContainerClassName(); |
| if (outputClassName) |
| { |
| associatedMarkupOpenTag.addBehavior(OutputMarkupContainerClassNameBehavior.INSTANCE); |
| } |
| |
| renderComponentTag(associatedMarkupOpenTag); |
| associatedMarkupStream.next(); |
| |
| renderComponentTagBody(associatedMarkupStream, associatedMarkupOpenTag); |
| |
| renderClosingComponentTag(associatedMarkupStream, associatedMarkupOpenTag, false); |
| } |
| finally |
| { |
| setIgnoreAttributeModifier(false); |
| } |
| } |
| |
| /** |
| * Replaces a child component of this container with another |
| * |
| * @param child |
| * The child |
| * @throws IllegalArgumentException |
| * Thrown if there was no child with the same id. |
| * @return This |
| */ |
| public MarkupContainer replace(final Component child) |
| { |
| Args.notNull(child, "child"); |
| |
| checkHierarchyChange(child); |
| |
| if (log.isDebugEnabled()) |
| { |
| log.debug("Replacing " + child.getId() + " in " + this); |
| } |
| |
| if (child.getParent() != this) |
| { |
| final Component replaced = children_put(child); |
| |
| // Look up to make sure it was already in the map |
| if (replaced == null) |
| { |
| throw new WicketRuntimeException( |
| exceptionMessage("Cannot replace a component which has not been added: id='" + |
| child.getId() + "', component=" + child)); |
| } |
| |
| // first remove the component. |
| removedComponent(replaced); |
| |
| // The generated markup id remains the same |
| child.setMarkupId(replaced); |
| |
| // then add the other one. |
| addedComponent(child); |
| } |
| |
| return this; |
| } |
| |
| @Override |
| public MarkupContainer setDefaultModel(final IModel<?> model) |
| { |
| final IModel<?> previous = getModelImpl(); |
| super.setDefaultModel(model); |
| if (previous instanceof IComponentInheritedModel) |
| { |
| visitChildren(new IVisitor<Component, Void>() |
| { |
| @Override |
| public void component(final Component component, final IVisit<Void> visit) |
| { |
| IModel<?> compModel = component.getDefaultModel(); |
| if (compModel instanceof IWrapModel) |
| { |
| compModel = ((IWrapModel<?>)compModel).getWrappedModel(); |
| } |
| if (compModel == previous) |
| { |
| component.setDefaultModel(null); |
| } |
| else if (compModel == model) |
| { |
| component.modelChanged(); |
| } |
| } |
| |
| }); |
| } |
| return this; |
| } |
| |
| /** |
| * Get the number of children in this container. |
| * |
| * @return Number of children in this container |
| */ |
| public int size() |
| { |
| return children_size(); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return toString(false); |
| } |
| |
| /** |
| * @param detailed |
| * True if a detailed string is desired |
| * @return String representation of this container |
| */ |
| @Override |
| public String toString(final boolean detailed) |
| { |
| final StringBuilder buffer = new StringBuilder(); |
| buffer.append('[').append(Classes.simpleName(this.getClass())).append(' '); |
| buffer.append(super.toString(detailed)); |
| if (detailed && children_size() != 0) |
| { |
| |
| buffer.append(", children = "); |
| |
| // Loop through child components |
| boolean first = true; |
| for (Component child : this) |
| { |
| if (first) |
| { |
| buffer.append(' '); |
| first = false; |
| } |
| buffer.append(child.toString()); |
| } |
| |
| } |
| buffer.append(']'); |
| return buffer.toString(); |
| } |
| |
| /** |
| * Traverses all child components of the given class in this container, calling the visitor's |
| * visit method at each one. |
| * |
| * Make sure that if you give a type S that the clazz parameter will only resolve to those |
| * types. Else a class cast exception will occur. |
| * |
| * @param <S> |
| * The type that goes into the Visitor.component() method. |
| * @param <R> |
| * @param clazz |
| * The class of child to visit |
| * @param visitor |
| * The visitor to call back to |
| * @return The return value from a visitor which halted the traversal, or null if the entire |
| * traversal occurred |
| */ |
| public final <S extends Component, R> R visitChildren(final Class<?> clazz, |
| final IVisitor<S, R> visitor) |
| { |
| return Visits.visitChildren(this, visitor, new ClassVisitFilter(clazz)); |
| } |
| |
| /** |
| * Traverses all child components in this container, calling the visitor's visit method at each |
| * one. |
| * |
| * @param <R> |
| * @param visitor |
| * The visitor to call back to |
| * @return The return value from a visitor which halted the traversal, or null if the entire |
| * traversal occurred |
| */ |
| public final <R> R visitChildren(final IVisitor<Component, R> visitor) |
| { |
| return Visits.visitChildren(this, visitor); |
| } |
| |
| /** |
| * @param child |
| * Component being added |
| */ |
| private void addedComponent(final Component child) |
| { |
| // Check for degenerate case |
| Args.notNull(child, "child"); |
| |
| MarkupContainer parent = child.getParent(); |
| if (parent != null && parent != this) |
| { |
| parent.remove(child); |
| } |
| |
| // Set child's parent |
| child.setParent(this); |
| |
| final DebugSettings debugSettings = Application.get().getDebugSettings(); |
| if (debugSettings.isLinePreciseReportingOnAddComponentEnabled() |
| && debugSettings.getComponentUseCheck()) |
| { |
| child.setMetaData(ADDED_AT_KEY, |
| ComponentStrings.toString(child, new MarkupException("added"))); |
| } |
| |
| Page page = findPage(); |
| |
| if (page != null) |
| { |
| // tell the page a component has been added first, to allow it to initialize |
| page.componentAdded(child); |
| |
| // initialize the component |
| if (page.isInitialized()) |
| { |
| child.internalInitialize(); |
| } |
| } |
| |
| // if the PREPARED_FOR_RENDER flag is set, we have already called |
| // beforeRender on this component's children. So we need to initialize the newly added one |
| if (isPreparedForRender()) |
| { |
| child.beforeRender(); |
| } |
| } |
| |
| /** |
| * THIS METHOD IS NOT PART OF THE PUBLIC API, DO NOT CALL IT |
| * |
| * Overrides {@link Component#internalInitialize()} to call {@link Component#fireInitialize()} |
| * for itself and for all its children. |
| * |
| * @see org.apache.wicket.Component#fireInitialize() |
| */ |
| @Override |
| public final void internalInitialize() |
| { |
| super.fireInitialize(); |
| visitChildren(new IVisitor<Component, Void>() |
| { |
| @Override |
| public void component(final Component component, final IVisit<Void> visit) |
| { |
| component.fireInitialize(); |
| } |
| }); |
| } |
| |
| /* |
| * === Internal management for keeping track of child components === |
| * |
| * A markup container is the base component for containing child objects. It is one of the most |
| * heavily used components so we should keep it's (memory and CPU) footprint small. |
| * |
| * The goals for the internal management of the list of child components are: |
| * |
| * - as low big-O complexity as possible, preferrably O(1) |
| * |
| * - as low memory consumption as possible (don't use more memory than strictly necessary) |
| * |
| * - ensure that iterating through the (list of) children be as consistent as possible |
| * |
| * - retain the order of addition in the iteration |
| * |
| * These goals are attained by storing the children in a single field that is implemented using: |
| * |
| * - a component when there's only one child |
| * |
| * - a list of components when there are more than 1 children |
| * |
| * - a map of components when the number of children makes looking up children by id more costly |
| * than an indexed search (see MAPIFY_THRESHOLD) |
| * |
| * To ensure that iterating through the list of children keeps working even when children are |
| * added, replaced and removed without throwing a ConcurrentModificationException a special |
| * iterator is used. The markup container tracks removals from and additions to the children |
| * during the request, enabling the iterator to skip over those items and adjust to changing |
| * internal data structures. |
| */ |
| |
| /** |
| * A type washing accessor method for getting the children without having to cast the field |
| * explicitly. |
| * |
| * @return the children as a T |
| */ |
| @SuppressWarnings("unchecked") |
| private <T> T children() |
| { |
| return (T)children; |
| } |
| |
| /** |
| * Gets the child with the given {@code childId} |
| * |
| * @param childId |
| * the component identifier |
| * @return The child component or {@code null} when no child with the given identifier exists |
| */ |
| private Component children_get(final String childId) |
| { |
| if (children == null) |
| { |
| return null; |
| } |
| if (children instanceof Component) |
| { |
| Component child = children(); |
| return child.getId().equals(childId) ? child : null; |
| } |
| if (children instanceof List) |
| { |
| List<Component> kids = children(); |
| for (Component child : kids) |
| { |
| if (child.getId().equals(childId)) |
| { |
| return child; |
| } |
| } |
| return null; |
| } |
| Map<String, Component> kids = children(); |
| return kids.get(childId); |
| } |
| |
| /** |
| * Removes the child component identified by {@code childId} from the list of children. |
| * |
| * Will change the internal list or map to a single component when the number of children hits |
| * 1, but not change the internal map to a list when the threshold is reached (the memory was |
| * already claimed, so there's little to be gained other than wasting CPU cycles for the |
| * conversion). |
| * |
| * @param childId |
| * the id of the child component to remove |
| */ |
| private void children_remove(String childId) |
| { |
| if (children instanceof Component) |
| { |
| Component oldChild = children(); |
| if (oldChild.getId().equals(childId)) |
| { |
| children = null; |
| removals_add(oldChild, null); |
| } |
| } |
| else if (children instanceof List) |
| { |
| List<Component> childrenList = children(); |
| Iterator<Component> it = childrenList.iterator(); |
| Component prevChild = null; |
| while (it.hasNext()) |
| { |
| Component child = it.next(); |
| if (child.getId().equals(childId)) |
| { |
| it.remove(); |
| removals_add(child, prevChild); |
| if (childrenList.size() == 1) |
| { |
| children = childrenList.get(0); |
| } |
| return; |
| } |
| prevChild = child; |
| } |
| } |
| else if (children instanceof LinkedMap) |
| { |
| LinkedMap<String, Component> childrenMap = children(); |
| if (childrenMap.containsKey(childId)) |
| { |
| String prevSiblingId = childrenMap.previousKey(childId); |
| Component oldChild = childrenMap.remove(childId); |
| removals_add(oldChild, childrenMap.get(prevSiblingId)); |
| if (childrenMap.size() == 1) |
| { |
| children = childrenMap.values().iterator().next(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Gets the number of child components of this markup container. |
| * |
| * @return The number of children |
| */ |
| private int children_size() |
| { |
| if (children == null) |
| { |
| return 0; |
| } |
| if (children instanceof Component) |
| { |
| return 1; |
| } |
| if (children instanceof List) |
| { |
| List<?> kids = children(); |
| return kids.size(); |
| } |
| return ((Map<?, ?>)children).size(); |
| } |
| |
| /** |
| * Puts the {@code child} component into the list of children of this markup container. If a |
| * component existed with the same {@code child.getId()} it is replaced and the old component is |
| * returned. |
| * |
| * When a component is replaced, the internal structure of the children is not modified, so we |
| * don't have to update the internal {@link #modCounter} in those circumstances. When a |
| * component is added, we do have to increase the {@link #modCounter} to notify iterators of |
| * this change. |
| * |
| * @param child |
| * The child |
| * @return Any component that was replaced |
| */ |
| private Component children_put(final Component child) |
| { |
| if (children == null) |
| { |
| children = child; |
| |
| // it is an addtion, so we need to notify the iterators of this change. |
| modCounter++; |
| |
| return null; |
| } |
| |
| if (children instanceof Component) |
| { |
| /* first see if the child replaces the existing child */ |
| Component oldChild = children(); |
| if (oldChild.getId().equals(child.getId())) |
| { |
| children = child; |
| return oldChild; |
| } |
| else |
| { |
| /* |
| * the put doesn't replace the existing child, so we need to increase the children |
| * storage to a list and add the existing and new child to it |
| */ |
| Component originalChild = children(); |
| List<Component> newChildren = new ArrayList<>(INITIAL_CHILD_LIST_CAPACITY); |
| newChildren.add(originalChild); |
| newChildren.add(child); |
| children = newChildren; |
| |
| // it is an addtion, so we need to notify the iterators of this change. |
| modCounter++; |
| return null; |
| } |
| } |
| |
| if (children instanceof List) |
| { |
| List<Component> childrenList = children(); |
| |
| // first see if the child replaces an existing child |
| for (int i = 0; i < childrenList.size(); i++) |
| { |
| Component curChild = childrenList.get(i); |
| if (curChild.getId().equals(child.getId())) |
| { |
| return childrenList.set(i, child); |
| } |
| } |
| |
| // it is an addtion, so we need to notify the iterators of this change. |
| modCounter++; |
| |
| /* |
| * If it still fits in the allotted number of items of a List, just add it, otherwise |
| * change the internal data structure to a Map for speedier lookups. |
| */ |
| if (childrenList.size() < MAPIFY_THRESHOLD) |
| { |
| childrenList.add(child); |
| } |
| else |
| { |
| Map<String, Component> newChildren = new LinkedMap<>(MAPIFY_THRESHOLD * 2); |
| for (Component curChild : childrenList) |
| { |
| newChildren.put(curChild.getId(), curChild); |
| } |
| newChildren.put(child.getId(), child); |
| children = newChildren; |
| } |
| return null; |
| } |
| |
| Map<String, Component> childrenMap = children(); |
| Component oldChild = childrenMap.put(child.getId(), child); |
| |
| if (oldChild == null) |
| { |
| // it is an addtion, so we need to notify the iterators of this change. |
| modCounter++; |
| } |
| return oldChild; |
| } |
| |
| /** |
| * Retrieves the during the request removed children. These are stored in the metadata and |
| * cleared at the end of the request {@link #onDetach()} |
| * |
| * @return the list of removed children, may be {@code null} |
| */ |
| private LinkedList<RemovedChild> removals_get() |
| { |
| return getRequestFlag(RFLAG_CONTAINER_HAS_REMOVALS) ? getMetaData(REMOVALS_KEY) : null; |
| } |
| |
| /** |
| * Sets the during the request removed children. These are stored in the metadata and cleared at |
| * the end of the request, see {@link #onDetach()}. |
| * |
| * @param removals |
| * the new list of removals |
| */ |
| private void removals_set(LinkedList<RemovedChild> removals) |
| { |
| setRequestFlag(RFLAG_CONTAINER_HAS_REMOVALS, removals != null); |
| setMetaData(REMOVALS_KEY, removals); |
| } |
| |
| /** |
| * Removes the list of removals from the metadata. |
| */ |
| private void removals_clear() |
| { |
| if (getRequestFlag(RFLAG_CONTAINER_HAS_REMOVALS)) |
| { |
| removals_set(null); |
| } |
| } |
| |
| /** |
| * Adds the {@code removedChild} to the list of removals and links it to the |
| * {@code previousSibling} |
| * |
| * @param removedChild |
| * the child that was removed |
| * @param prevSibling |
| * the child that was the previous sibling of the removed child |
| */ |
| private void removals_add(Component removedChild, Component prevSibling) |
| { |
| modCounter++; |
| |
| LinkedList<RemovedChild> removals = removals_get(); |
| if (removals == null) |
| { |
| removals = new LinkedList<>(); |
| removals_set(removals); |
| } |
| removals.add(new RemovedChild(removedChild, prevSibling)); |
| } |
| |
| /** |
| * @param component |
| * Component being removed |
| */ |
| private void removedComponent(final Component component) |
| { |
| // Notify Page that component is being removed |
| final Page page = component.findPage(); |
| if (page != null) |
| { |
| page.componentRemoved(component); |
| } |
| |
| component.detach(); |
| |
| component.internalOnRemove(); |
| |
| // Component is removed |
| component.setParent(null); |
| } |
| |
| /** |
| * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE OR OVERWRITE IT. |
| * |
| * Renders the next element of markup in the given markup stream. |
| * |
| * @param markupStream |
| * The markup stream |
| * @return true, if element was rendered as RawMarkup |
| */ |
| protected boolean renderNext(final MarkupStream markupStream) |
| { |
| // Get the current markup element |
| final MarkupElement element = markupStream.get(); |
| |
| // If it's a tag like <wicket..> or <span wicket:id="..." > |
| if ((element instanceof ComponentTag) && !markupStream.atCloseTag()) |
| { |
| // Get element as tag |
| final ComponentTag tag = (ComponentTag)element; |
| |
| if (tag instanceof WicketTag && ((WicketTag)tag).isFragmentTag()){ |
| return false; |
| } |
| |
| // Get component id |
| final String id = tag.getId(); |
| |
| // Get the component for the id from the given container |
| Component component = get(id); |
| |
| if (component == null) |
| { |
| component = ComponentResolvers.resolve(this, markupStream, tag, null); |
| if ((component != null) && (component.getParent() == null)) |
| { |
| autoAdd(component, markupStream); |
| } |
| else if (component != null) |
| { |
| component.setMarkup(markupStream.getMarkupFragment()); |
| } |
| } |
| |
| // Failed to find it? |
| if (component != null) |
| { |
| component.render(); |
| } |
| else if (tag.getFlag(ComponentTag.RENDER_RAW)) |
| { |
| // No component found, but "render as raw markup" flag found |
| if (canRenderRawTag(tag)) |
| { |
| getResponse().write(element.toCharSequence()); |
| } |
| return true; |
| } |
| else |
| { |
| throwException(markupStream, tag); |
| } |
| } |
| else |
| { |
| // Render as raw markup |
| if (canRenderRawTag(element)) |
| { |
| getResponse().write(element.toCharSequence()); |
| } |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Says if the given tag can be handled as a raw markup. |
| * |
| * @param tag |
| * the current tag. |
| * @return true if the tag can be handled as raw markup, false otherwise. |
| */ |
| private boolean canRenderRawTag(MarkupElement tag) |
| { |
| boolean isWicketTag = tag instanceof WicketTag; |
| |
| boolean stripTag = isWicketTag ? Application.get().getMarkupSettings().getStripWicketTags() : false; |
| |
| return !stripTag; |
| } |
| |
| /** |
| * Throws a {@code org.apache.wicket.markup.MarkupException} when the |
| * component markup is not consistent. |
| * |
| * @param markupStream |
| * the source stream for the component markup. |
| * @param tag |
| * the tag that can not be handled. |
| */ |
| private void throwException(final MarkupStream markupStream, final ComponentTag tag) |
| { |
| final String id = tag.getId(); |
| |
| if (tag instanceof WicketTag) |
| { |
| if (((WicketTag)tag).isChildTag()) |
| { |
| markupStream.throwMarkupException("Found " + tag.toString() + |
| " but no <wicket:extend>. Container: " + toString()); |
| } |
| else |
| { |
| markupStream.throwMarkupException("Failed to handle: " + |
| tag.toString() + |
| ". It might be that no resolver has been registered to handle this special tag. " + |
| " But it also could be that you declared wicket:id=" + id + |
| " in your markup, but that you either did not add the " + |
| "component to your page at all, or that the hierarchy does not match. " + |
| "Container: " + toString()); |
| } |
| } |
| |
| List<String> names = findSimilarComponents(id); |
| |
| // No one was able to handle the component id |
| StringBuilder msg = new StringBuilder(500); |
| msg.append("Unable to find component with id '"); |
| msg.append(id); |
| msg.append("' in "); |
| msg.append(this.toString()); |
| msg.append("\n\tExpected: '"); |
| msg.append(getPageRelativePath()); |
| msg.append(PATH_SEPARATOR); |
| msg.append(id); |
| msg.append("'.\n\tFound with similar names: '"); |
| msg.append(Strings.join("', ", names)); |
| msg.append('\''); |
| |
| log.error(msg.toString()); |
| markupStream.throwMarkupException(msg.toString()); |
| } |
| |
| private List<String> findSimilarComponents(final String id) |
| { |
| final List<String> names = Generics.newArrayList(); |
| |
| Page page = findPage(); |
| if (page != null) |
| { |
| page.visitChildren(new IVisitor<Component, Void>() |
| { |
| @Override |
| public void component(Component component, IVisit<Void> visit) |
| { |
| if (Strings.getLevenshteinDistance(id.toLowerCase(Locale.ROOT), component.getId() |
| .toLowerCase(Locale.ROOT)) < 3) |
| { |
| names.add(component.getPageRelativePath()); |
| } |
| } |
| }); |
| } |
| |
| return names; |
| } |
| |
| /** |
| * Handle the container's body. If your override of this method does not advance the markup |
| * stream to the close tag for the openTag, a runtime exception will be thrown by the framework. |
| * |
| * @param markupStream |
| * The markup stream |
| * @param openTag |
| * The open tag for the body |
| */ |
| @Override |
| public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) |
| { |
| renderComponentTagBody(markupStream, openTag); |
| } |
| |
| @Override |
| protected void onRender() |
| { |
| internalRenderComponent(); |
| } |
| |
| /** |
| * Renders markup for the body of a ComponentTag from the current position in the given markup |
| * stream. If the open tag passed in does not require a close tag, nothing happens. Markup is |
| * rendered until the closing tag for openTag is reached. |
| * |
| * @param markupStream |
| * The markup stream |
| * @param openTag |
| * The open tag |
| */ |
| private void renderComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) |
| { |
| if ((markupStream != null) && (markupStream.getCurrentIndex() > 0)) |
| { |
| // If the original tag has been changed from open-close to open-body-close, than we are |
| // done. Other components, e.g. BorderBody, rely on this method being called. |
| ComponentTag origOpenTag = (ComponentTag)markupStream.get(markupStream.getCurrentIndex() - 1); |
| if (origOpenTag.isOpenClose()) |
| { |
| return; |
| } |
| } |
| |
| // If the open tag requires a close tag |
| boolean render = openTag.requiresCloseTag(); |
| if (render == false) |
| { |
| // Tags like <p> do not require a close tag, but they may have. |
| render = !openTag.hasNoCloseTag(); |
| } |
| |
| if (render) |
| { |
| renderAll(markupStream, openTag); |
| } |
| } |
| |
| /** |
| * Loop through the markup in this container |
| * |
| * @param markupStream |
| * @param openTag |
| */ |
| protected final void renderAll(final MarkupStream markupStream, final ComponentTag openTag) |
| { |
| while (markupStream.isCurrentIndexInsideTheStream()) |
| { |
| // In case of Page we need to render the whole file. For all other components just what |
| // is in between the open and the close tag. |
| if ((openTag != null) && markupStream.get().closes(openTag)) |
| { |
| break; |
| } |
| |
| // Remember where we are |
| final int index = markupStream.getCurrentIndex(); |
| |
| // Render the markup element |
| boolean rawMarkup = renderNext(markupStream); |
| |
| // Go back to where we were and move the markup stream forward to whatever the next |
| // element is. |
| markupStream.setCurrentIndex(index); |
| |
| if (rawMarkup) |
| { |
| markupStream.next(); |
| } |
| else if (!markupStream.getTag().isClose()) |
| { |
| markupStream.skipComponent(); |
| } |
| else |
| { |
| throw new WicketRuntimeException("Ups. This should never happen. " + |
| markupStream.toString()); |
| } |
| } |
| } |
| |
| @Override |
| void removeChildren() |
| { |
| super.removeChildren(); |
| |
| for (Component component : this) |
| { |
| component.internalOnRemove(); |
| } |
| } |
| |
| @Override |
| void detachChildren() |
| { |
| super.detachChildren(); |
| |
| for (Component component : this) |
| { |
| component.detach(); |
| } |
| } |
| |
| /** |
| * |
| * @see org.apache.wicket.Component#internalMarkRendering(boolean) |
| */ |
| @Override |
| void internalMarkRendering(boolean setRenderingFlag) |
| { |
| super.internalMarkRendering(setRenderingFlag); |
| |
| for (Component child : this) |
| { |
| child.internalMarkRendering(setRenderingFlag); |
| } |
| } |
| |
| /** |
| * @return a copy of the children array. |
| */ |
| @SuppressWarnings("unchecked") |
| private List<Component> copyChildren() |
| { |
| if (children == null) |
| { |
| return Collections.emptyList(); |
| } |
| else if (children instanceof Component) |
| { |
| return Collections.singletonList((Component)children); |
| } |
| else if (children instanceof List) |
| { |
| return new ArrayList<>((List<Component>)children); |
| } |
| else |
| { |
| return new ArrayList<>(((Map<String, Component>)children).values()); |
| } |
| } |
| |
| @Override |
| void onBeforeRenderChildren() |
| { |
| super.onBeforeRenderChildren(); |
| |
| try |
| { |
| // Loop through child components |
| for (final Component child : this) |
| { |
| // Get next child |
| // Call begin request on the child |
| // We need to check whether the child's wasn't removed from the |
| // component in the meanwhile (e.g. from another's child |
| // onBeforeRender) |
| if (child.getParent() == this) |
| { |
| child.beforeRender(); |
| } |
| } |
| } |
| catch (RuntimeException ex) |
| { |
| if (ex instanceof WicketRuntimeException) |
| { |
| throw ex; |
| } |
| else |
| { |
| throw new WicketRuntimeException("Error attaching this container for rendering: " + |
| this, ex); |
| } |
| } |
| } |
| |
| @Override |
| void onEnabledStateChanged() |
| { |
| super.onEnabledStateChanged(); |
| visitChildren(new IVisitor<Component, Void>() |
| { |
| @Override |
| public void component(Component component, IVisit<Void> visit) |
| { |
| component.clearEnabledInHierarchyCache(); |
| } |
| }); |
| } |
| |
| @Override |
| void onVisibleStateChanged() |
| { |
| super.onVisibleStateChanged(); |
| visitChildren(new IVisitor<Component, Void>() |
| { |
| @Override |
| public void component(Component component, IVisit<Void> visit) |
| { |
| component.clearVisibleInHierarchyCache(); |
| } |
| }); |
| } |
| |
| @Override |
| protected void onDetach() |
| { |
| super.onDetach(); |
| |
| modCounter++; |
| removals_clear(); |
| } |
| |
| /** |
| * Search the child component for the given tag. |
| * |
| * @param tag |
| * the component tag |
| * @return the child component for the given tag or null if no child can not be found. |
| */ |
| protected Component findChildComponent(ComponentTag tag) |
| { |
| return get(tag.getId()); |
| } |
| |
| /** |
| * Returns a sequential {@code Stream} with the direct children of this markup container as its |
| * source. This stream doesn't traverse the component tree. |
| * |
| * @return a sequential {@code Stream} over the direct children of this markup container |
| * @since 8.0 |
| */ |
| public Stream<Component> stream() |
| { |
| return StreamSupport.stream(spliterator(), false); |
| } |
| |
| /** |
| * Returns a sequential {@code Stream} with the all children of this markup container as its |
| * source. This stream does traverse the component tree. |
| * |
| * @return a sequential {@code Stream} over the all children of this markup container |
| * @since 8.0 |
| */ |
| @SuppressWarnings("unchecked") |
| public Stream<Component> streamChildren() |
| { |
| class ChildrenIterator<C> implements Iterator<C> |
| { |
| private Iterator<C> currentIterator; |
| |
| private Deque<Iterator<C>> iteratorStack = new ArrayDeque<>(); |
| |
| private ChildrenIterator(Iterator<C> iterator) |
| { |
| currentIterator = iterator; |
| } |
| |
| @Override |
| public boolean hasNext() |
| { |
| while (!currentIterator.hasNext() && !iteratorStack.isEmpty()) |
| { |
| currentIterator = iteratorStack.pop(); |
| } |
| return currentIterator.hasNext(); |
| } |
| |
| @Override |
| public C next() |
| { |
| C child = currentIterator.next(); |
| if (child instanceof Iterable) |
| { |
| iteratorStack.push(currentIterator); |
| currentIterator = ((Iterable<C>)child).iterator(); |
| } |
| return child; |
| } |
| } |
| return StreamSupport.stream( |
| Spliterators.spliteratorUnknownSize(new ChildrenIterator<>(iterator()), 0), false); |
| } |
| } |