All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wicket.MarkupContainer Maven / Gradle / Ivy

Go to download

Pax Wicket Service is an OSGi extension of the Wicket framework, allowing for dynamic loading and unloading of Wicket components and pageSources.

There is a newer version: 5.0.0
Show newest version
/*
 * 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.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

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.html.internal.InlineEnclosure;
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.IDebugSettings;
import org.apache.wicket.util.iterator.ComponentHierarchyIterator;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.lang.Generics;
import org.apache.wicket.util.string.ComponentStrings;
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.
 * 
    *
  • Children - Children can be added by calling the add() method, and they can be looked * up using a dotted 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. * *
  • Markup Rendering - 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. *

    * 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. *

    * The type of markup held in a given container subclass can be determined by calling * 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. *

    * 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 findMarkupStream() method * will locate the first container at or above this container which has a markup stream. *

    * All Page containers set a markup stream before rendering by calling the method * getAssociatedMarkupStream() to load the markup associated with the page. Since Page is at the top * of the container hierarchy, it is guaranteed that findMarkupStream will always return a valid * markup stream. * * @see MarkupStream * @author Jonathan Locke * */ public abstract class MarkupContainer extends Component implements Iterable { private static final long serialVersionUID = 1L; /** Log for reporting. */ private static final Logger log = LoggerFactory.getLogger(MarkupContainer.class); /** List of children or single child */ private Object children; /** * @see org.apache.wicket.Component#Component(String) */ public MarkupContainer(final String id) { this(id, null); } /** * @see org.apache.wicket.Component#Component(String, IModel) */ public MarkupContainer(final String id, IModel model) { super(id, model); } /** * Adds a child component to this container. * * @param childs * The child(s) * @throws IllegalArgumentException * Thrown if a child with the same id is replaced by the add operation. * @return This */ public MarkupContainer add(final Component... childs) { for (Component child : childs) { 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 " + this.getClass().getSimpleName() + "(\"" + 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 to map addedComponent(child); if (put(child) != null) { throw new IllegalArgumentException(exceptionMessage("A child with id '" + child.getId() + "' already exists")); } } 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 childs * The child(s) to be added or replaced * @return This */ public MarkupContainer addOrReplace(final Component... childs) { for (Component child : childs) { if (child == null) { throw new IllegalArgumentException("argument child must be not null"); } 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. *

    * 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) { if (component == null) { throw new IllegalArgumentException("argument component may not be null"); } // 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. int index = children_indexOf(component); if (index >= 0) { children_remove(index); } 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) { if (component == null) { throw new IllegalArgumentException("argument component may not be null"); } 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. * * @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 childs markup * * @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 childs 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. * * @return The type of associated markup for this component (for example, "html", "wml" or * "vxml"). 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). 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 addedComponent(child); put(child); } /** * @return Iterator that iterates through children in the order they were added */ public Iterator iterator() { return new Iterator() { int index = 0; public boolean hasNext() { return index < children_size(); } public Component next() { return children_get(index++); } public void remove() { final Component removed = children_remove(--index); checkHierarchyChange(removed); removedComponent(removed); } }; } /** * @param comparator * The comparator * @return Iterator that iterates over children in the order specified by comparator */ public final Iterator iterator(Comparator comparator) { final List sorted; if (children == null) { sorted = Collections.emptyList(); } else { if (children instanceof Component) { sorted = new ArrayList(1); sorted.add((Component)children); } else { int size = children_size(); sorted = new ArrayList(size); for (int i = 0; i < size; i++) { sorted.add(children_get(i)); } } } Collections.sort(sorted, comparator); return sorted.iterator(); } /** * @param component * Component to remove from this container * @return {@code this} for chaining */ public MarkupContainer remove(final Component component) { checkHierarchyChange(component); if (component == null) { throw new IllegalArgumentException("argument component may not be null"); } children_remove(component); 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) { if (id == null) { throw new IllegalArgumentException("argument id may not be null"); } 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. *

    * 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(); // Loop through child components int size = children_size(); for (int i = 0; i < size; i++) { Object childObject = children_get(i, false); if (childObject instanceof Component) { // Get next child final Component child = (Component)childObject; // Do not call remove() because the state change would than be // recorded twice. child.internalOnRemove(); child.detach(); child.setParent(null); } } children = 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 * @param exceptionMessage * message that will be used for exceptions */ public final void renderAssociatedMarkup(final String openTagName, final String exceptionMessage) { // 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. " + exceptionMessage); } // Check for required open tag name ComponentTag associatedMarkupOpenTag = (ComponentTag)elem; if (!((associatedMarkupOpenTag != null) && associatedMarkupOpenTag.isOpen() && (associatedMarkupOpenTag instanceof WicketTag))) { associatedMarkupStream.throwMarkupException(exceptionMessage); } try { setIgnoreAttributeModifier(true); renderComponentTag(associatedMarkupOpenTag); associatedMarkupStream.next(); String className = null; final boolean outputClassName = getApplication().getDebugSettings() .isOutputMarkupContainerClassName(); if (outputClassName) { Class klass = getClass(); while (klass.isAnonymousClass()) { klass = klass.getSuperclass(); } className = klass.getName(); getResponse().write(""); } renderComponentTagBody(associatedMarkupStream, associatedMarkupOpenTag); if (outputClassName) { getResponse().write(""); } 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) { checkHierarchyChange(child); if (child == null) { throw new IllegalArgumentException("argument child must be not null"); } if (log.isDebugEnabled()) { log.debug("Replacing " + child.getId() + " in " + this); } if (child.getParent() != this) { // Add to map final Component replaced = 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; } /** * @see org.apache.wicket.Component#setDefaultModel(org.apache.wicket.model.IModel) */ @Override public MarkupContainer setDefaultModel(final IModel model) { final IModel previous = getModelImpl(); super.setDefaultModel(model); if (previous instanceof IComponentInheritedModel) { visitChildren(new IVisitor() { public void component(final Component component, final IVisit 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(); } /** * @see org.apache.wicket.Component#toString() */ @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(this.getClass().getSimpleName()).append(' '); buffer.append(super.toString(detailed)); if (detailed && children_size() != 0) { buffer.append(", children = "); // Loop through child components final int size = children_size(); for (int i = 0; i < size; i++) { // Get next child final Component child = children_get(i); if (i != 0) { buffer.append(' '); } 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 * The type that goes into the Visitor.component() method. * @param * @param clazz * The class of child to visit, or null to visit all children * @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 visitChildren(final Class clazz, final IVisitor 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 * @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 visitChildren(final IVisitor visitor) { return Visits.visitChildren(this, visitor); } /** * @return A iterator which iterators over all children and grand-children the Component */ public final ComponentHierarchyIterator visitChildren() { return new ComponentHierarchyIterator(this); } /** * @param clazz * Filter condition * @return A iterator which iterators over all children and grand-children the Component, * returning only components which implement (instanceof) the provided clazz. */ public final ComponentHierarchyIterator visitChildren(final Class clazz) { return new ComponentHierarchyIterator(this).filterByClass(clazz); } /** * @param child * Component being added */ private final void addedComponent(final Component child) { // Check for degenerate case Args.notNull(child, "child"); MarkupContainer parent = child.getParent(); if (parent != null) { parent.remove(child); } // Set child's parent child.setParent(this); final IDebugSettings debugSettings = Application.get().getDebugSettings(); if (debugSettings.isLinePreciseReportingOnAddComponentEnabled()) { child.setMetaData(ADDED_AT_KEY, ComponentStrings.toString(child, new MarkupException("added"))); } final 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() { public void component(final Component component, final IVisit visit) { component.fireInitialize(); } }); } /** * @param child * Child to add */ private final void children_add(final Component child) { if (children == null) { children = child; } else { if (!(children instanceof ChildList)) { // Save new children children = new ChildList(children); } ((ChildList)children).add(child); } } /** * Returns child component at the specified index * * @param index * @throws ArrayIndexOutOfBoundsException * @return child component at the specified index */ public final Component get(int index) { return children_get(index); } /** * * @param index * @return The child component */ private final Component children_get(int index) { return (Component)children_get(index, true); } /** * If the given object is a {@link ComponentSourceEntry} instance and reconstruct * is true, it reconstructs the component and returns it. Otherwise it just returns the object * passed as parameter * * @param object * @param reconstruct * @param parent * @param index * @return The object directly or the reconstructed component */ private final Object postprocess(Object object, boolean reconstruct, MarkupContainer parent, int index) { if (reconstruct && object instanceof ComponentSourceEntry) { object = ((ComponentSourceEntry)object).reconstruct(parent, index); } return object; } /** * * @param index * @param reconstruct * @return the child component */ private final Object children_get(int index, boolean reconstruct) { Object component = null; if (children != null) { if (children instanceof Object[] == false && children instanceof ChildList == false) { if (index != 0) { throw new ArrayIndexOutOfBoundsException("index " + index + " is greater then 0"); } component = postprocess(children, reconstruct, this, 0); if (children != component) { children = component; } } else { Object[] children; if (this.children instanceof ChildList) { // we have a list children = ((ChildList)this.children).childs; } else { // we have a object array children = (Object[])this.children; } component = postprocess(children[index], reconstruct, this, index); if (children[index] != component) { children[index] = component; } } } return component; } /** * Returns the wicket:id of the given object, that can be either a {@link Component} or a * {@link ComponentSourceEntry} * * @param object * @return The id of the object (object can be component or componentsourcentry) */ private final String getId(Object object) { if (object instanceof Component) { return ((Component)object).getId(); } else if (object instanceof ComponentSourceEntry) { return ((ComponentSourceEntry)object).id; } else { throw new IllegalArgumentException("Unknown type of object " + object); } } /** * * @param id * @return The child component */ private final Component children_get(final String id) { if (children == null) { return null; } Component component = null; if ((children instanceof Object[] == false) && (children instanceof List == false)) { if (getId(children).equals(id)) { component = (Component)postprocess(children, true, this, 0); if (children != component) { children = component; } } } else { Object[] children; int size = 0; if (this.children instanceof ChildList) { children = ((ChildList)this.children).childs; size = ((ChildList)this.children).size; } else { children = (Object[])this.children; size = children.length; } for (int i = 0; i < size; i++) { if (getId(children[i]).equals(id)) { component = (Component)postprocess(children[i], true, this, i); if (children[i] != component) { children[i] = component; } break; } } } return component; } /** * * @param child * @return The index of the given child component */ private final int children_indexOf(Component child) { if (children == null) { return -1; } if (children instanceof Object[] == false && children instanceof ChildList == false) { if (getId(children).equals(child.getId())) { return 0; } } else { int size = 0; Object[] children; if (this.children instanceof Object[]) { children = (Object[])this.children; size = children.length; } else { children = ((ChildList)this.children).childs; size = ((ChildList)this.children).size; } for (int i = 0; i < size; i++) { if (getId(children[i]).equals(child.getId())) { return i; } } } return -1; } /** * * @param component * @return The component that is removed. */ private final Component children_remove(Component component) { int index = children_indexOf(component); if (index != -1) { return children_remove(index); } return null; } /** * * @param index * @return The component that is removed */ private final Component children_remove(int index) { if (children == null) { return null; } if (children instanceof Component || children instanceof ComponentSourceEntry) { if (index == 0) { final Component removed = (Component)postprocess(children, true, null, -1); children = null; return removed; } else { throw new IndexOutOfBoundsException(); } } else { if (children instanceof Object[]) { Object[] c = ((Object[])children); final Object removed = c[index]; if (c.length == 2) { if (index == 0) { children = c[1]; } else if (index == 1) { children = c[0]; } else { throw new IndexOutOfBoundsException(); } return (Component)postprocess(removed, true, null, -1); } children = new ChildList(children); } ChildList lst = (ChildList)children; Object removed = lst.remove(index); if (lst.size == 1) { children = lst.get(0); } return (Component)postprocess(removed, true, null, -1); } } /** * * @param index * @param child * @param reconstruct * @return The replaced child */ private final Object children_set(int index, Object child, boolean reconstruct) { Object replaced; if (index >= 0 && index < children_size()) { if (children instanceof Component || children instanceof ComponentSourceEntry) { replaced = children; children = child; } else { if (children instanceof ChildList) { replaced = ((ChildList)children).set(index, child); } else { final Object[] children = (Object[])this.children; replaced = children[index]; children[index] = child; } } } else { throw new IndexOutOfBoundsException(); } return postprocess(replaced, reconstruct, null, -1); } /** * * @param index * @param child * @return The component that is replaced */ private final Component children_set(int index, Component child) { return (Component)children_set(index, child, true); } /** * * @return The size of the children */ private final int children_size() { if (children == null) { return 0; } else { if (children instanceof Component || children instanceof ComponentSourceEntry) { return 1; } else if (children instanceof ChildList) { return ((ChildList)children).size; } return ((Object[])children).length; } } /** * Ensure that there is space in childForId map for a new entry before adding it. * * @param child * The child to put into the map * @return Any component that was replaced */ private final Component put(final Component child) { int index = children_indexOf(child); if (index == -1) { children_add(child); return null; } else { return children_set(index, child); } } /** * @param component * Component being removed */ private final 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 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 final boolean renderNext(final MarkupStream markupStream) { // Get the current markup element final MarkupElement element = markupStream.get(); // If it's a tag like or if ((element instanceof ComponentTag) && !markupStream.atCloseTag()) { // Get element as tag final ComponentTag tag = (ComponentTag)element; // 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 getResponse().write(element.toCharSequence()); return true; } else { if (tag instanceof WicketTag) { if (((WicketTag)tag).isChildTag()) { markupStream.throwMarkupException("Found " + tag.toString() + " but no "); } 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 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."); } } List names = findSimilarComponents(id); // No one was able to handle the component id StringBuffer msg = new StringBuffer(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("."); 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()); } } else { // Render as raw markup getResponse().write(element.toCharSequence()); return true; } return false; } private List findSimilarComponents(final String id) { final List names = Generics.newArrayList(); Page page = findPage(); if (page != null) { page.visitChildren(new IVisitor() { public void component(Component component, IVisit visit) { if (Strings.getLevenshteinDistance(id.toLowerCase(), component.getId() .toLowerCase()) < 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); } /** * @see org.apache.wicket.Component#onRender() */ @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 final 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

    do not require a close tag, but they may have. render = !openTag.hasNoCloseTag(); } if (render == true) { 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.hasMore()) { // 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); MarkupElement elem = markupStream.get(); if (rawMarkup) { markupStream.next(); } else if (!markupStream.getTag().isClose()) { markupStream.skipComponent(); } else { throw new WicketRuntimeException("Ups. This should never happen. " + markupStream.toString()); } } } /** * */ private static class ComponentSourceEntry extends org.apache.wicket.ComponentSourceEntry { private ComponentSourceEntry(MarkupContainer container, Component component, IComponentSource componentSource) { super(container, component, componentSource); } private static final long serialVersionUID = 1L; @Override protected void setChild(MarkupContainer parent, int index, Component child) { parent.children_set(index, child, false); } } /** * @see org.apache.wicket.Component#removeChildren() */ @Override void removeChildren() { super.removeChildren(); for (int i = children_size(); i-- > 0;) { Object child = children_get(i, false); if (child instanceof Component) { Component component = (Component)child; component.internalOnRemove(); } } } @Override void detachChildren() { super.detachChildren(); for (int i = children_size(); i-- > 0;) { Object child = children_get(i, false); if (child instanceof Component) { Component component = (Component)child; component.detach(); // We need to keep InlineEnclosures for Ajax request handling. // TODO this is really ugly. Feature request for 1.5: change auto-component that // they don't need to be removed anymore. if (component.isAuto() && !(component instanceof InlineEnclosure)) { children_remove(i); } } } if (children instanceof ChildList) { ChildList lst = (ChildList)children; Object[] tmp = new Object[lst.size]; System.arraycopy(lst.childs, 0, tmp, 0, lst.size); children = tmp; } } /** * * @see org.apache.wicket.Component#internalMarkRendering() */ @Override void internalMarkRendering(boolean setRenderingFlag) { super.internalMarkRendering(setRenderingFlag); final int size = children_size(); for (int i = 0; i < size; i++) { final Component child = children_get(i); child.internalMarkRendering(setRenderingFlag); } } /** * @return a copy of the children array. */ private Component[] copyChildren() { int size = children_size(); Component result[] = new Component[size]; for (int i = 0; i < size; ++i) { result[i] = children_get(i); } return result; } /** * * @see org.apache.wicket.Component#onBeforeRenderChildren() */ @Override void onBeforeRenderChildren() { super.onBeforeRenderChildren(); // We need to copy the children list because the children components can // modify the hierarchy in their onBeforeRender. Component[] children = copyChildren(); try { // Loop through child components for (final Component child : children) { // 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() { public void component(Component component, IVisit visit) { component.clearEnabledInHierarchyCache(); } }); } @Override void onVisibleStateChanged() { super.onVisibleStateChanged(); visitChildren(new IVisitor() { public void component(Component component, IVisit visit) { component.clearVisibleInHierarchyCache(); } }); } @Override protected void onAfterRenderChildren() { for (Component child : this) { // set RENDERING_FLAG to false for auto-component's children (like Enclosure) child.markRendering(false); } super.onAfterRenderChildren(); } /** * @return True if this markup container has associated markup * @deprecated Use #getAssociatedMarkup() != null instead */ @Deprecated public boolean hasAssociatedMarkup() { return getAssociatedMarkup() != null; } /** * */ private static class ChildList extends AbstractList implements IClusterable { private static final long serialVersionUID = -7861580911447631127L; private int size; private Object[] childs; /** * Construct. * * @param children */ public ChildList(Object children) { if (children instanceof Object[]) { childs = (Object[])children; size = childs.length; } else { childs = new Object[3]; add(children); } } @Override public Object get(int index) { return childs[index]; } @Override public int size() { return size; } @Override public boolean add(Object o) { ensureCapacity(size + 1); childs[size++] = o; return true; } @Override public void add(int index, Object element) { if (index > size || index < 0) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } ensureCapacity(size + 1); System.arraycopy(childs, index, childs, index + 1, size - index); childs[index] = element; size++; } @Override public Object set(int index, Object element) { if (index >= size) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } Object oldValue = childs[index]; childs[index] = element; return oldValue; } @Override public Object remove(int index) { if (index >= size) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); } Object oldValue = childs[index]; int numMoved = size - index - 1; if (numMoved > 0) { System.arraycopy(childs, index + 1, childs, index, numMoved); } childs[--size] = null; // Let gc do its work return oldValue; } /** * @param minCapacity */ public void ensureCapacity(int minCapacity) { int oldCapacity = childs.length; if (minCapacity > oldCapacity) { Object oldData[] = childs; int newCapacity = oldCapacity * 2; if (newCapacity < minCapacity) { newCapacity = minCapacity; } childs = new Object[newCapacity]; System.arraycopy(oldData, 0, childs, 0, size); } } } /** * Swaps position of children. This method is particularly useful for adjusting positions of * repeater's items without rebuilding the component hierarchy * * @param idx1 * index of first component to be swapped * @param idx2 * index of second component to be swapped */ public final void swap(int idx1, int idx2) { int size = children_size(); if (idx1 < 0 || idx1 >= size) { throw new IndexOutOfBoundsException("Argument idx is out of bounds: " + idx1 + "<>[0," + size + ")"); } if (idx2 < 0 || idx2 >= size) { throw new IndexOutOfBoundsException("Argument idx is out of bounds: " + idx2 + "<>[0," + size + ")"); } if (idx1 == idx2) { return; } if (children instanceof Object[]) { final Object[] array = (Object[])children; Object tmp = array[idx1]; array[idx1] = array[idx2]; array[idx2] = tmp; } else { ChildList list = (ChildList)children; Object tmp = list.childs[idx1]; list.childs[idx1] = list.childs[idx2]; list.childs[idx2] = tmp; } } /** * @see org.apache.wicket.Component#onMarkupAttached() */ @Override protected void onMarkupAttached() { super.onMarkupAttached(); // createAndAddComponentsForWicketTags(); } /** * Automatically create components for tag. */ private void createAndAddComponentsForWicketTags() { // Markup must be available IMarkupFragment markup = getMarkup(); if ((markup != null) && (markup.size() > 1)) { MarkupStream stream = new MarkupStream(markup); // Skip the first component tag which already belongs to 'this' container if (stream.skipUntil(ComponentTag.class)) { stream.next(); } // Search for in the remaining markup and try to resolve the component while (stream.skipUntil(ComponentTag.class)) { ComponentTag tag = stream.getTag(); if (tag.isOpen() || tag.isOpenClose()) { if (tag instanceof WicketTag) { Component component = ComponentResolvers.resolve(this, stream, tag, null); if ((component != null) && (component.getParent() == null)) { if (component.getId().equals(tag.getId()) == false) { // make sure we are able to get() the component during rendering tag.setId(component.getId()); tag.setModified(true); } add(component); } } if (tag.isOpen()) { stream.skipToMatchingCloseTag(tag); } } stream.next(); } } } }