Package org.apache.wicket

Source Code of org.apache.wicket.MarkupContainer$ChildList

/*
* 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.RawMarkup;
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.IDebugSettings;
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.
* <ul>
* <li><b>Children </b>- 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.
*
* <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.
* <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
* 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 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
* 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<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 MarkupContainer add(final Component... childs)
  {
    for (Component child : childs)
    {
      if (child == null)
      {
        throw new IllegalArgumentException("argument child may not be null");
      }

      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"));
      }

      // Check if the markup is available after the child has been added to the parent
      try
      {
        // If not yet triggered, than do now (e.g. Pages)
        if (getMarkup() != null)
        {
          internalOnMarkupAttached();
        }

        if (child.getMarkup() != null)
        {
          child.internalOnMarkupAttached();

          // Tell all children of "component" as well
          if (child instanceof MarkupContainer)
          {
            MarkupContainer container = (MarkupContainer)child;
            container.visitChildren(new IVisitor<Component, Void>()
            {
              public void component(final Component component,
                final IVisit<Void> visit)
              {
                if (component.internalOnMarkupAttached())
                {
                  visit.dontGoDeeper();
                }
              }
            });
          }
        }
      }
      catch (WicketRuntimeException exception)
      {
        // ignore
      }
    }
    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.
   * <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)
  {
    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 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(final String path)
  {
    // Reference to this container
    if (path == null || path.trim().equals(""))
    {
      return this;
    }

    // Get child's id, if any
    final String id = Strings.firstPathComponent(path, Component.PATH_SEPARATOR);

    // Get child by id
    Component child = 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
   */
  public IMarkupFragment getAssociatedMarkup()
  {
    try
    {
      IMarkupFragment 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 stream set on this container.
   *
   * @return Returns the markup stream set on this container.
   */
  public final MarkupStream getMarkupStream()
  {
    return markupStream;
  }

  /**
   * 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)
  {
    // Get the markup for the container
    IMarkupFragment markup = getMarkup();
    if (markup == null)
    {
      return null;
    }

    if (child == null)
    {
      return markup;
    }

    // Find the child's markup
    markup = markup.find(child.getId());
    if (markup != null)
    {
      return markup;
    }

    // This is to make migration for Items from 1.4 to 1.5 more easy
    if (Character.isDigit(child.getId().charAt(0)))
    {
      String id = child.getId();
      boolean miss = false;
      for (int i = 1; i < id.length(); i++)
      {
        if (Character.isDigit(id.charAt(i)) == false)
        {
          miss = true;
          break;
        }
      }

      if (miss == false)
      {
        // The LoopItems markup is equal to the Loops markup
        markup = getMarkup();

        if (log.isWarnEnabled())
        {
          log.warn("1.4 to 1.5 migration issue: your item component should be derived from AbstractItem. Item=" +
            child.toString());
        }
      }
    }

    return markup;
  }

  /**
   * 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 MarkupType getMarkupType()
  {
    return getPage().getMarkupType();
  }

  /**
   * 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<Component> iterator()
  {
    return new Iterator<Component>()
    {
      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<Component> iterator(Comparator<Component> comparator)
  {
    final List<Component> sorted;
    if (children == null)
    {
      sorted = Collections.emptyList();
    }
    else
    {
      if (children instanceof Component)
      {
        sorted = new ArrayList<Component>(1);
        sorted.add((Component)children);
      }
      else
      {
        int size = children_size();
        sorted = new ArrayList<Component>(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
   */
  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.
   * <p>
   * Note: implementation does not call {@link MarkupContainer#remove(Component) } for each
   * component.
   */
  public final void 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.detachModel();
          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 = new MarkupStream(getMarkup(null));

    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);
    }

    // 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;

      if (getApplication().getDebugSettings().isOutputMarkupContainerClassName())
      {
        Class<?> klass = getClass();
        while (klass.isAnonymousClass())
        {
          klass = klass.getSuperclass();
        }
        className = klass.getName();
        getResponse().write("<!-- MARKUP FOR ");
        getResponse().write(className);
        getResponse().write(" BEGIN -->");
      }

      renderComponentTagBody(associatedMarkupStream, associatedMarkupOpenTag);

      if (getApplication().getDebugSettings().isOutputMarkupContainerClassName())
      {
        getResponse().write("<!-- MARKUP FOR ");
        getResponse().write(className);
        getResponse().write(" END -->");
      }

      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 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<Component, Void>()
      {
        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 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("[" + this.getClass().getSimpleName() + " ");
    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 <S>
   *            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 <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 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 final void addedComponent(final Component child)
  {
    // Check for degenerate case
    if (child == this)
    {
      throw new IllegalArgumentException("Component can't be added to itself");
    }

    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)
    {
      child.initialize();

      // Tell the page a component has been added
      page.componentAdded(child);
    }

    // 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();
    }
  }

  /**
   * 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<Component, Void>()
    {
      public void component(final Component component, final IVisit<Void> 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 <code>reconstruct</code>
   * 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's a tag like <wicket..> or <span wicket:id="..." >
    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);
        }
      }

      // Failed to find it?
      if (component != null)
      {
        component.render();
      }
      else
      {
        if (tag instanceof WicketTag)
        {
          if (((WicketTag)tag).isChildTag())
          {
            markupStream.throwMarkupException("Found " + tag.toString() +
              " but no <wicket:extend>");
          }
          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());
    }
  }

  /**
   * 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);
  }

  /**
   * @see org.apache.wicket.Component#onRender()
   */
  @Override
  protected void onRender()
  {
    renderComponent();
  }

  /**
   * 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 <p> 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
      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 (elem instanceof RawMarkup)
      {
        markupStream.next();
      }
      else if (!markupStream.getTag().isClose())
      {
        markupStream.skipComponent();
      }
      else
      {
        throw new WicketRuntimeException("Ups. This should never happen. " +
          markupStream.toString());
      }
    }
  }

  /**
   * 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();

        if (component.isAuto())
        {
          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);
      }
    }
  }

  /**
   *
   * @see org.apache.wicket.Component#onAfterRenderChildren()
   */
  @Override
  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 MarkupFactory.get().hasAssociatedMarkup(this);
  }

  /**
   * @see org.apache.wicket.Component#setRenderAllowed()
   */
  @Override
  void setRenderAllowed()
  {
    super.setRenderAllowed();

    visitChildren(new IVisitor<Component, Void>()
    {
      public void component(final Component component, final IVisit<Void> visit)
      {
        // Find out if this component can be rendered
        final boolean renderAllowed = component.isActionAuthorized(RENDER);
        // Authorize rendering
        component.setRenderAllowed(renderAllowed);
      }
    });
  }

  /**
   *
   */
  private static class ChildList extends AbstractList<Object> 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 <wicket:xxx> 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 <wicket:xxx> 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();
      }
    }
  }

}
TOP

Related Classes of org.apache.wicket.MarkupContainer$ChildList

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
.js','ga'); ga('create', 'UA-20639858-1', 'auto'); ga('send', 'pageview');