org.apache.wicket.MarkupContainer Maven / Gradle / Ivy
Show all versions of org.ops4j.pax.wicket.service Show documentation
/*
* 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.MarkupElement;
import org.apache.wicket.markup.MarkupException;
import org.apache.wicket.markup.MarkupNotFoundException;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.WicketTag;
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.resource.IResourceStream;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.version.undo.Change;
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 Component.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
{
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;
/**
* The markup stream for this container. This variable is used only during the render phase to
* provide access to the current element within the stream.
*/
private transient MarkupStream markupStream;
/**
* @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 final MarkupContainer add(final Component... childs)
{
for (Component child : childs)
{
if (child == null)
{
throw new IllegalArgumentException("argument child may not be null");
}
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 final 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 AutoComponentResolver
* or 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, final MarkupStream markupStream)
{
if (component == null)
{
throw new IllegalArgumentException("argument component may not be null");
}
/* Replace strategy */
component.setAuto(true);
int index = children_indexOf(component);
if (index >= 0)
{
children_remove(index);
}
add(component);
component.prepareForRender();
try
{
if (markupStream == null)
{
component.render();
}
else
{
component.render(markupStream);
}
}
finally
{
component.afterRender();
}
return true;
}
/**
*
* @param component
* The component to add
* @return True, if component has been added
*
* @deprecated since 1.3 Please use {@link #autoAdd(Component, MarkupStream)} instead
*/
@Deprecated
public final boolean autoAdd(final Component component)
{
return autoAdd(component, null);
}
/**
* @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 final 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);
// If the container is transparent, than ask its parent.
// ParentResolver does something quite similar, but because of
,
// , etc. it is quite common to have transparent
// components. Hence, this is little short cut for a tiny performance
// optimization.
if ((child == null) && isTransparentResolver() && (container.getParent() != null))
{
child = container.getParent().get(path);
}
// Found child?
if (child != null)
{
final String path2 = Strings.afterFirstPathComponent(path, Component.PATH_SEPARATOR);
// Recurse on latter part of path
return child.get(path2);
}
return child;
}
/**
* 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)
{
try
{
return getApplication().getMarkupSettings()
.getMarkupCache()
.getMarkupStream(this, false, throwException);
}
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() + "' 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 stream set on this container.
*
* @return Returns the markup stream set on this container.
*/
public final MarkupStream getMarkupStream()
{
return markupStream;
}
/**
* 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.
*/
public String getMarkupType()
{
return getPage().getMarkupType();
}
/**
* Overrides {@link Component#initialize()} to call {@link Component#fireInitialize()} for
* itself and for all its children.
*
* @see org.apache.wicket.Component#fireInitialize()
*/
@Override
final void initialize()
{
super.fireInitialize();
visitChildren(new IVisitor()
{
public Object component(Component component)
{
component.fireInitialize();
return CONTINUE_TRAVERSAL;
}
});
}
/**
* 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);
}
/**
* Some MarkupContainers (e.g. HtmlHeaderContainer) have to be transparent with respect to their
* child components. A transparent container gets its children from its parent container.
*
*
* @see org.apache.wicket.markup.resolver.ParentResolver
*
* @return false. By default a MarkupContainer is not transparent.
*/
public boolean isTransparentResolver()
{
return false;
}
/**
* @return Iterator that iterates through children in the order they were added
*/
public Iterator extends Component> 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();
}
/**
* NOT USED ANYMORE; it's here for helping people migrate from Wicket 1.2 to Wicket 1.3
*
* @param
*
* @param containerClass
* @return nothing
* @throws IllegalStateException
* throws an {@link IllegalStateException}
*/
// TODO remove after release 1.3.0
public final IResourceStream newMarkupResourceStream(
Class containerClass)
{
throw new IllegalStateException(
"this method is not used any more (and shouldn't be called by clients anyway)");
}
/**
* @param component
* Component to remove from this container
*/
public void remove(final Component component)
{
checkHierarchyChange(component);
if (component == null)
{
throw new IllegalArgumentException("argument component may not be null");
}
children_remove(component);
removedComponent(component);
}
/**
* Removes the given component
*
* @param id
* The id of the component to remove
*/
public final void 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");
}
}
/**
* Removes all children from this container.
*
* Note: implementation does not call {@link MarkupContainer#remove(Component) } for each
* component.
*/
public final void removeAll()
{
if (children != null)
{
addStateChange(new Change()
{
private static final long serialVersionUID = 1L;
final Object removedChildren = children;
@Override
public String toString()
{
return "RemoveAllChange[component: " + getPath() + ", removed Children: " +
removedChildren + "]";
}
@Override
public void undo()
{
children = removedChildren;
int size = children_size();
for (int i = 0; i < size; i++)
{
// Get next child
final Component child = children_get(i);
child.setParent(MarkupContainer.this);
}
}
});
// 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;
}
}
/**
* Renders the entire associated markup stream 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 markup associated with Border or Panel component
final MarkupStream originalMarkupStream = getMarkupStream();
final MarkupStream associatedMarkupStream = getAssociatedMarkupStream(true);
// skip until the targetted tag is found
associatedMarkupStream.skipUntil(openTagName);
setMarkupStream(associatedMarkupStream);
// 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);
}
ComponentTag associatedMarkupOpenTag = (ComponentTag)elem;
// Check for required open tag name
if (!((associatedMarkupOpenTag != null) && associatedMarkupOpenTag.isOpen() && (associatedMarkupOpenTag instanceof WicketTag)))
{
associatedMarkupStream.throwMarkupException(exceptionMessage);
}
try
{
setIgnoreAttributeModifier(true);
renderComponentTag(associatedMarkupOpenTag);
associatedMarkupStream.next();
String className = null;
if (getApplication().getDebugSettings().isOutputMarkupContainerClassName())
{
Class> klass = getClass();
while (klass.isAnonymousClass())
{
klass = klass.getSuperclass();
}
className = klass.getName();
getResponse().write("");
}
renderComponentTagBody(associatedMarkupStream, associatedMarkupOpenTag);
if (getApplication().getDebugSettings().isOutputMarkupContainerClassName())
{
getResponse().write("");
}
renderClosingComponentTag(associatedMarkupStream, associatedMarkupOpenTag, false);
setMarkupStream(originalMarkupStream);
}
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 final 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);
// then add the other one.
addedComponent(child);
// The position of the associated markup remains the same
child.markupIndex = replaced.markupIndex;
// The generated markup id remains the same
child.setMarkupIdImpl(replaced.getMarkupIdImpl());
}
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 Object component(Component component)
{
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 IVisitor.CONTINUE_TRAVERSAL;
}
});
}
return this;
}
/**
* Get the number of children in this container.
*
* @return Number of children in this container
*/
public final 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 StringBuffer buffer = new StringBuffer();
buffer.append("[MarkupContainer ");
buffer.append(super.toString(detailed));
if (detailed)
{
if (getMarkupStream() != null)
{
buffer.append(", markupStream = " + getMarkupStream());
}
if (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 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 Object visitChildren(final Class> clazz,
final IVisitor visitor)
{
if (visitor == null)
{
throw new IllegalArgumentException("argument visitor may not be null");
}
// Iterate through children of this container
for (int i = 0; i < children_size(); i++)
{
// Get next child component
final Component child = children_get(i);
Object value = null;
// Is the child of the correct class (or was no class specified)?
if (clazz == null || clazz.isInstance(child))
{
// Call visitor
@SuppressWarnings("unchecked")
S s = (S)child;
value = visitor.component(s);
// If visitor returns a non-null value, it halts the traversal
if ((value != IVisitor.CONTINUE_TRAVERSAL) &&
(value != IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER))
{
return value;
}
}
// If child is a container
if ((child instanceof MarkupContainer) &&
(value != IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER))
{
// visit the children in the container
value = ((MarkupContainer)child).visitChildren(clazz, visitor);
// If visitor returns a non-null value, it halts the traversal
if ((value != IVisitor.CONTINUE_TRAVERSAL) &&
(value != IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER))
{
return value;
}
}
}
return null;
}
/**
* Traverses all child components in this container, calling the visitor's visit method at each
* one.
*
* @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 Object visitChildren(final IVisitor visitor)
{
return visitChildren(null, visitor);
}
/**
* @param component
* Component being added
*/
private final void addedComponent(final Component component)
{
// Check for degenerate case
if (component == this)
{
throw new IllegalArgumentException("Component can't be added to itself");
}
MarkupContainer parent = component.getParent();
if (parent != null)
{
parent.remove(component);
}
// Set child's parent
component.setParent(this);
final Page page = findPage();
final IDebugSettings debugSettings = Application.get().getDebugSettings();
if (debugSettings.isLinePreciseReportingOnAddComponentEnabled())
{
component.setMetaData(ADDED_AT_KEY,
Strings.toString(component, new MarkupException("added")));
}
if (page != null)
{
page.componentAdded(component);
}
if (page != null)
{
component.initialize();
}
// 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())
{
component.beforeRender();
}
}
/**
* @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 = null;
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 = null;
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
*/
protected final void renderNext(final MarkupStream markupStream)
{
// Get the current markup element
final MarkupElement element = markupStream.get();
// If it 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
final Component component = get(id);
// Failed to find it?
if (component != null)
{
component.render(markupStream);
}
else
{
if (ComponentResolvers.resolve(getApplication(), this, markupStream, tag))
{
return;
}
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.");
}
}
// No one was able to handle the component id
markupStream.throwMarkupException("Unable to find component with id '" + id +
"' in " + this + ". This means 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.");
}
}
else
{
// Render as raw markup
if (log.isDebugEnabled())
{
log.debug("Rendering raw markup");
}
getResponse().write(element.toCharSequence());
markupStream.next();
}
}
/**
* Get the markup stream for this component.
*
* @return The markup stream for this component, or if it doesn't have one, the markup stream
* for the nearest parent which does have one
*/
@Override
protected final MarkupStream findMarkupStream()
{
// Start here
MarkupContainer c = this;
// Walk up hierarchy until markup found
while (c.getMarkupStream() == null)
{
// Check parent
c = c.getParent();
// Are we at the top of the hierarchy?
if (c == null)
{
// Failed to find markup stream
throw new WicketRuntimeException(exceptionMessage("No markup found"));
}
}
return c.getMarkupStream();
}
/**
* 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
protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag)
{
renderComponentTagBody(markupStream, openTag);
}
/**
* Renders this component. This implementation just calls renderComponent.
*
* @param markupStream
*/
@Override
protected void onRender(final MarkupStream markupStream)
{
renderComponent(markupStream);
}
/**
* Renders this component and all sub-components using the given markup stream.
*
* @param markupStream
* The markup stream
*/
protected void renderAll(final MarkupStream markupStream)
{
// Loop through the markup in this container
while (markupStream.hasMore())
{
// Element rendering is responsible for advancing markup stream!
final int index = markupStream.getCurrentIndex();
renderNext(markupStream);
if (index == markupStream.getCurrentIndex())
{
markupStream.throwMarkupException("Component at markup stream index " + index +
" failed to advance the markup stream");
}
}
}
/**
* 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
*/
protected 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 historically renderComponentTagBody gets called, but actually
// it shouldn't do anything since there is no body for that tag.
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)
{
// Loop through the markup in this container
while (markupStream.hasMore() && !markupStream.get().closes(openTag))
{
// Render markup element. Doing so must advance the markup
// stream
final int index = markupStream.getCurrentIndex();
renderNext(markupStream);
if (index == markupStream.getCurrentIndex())
{
markupStream.throwMarkupException("Markup element at index " + index +
" failed to advance the markup stream");
}
}
}
}
/**
* Set markup stream for this container.
*
* @param markupStream
* The markup stream
*/
@Override
protected final void setMarkupStream(final MarkupStream markupStream)
{
this.markupStream = markupStream;
}
/**
*
*/
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();
}
}
}
/**
*
* @see org.apache.wicket.Component#detachChildren()
*/
@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 (int i = 0; i < children.length; i++)
{
// Get next child
final Component child = children[i];
// 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 Object component(Component component)
{
component.clearEnabledInHierarchyCache();
return CONTINUE_TRAVERSAL;
}
});
}
/**
*
* @see org.apache.wicket.Component#onAfterRenderChildren()
*/
@Override
protected void onAfterRenderChildren()
{
// Loop through child components
final Iterator extends Component> iter = iterator();
while (iter.hasNext())
{
// Get next child
final Component child = iter.next();
// Call end request on the child
child.afterRender();
}
super.onAfterRenderChildren();
}
/**
* @return True if this markup container has associated markup
*/
public boolean hasAssociatedMarkup()
{
return getApplication().getMarkupSettings().getMarkupCache().hasAssociatedMarkup(this);
}
/**
* @see org.apache.wicket.Component#setRenderAllowed()
*/
@Override
void setRenderAllowed()
{
super.setRenderAllowed();
visitChildren(new IVisitor()
{
public Object component(final Component component)
{
// Find out if this component can be rendered
final boolean renderAllowed = component.isActionAuthorized(RENDER);
// Authorize rendering
component.setRenderAllowed(renderAllowed);
return IVisitor.CONTINUE_TRAVERSAL;
}
});
}
/**
*
*/
private static class ChildList extends AbstractList