Package org.apache.wicket

Source Code of org.apache.wicket.MarkupContainer

/*
* 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.ArrayList;
import java.util.Arrays;
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.resolver.IComponentResolver;
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.
* <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 Component.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
{
  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)
  {
    super(id);
  }

  /**
   * @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.
   * <p>
   * Be careful when overriding this method, if not implemented properly it
   * may lead to a java component hierarchy which no longer matches the
   * template hierarchy, which in turn will lead to an error.
   *
   * @param child
   *            The child
   * @throws IllegalArgumentException
   *             Thrown if a child with the same id is replaced by the add
   *             operation.
   * @return This
   */
  public final MarkupContainer add(final Component child)
  {
    checkHierarchyChange(child);
   
    if (child == null)
    {
      throw new IllegalArgumentException("argument child may not be null");
    }

    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 child
   *            The child
   * @return This
   */
  public final MarkupContainer addOrReplace(final Component child)
  {
    checkHierarchyChange(child);
   
    if (child == null)
    {
      throw new IllegalArgumentException("argument child must be not null");
    }

    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
   * @return True, if component has been added
   */
  public final boolean autoAdd(final Component component)
  {
    if (component == null)
    {
      throw new IllegalArgumentException("argument component may not be null");
    }

    /* Replace strategy */
    component.setAuto(true);
    if (get(component.getId()) != null)
    {
      this.remove(component);
    }
    add(component);
    component.beforeRender();
    component.render();
    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
   */
  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);

    // If the container is transparent, than ask its parent.
    // ParentResolver does something quite similar, but because of <head>,
    // <body>, <wicket:panel> etc. it is quite common to have transparent
    // components. Hence, this is little short cut for a tiny performance
    // optimization.
    if ((child == null) && isTransparentResolver() && (getParent() != null))
    {
      // Special tags like "_body", "_panel" must implement
      // IComponentResolver
      // if they want to be transparent.
      if (path.startsWith("_") == false)
      {
        child = 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 final 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 (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()
  {
    throw new IllegalStateException(
        exceptionMessage("You cannot directly subclass Page or MarkupContainer.   Instead, subclass a markup-specific class, such as WebPage or WebMarkupContainer"));
  }

  /**
   * 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, BodyOnLoadContainer)
   * have to be transparent with respect to there child components. A
   * transparent container gets its children from its parent container.
   * <p>
   *
   * @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 final Iterator iterator()
  {
    return new Iterator()
    {
      int index = 0;

      public boolean hasNext()
      {
        return index < children_size();
      }

      public Object 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.EMPTY_LIST;
    }
    else
    {
      if (children instanceof Component)
      {
        sorted = new ArrayList(1);
        sorted.add(children);
      }
      else
      {
        sorted = Arrays.asList((Component[])children);
      }
    }
    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 containerClass
   * @return nothing
   * @throws always
   *             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.
   * <p>
   * 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 = MarkupContainer.this.children;

        public String toString()
        {
          return "RemoveAllChange[component: " + getPath() + ", removed Children: "
              + removedChildren + "]";
        }

        public void undo()
        {
          MarkupContainer.this.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++)
      {
        // Get next child
        final Component child = children_get(i);

        // Do not call remove() because the state change would than be
        // recorded twice.
        child.detachModel();
        child.setParent(null);
      }

      this.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
    final ComponentTag associatedMarkupOpenTag = associatedMarkupStream.getTag();

    // Check for required open tag name
    if (!((associatedMarkupOpenTag != null) && associatedMarkupOpenTag.isOpen() && (associatedMarkupOpenTag instanceof WicketTag)))
    {
      associatedMarkupStream.throwMarkupException(exceptionMessage);
    }

    try
    {
      setIgnoreAttributeModifier(true);
      renderComponentTag(associatedMarkupOpenTag);
      associatedMarkupStream.next();
     
      if (getApplication().getDebugSettings().isOutputMarkupContainerClassName())
      {
        getResponse().write("<!-- MARKUP FOR ");
        getResponse().write(getClass().getName());
        getResponse().write(" BEGIN -->");
      }
       
      renderComponentTagBody(associatedMarkupStream, associatedMarkupOpenTag);
     
      if (getApplication().getDebugSettings().isOutputMarkupContainerClassName())
      {
        getResponse().write("<!-- MARKUP FOR ");
        getResponse().write(getClass().getName());
        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 position of the associated markup remains the same
      child.markupIndex = replaced.markupIndex;

      // The generated markup id remains the same
      String replacedId = (replaced.hasMarkupIdMetaData()) ? replaced.getMarkupId() : null;
      child.setMarkupIdMetaData(replacedId);
    }

    return this;
  }

  /**
   * @see org.apache.wicket.Component#setModel(org.apache.wicket.model.IModel)
   */
  public Component setModel(final IModel model)
  {
    final IModel previous = this.model;
    super.setModel(model);
    if (previous instanceof IComponentInheritedModel)
    {
      visitChildren(new IVisitor()
      {
        public Object component(Component component)
        {
          IModel compModel = component.getModel();
          if (compModel instanceof IWrapModel)
          {
            compModel = ((IWrapModel)compModel).getWrappedModel();
          }
          if (compModel == previous)
          {
            component.setModel(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()
   */
  public String toString()
  {
    return toString(false);
  }

  /**
   * @param detailed
   *            True if a detailed string is desired
   * @return String representation of this container
   */
  public String toString(final boolean detailed)
  {
    final StringBuffer buffer = new StringBuffer();
    buffer.append("[MarkupContainer ");
    buffer.append(super.toString(true));
    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.
   *
   * @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
        value = visitor.component(child);

        // 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();
    if (page != null && page.isAttached())
    {
      // if page is not null and the page has already been attached,
      // attach the component. we only attach if the page has been
      // attached because at some point the page must be attached and the
      // call would cascade down anyways.
      component.attach();
    }

    final IDebugSettings debugSettings = Application.get().getDebugSettings();
    if (debugSettings.getComponentUseCheck())
    {
      component.setMetaData(ADDED_AT_KEY, Strings.toString(component, new MarkupException(
          "added")));
    }

    if (page != null)
    {
      page.componentAdded(component);
    }
  }

  /**
   * @param child
   *            Child to add
   */
  private final void children_add(final Component child)
  {
    if (this.children == null)
    {
      this.children = child;
    }
    else
    {
      // Get current list size
      final int size = children_size();

      // Create array that holds size + 1 elements
      final Component[] children = new Component[size + 1];

      // Loop through existing children copying them
      for (int i = 0; i < size; i++)
      {
        children[i] = children_get(i);
      }

      // Add new child to the end
      children[size] = child;

      // Save new children
      this.children = children;
    }
  }

  private final Component children_get(int index)
  {
    if (index == 0)
    {
      if (children instanceof Component)
      {
        return (Component)children;
      }
      else
      {
        return ((Component[])children)[index];
      }
    }
    else
    {
      return ((Component[])children)[index];
    }
  }

  private final Component children_get(final String id)
  {
    if (children instanceof Component)
    {
      final Component component = (Component)children;
      if (component.getId().equals(id))
      {
        return component;
      }
    }
    else
    {
      if (children != null)
      {
        final Component[] components = (Component[])children;
        for (int i = 0; i < components.length; i++)
        {
          if (components[i].getId().equals(id))
          {
            return components[i];
          }
        }
      }
    }
    return null;
  }

  private final int children_indexOf(Component child)
  {
    if (children instanceof Component)
    {
      if (((Component)children).getId().equals(child.getId()))
      {
        return 0;
      }
    }
    else
    {
      if (children != null)
      {
        final Component[] components = (Component[])children;
        for (int i = 0; i < components.length; i++)
        {
          if (components[i].getId().equals(child.getId()))
          {
            return i;
          }
        }
      }
    }
    return -1;
  }

  private final Component children_remove(Component component)
  {
    int index = children_indexOf(component);
    if (index != -1)
    {
      return children_remove(index);
    }
    return null;
  }

  private final Component children_remove(int index)
  {
    if (children instanceof Component)
    {
      if (index == 0)
      {
        final Component removed = (Component)children;
        this.children = null;
        return removed;
      }
      else
      {
        throw new IndexOutOfBoundsException();
      }
    }
    else
    {
      Component[] c = ((Component[])children);
      final Component removed = c[index];
      if (c.length == 2)
      {
        if (index == 0)
        {
          this.children = c[1];
        }
        else if (index == 1)
        {
          this.children = c[0];
        }
        else
        {
          throw new IndexOutOfBoundsException();
        }
      }
      else
      {
        Component[] newChildren = new Component[c.length - 1];
        int j = 0;
        for (int i = 0; i < c.length; i++)
        {
          if (i != index)
          {
            newChildren[j++] = c[i];
          }
        }
        this.children = newChildren;
      }
      return removed;
    }
  }

  private final Component children_set(int index, Component child)
  {
    final Component replaced;
    if (index < children_size())
    {
      if (children == null || children instanceof Component)
      {
        replaced = (Component)children;
        children = child;
      }
      else
      {
        final Component[] children = (Component[])this.children;
        replaced = children[index];
        children[index] = child;
      }
    }
    else
    {
      throw new IndexOutOfBoundsException();
    }
    return replaced;
  }

  private final int children_size()
  {
    if (children == null)
    {
      return 0;
    }
    else
    {
      if (children instanceof Component)
      {
        return 1;
      }
      return ((Component[])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 is removed
    component.setParent(null);
  }

  /**
   * Renders the next element of markup in the given markup stream.
   *
   * @param markupStream
   *            The markup stream
   */
  private final void renderNext(final MarkupStream markupStream)
  {
    // Get the current markup element
    final MarkupElement element = markupStream.get();

    // If it 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
      final Component component = get(id);

      // Failed to find it?
      if (component != null)
      {
        component.render(markupStream);
      }
      else
      {
        // 2rd try: Components like Border and Panel might implement
        // the ComponentResolver interface as well.
        MarkupContainer container = this;
        while (container != null)
        {
          if (container instanceof IComponentResolver)
          {
            if (((IComponentResolver)container).resolve(this, markupStream, tag))
            {
              return;
            }
          }

          container = container.findParent(MarkupContainer.class);
        }

        // 3rd try: Try application's component resolvers
        final List componentResolvers = this.getApplication().getPageSettings()
            .getComponentResolvers();
        final Iterator iterator = componentResolvers.iterator();
        while (iterator.hasNext())
        {
          final IComponentResolver resolver = (IComponentResolver)iterator.next();
          if (resolver.resolve(this, markupStream, tag))
          {
            return;
          }
        }

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

        // 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
   */
  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
   */
  protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag)
  {
    renderComponentTagBody(markupStream, openTag);
  }

  /**
   * Renders this component. This implementation just calls renderComponent.
   *
   * @param markupStream
   */
  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 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)
    {
      // 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
   */
  protected final void setMarkupStream(final MarkupStream markupStream)
  {
    this.markupStream = markupStream;
  }

  final void internalAttach2()
  {
    if (!getFlag(FLAG_ATTACHED))
    {
      setFlag(FLAG_ATTACHING, true);
      visitChildren(new IVisitor()
      {
        public Object component(Component component)
        {
          component.setFlag(FLAG_ATTACHING, true);
          return IVisitor.CONTINUE_TRAVERSAL;
        }
      });
      setFlag(FLAG_ATTACH_SUPER_CALL_VERIFIED, false);
      onAttach();
      if (!getFlag(FLAG_ATTACH_SUPER_CALL_VERIFIED))
      {
        throw new IllegalStateException("Component " + this + " of type " + getClass().getName() + " has not been properly attached.  "
            + "Something in its class hierarchy has failed to call super.onAttach() in an override of onAttach() method");
      }
     
      visitChildren(new IVisitor()
      {
        public Object component(Component component)
        {
          component.setFlag(FLAG_ATTACH_SUPER_CALL_VERIFIED, false);
          component.onAttach();
          if (!component.getFlag(FLAG_ATTACH_SUPER_CALL_VERIFIED))
          {
            throw new IllegalStateException("Component " + component + " of type " + component.getClass().getName() + " has not been properly attached.  "
                + "Something in its class hierarchy has failed to call super.onAttach() in an override of onAttach() method");
          }
          return IVisitor.CONTINUE_TRAVERSAL;
        }
      });
     
      visitChildren(new IVisitor()
      {
        public Object component(Component component)
        {
          component.setFlag(FLAG_ATTACHING, false);
          component.setFlag(FLAG_ATTACHED, true);
          return IVisitor.CONTINUE_TRAVERSAL;
        }
      });

      setFlag(FLAG_ATTACHING, false);
      setFlag(FLAG_ATTACHED, true);
    }
  }

  void detachChildren()
  {
    // Loop through child components
    final Iterator iter = iterator();
    while (iter.hasNext())
    {
      // Get next child
      final Component child = (Component)iter.next();

      // Call end request on the child
      child.detach();
    }
    super.detachChildren();
  }

  void onBeforeRenderChildren()
  {
    super.onBeforeRenderChildren();
    try
    {
      // 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);

        // Call begin request on the child
        child.beforeRender();
      }
    }
    catch (RuntimeException ex)
    {
      if (ex instanceof WicketRuntimeException)
        throw ex;
      else
        throw new WicketRuntimeException("Error attaching this container for rendering: "
            + this, ex);
    }
  }

  void onAfterRenderChildren()
  {
    // Loop through child components
    final Iterator iter = iterator();
    while (iter.hasNext())
    {
      // Get next child
      final Component child = (Component)iter.next();

      // Call end request on the child
      child.afterRender();
    }
    super.onAfterRenderChildren();
  }

  /**
   * @return True if this markup container has associated markup
   */
  final boolean hasAssociatedMarkup()
  {
    return getApplication().getMarkupSettings().getMarkupCache().hasAssociatedMarkup(this);
  }
}
TOP

Related Classes of org.apache.wicket.MarkupContainer

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.