0001: /*
0002: * $Id: MarkupContainer.java 4965 2006-03-16 11:21:07 -0800 (Thu, 16 Mar 2006)
0003: * ivaynberg $ $Revision: 461664 $ $Date: 2006-03-16 11:21:07 -0800 (Thu, 16 Mar
0004: * 2006) $
0005: *
0006: * ==============================================================================
0007: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0008: * use this file except in compliance with the License. You may obtain a copy of
0009: * the License at
0010: *
0011: * http://www.apache.org/licenses/LICENSE-2.0
0012: *
0013: * Unless required by applicable law or agreed to in writing, software
0014: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0015: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0016: * License for the specific language governing permissions and limitations under
0017: * the License.
0018: */
0019: package wicket;
0020:
0021: import java.util.ArrayList;
0022: import java.util.Arrays;
0023: import java.util.Collections;
0024: import java.util.Comparator;
0025: import java.util.Iterator;
0026: import java.util.List;
0027:
0028: import org.apache.commons.logging.Log;
0029: import org.apache.commons.logging.LogFactory;
0030:
0031: import wicket.feedback.IFeedback;
0032: import wicket.markup.ComponentTag;
0033: import wicket.markup.ContainerInfo;
0034: import wicket.markup.MarkupElement;
0035: import wicket.markup.MarkupException;
0036: import wicket.markup.MarkupNotFoundException;
0037: import wicket.markup.MarkupResourceStream;
0038: import wicket.markup.MarkupStream;
0039: import wicket.markup.WicketTag;
0040: import wicket.markup.resolver.IComponentResolver;
0041: import wicket.model.ICompoundModel;
0042: import wicket.model.IModel;
0043: import wicket.util.resource.IResourceStream;
0044: import wicket.util.resource.locator.IResourceStreamLocator;
0045: import wicket.util.string.Strings;
0046: import wicket.version.undo.Change;
0047:
0048: /**
0049: * A MarkupContainer holds a map of child components.
0050: * <ul>
0051: * <li><b>Children </b>- Children can be added by calling the add() method, and
0052: * they can be looked up using a dotted path. For example, if a container called
0053: * "a" held a nested container "b" which held a nested component "c", then
0054: * a.get("b.c") would return the Component with id "c". The number of children
0055: * in a MarkupContainer can be determined by calling size(), and the whole
0056: * hierarchy of children held by a MarkupContainer can be traversed by calling
0057: * visitChildren(), passing in an implementation of Component.IVisitor.
0058: *
0059: * <li><b>Markup Rendering </b>- A MarkupContainer also holds/references
0060: * associated markup which is used to render the container. As the markup stream
0061: * for a container is rendered, component references in the markup are resolved
0062: * by using the container to look up Components in the container's component map
0063: * by id. Each component referenced by the markup stream is given an opportunity
0064: * to render itself using the markup stream.
0065: * <p>
0066: * Components may alter their referring tag, replace the tag's body or insert
0067: * markup after the tag. But components cannot remove tags from the markup
0068: * stream. This is an important guarantee because graphic designers may be
0069: * setting attributes on component tags that affect visual presentation.
0070: * <p>
0071: * The type of markup held in a given container subclass can be determined by
0072: * calling getMarkupType(). Markup is accessed via a MarkupStream object which
0073: * allows a component to traverse ComponentTag and RawMarkup MarkupElements
0074: * while rendering a response. Markup in the stream may be HTML or some other
0075: * kind of markup, such as VXML, as determined by the specific container
0076: * subclass.
0077: * <p>
0078: * A markup stream may be directly associated with a container via
0079: * setMarkupStream. However, a container which does not have a markup stream
0080: * (its getMarkupStream() returns null) may inherit a markup stream from a
0081: * container above it in the component hierarchy. The findMarkupStream() method
0082: * will locate the first container at or above this container which has a markup
0083: * stream.
0084: * <p>
0085: * All Page containers set a markup stream before rendering by calling the
0086: * method getAssociatedMarkupStream() to load the markup associated with the
0087: * page. Since Page is at the top of the container hierarchy, it is guaranteed
0088: * that findMarkupStream will always return a valid markup stream.
0089: *
0090: * @see MarkupStream
0091: * @author Jonathan Locke
0092: */
0093: public abstract class MarkupContainer extends Component {
0094: private static final long serialVersionUID = 1L;
0095:
0096: /** Log for reporting. */
0097: private static final Log log = LogFactory
0098: .getLog(MarkupContainer.class);
0099:
0100: /** List of children or single child */
0101: private Object children;
0102:
0103: /**
0104: * The markup stream for this container. This variable is used only during
0105: * the render phase to provide access to the current element within the
0106: * stream.
0107: */
0108: private transient MarkupStream markupStream;
0109:
0110: /**
0111: * @see wicket.Component#Component(String)
0112: */
0113: public MarkupContainer(final String id) {
0114: super (id);
0115: }
0116:
0117: /**
0118: * @see wicket.Component#Component(String, IModel)
0119: */
0120: public MarkupContainer(final String id, IModel model) {
0121: super (id, model);
0122: }
0123:
0124: /**
0125: * Adds a child component to this container.
0126: * <p>
0127: * Be careful when overriding this method, if not implemented properly it
0128: * may lead to a java component hierarchy which no longer matches the
0129: * template hierarchy, which in turn will lead to an error.
0130: *
0131: * @param child
0132: * The child
0133: * @throws IllegalArgumentException
0134: * Thrown if a child with the same id is replaced by the add
0135: * operation.
0136: * @return This
0137: */
0138: public final MarkupContainer add(final Component child) {
0139: if (child == null) {
0140: throw new IllegalArgumentException(
0141: "argument child may not be null");
0142: }
0143:
0144: if (log.isDebugEnabled()) {
0145: log.debug("Add " + child.getId() + " to " + this );
0146: }
0147:
0148: // Add to map
0149: addedComponent(child);
0150: if (put(child) != null) {
0151: throw new IllegalArgumentException(
0152: exceptionMessage("A child with id '"
0153: + child.getId() + "' already exists"));
0154: }
0155:
0156: return this ;
0157: }
0158:
0159: /**
0160: * This method allows a component to be added by an auto-resolver such as
0161: * AutoComponentResolver or AutoLinkResolver. While the component is being
0162: * added, the component's FLAG_AUTO boolean is set. The isAuto() method of
0163: * Component returns true if a component or any of its parents has this bit
0164: * set. When a component is added via autoAdd(), the logic in Page that
0165: * normally (a) checks for modifications during the rendering process, and
0166: * (b) versions components, is bypassed if Component.isAuto() returns true.
0167: * <p>
0168: * The result of all this is that components added with autoAdd() are free
0169: * from versioning and can add their own children without the usual
0170: * exception that would normally be thrown when the component hierarchy is
0171: * modified during rendering.
0172: *
0173: * @param component
0174: * The component to add
0175: * @return True, if component has been added
0176: */
0177: public final boolean autoAdd(final Component component) {
0178: if (component == null) {
0179: throw new IllegalArgumentException(
0180: "argument component may not be null");
0181: }
0182:
0183: /* Replace strategy */
0184: if (get(component.getId()) != null) {
0185: this .remove(component);
0186: }
0187: component.setAuto(true);
0188: add(component);
0189: component.internalAttach();
0190: component.render();
0191: return true;
0192: }
0193:
0194: /**
0195: * @param component
0196: * The component to check
0197: * @param recurse
0198: * True if all descendents should be considered
0199: * @return True if the component is contained in this container
0200: */
0201: public final boolean contains(final Component component,
0202: final boolean recurse) {
0203: if (component == null) {
0204: throw new IllegalArgumentException(
0205: "argument component may not be null");
0206: }
0207:
0208: if (recurse) {
0209: // Start at component and continue while we're not out of parents
0210: for (Component current = component; current != null;) {
0211: // Get parent
0212: final MarkupContainer parent = current.getParent();
0213:
0214: // If this container is the parent, then the component is
0215: // recursively contained by this container
0216: if (parent == this ) {
0217: // Found it!
0218: return true;
0219: }
0220:
0221: // Move up the chain to the next parent
0222: current = parent;
0223: }
0224:
0225: // Failed to find this container in component's ancestry
0226: return false;
0227: } else {
0228: // Is the component contained in this container?
0229: return component.getParent() == this ;
0230: }
0231: }
0232:
0233: /**
0234: * Get a child component by looking it up with the given path.
0235: *
0236: * @param path
0237: * Path to component
0238: * @return The component at the path
0239: */
0240: public final Component get(final String path) {
0241: // Reference to this container
0242: if (path == null || path.trim().equals("")) {
0243: return this ;
0244: }
0245:
0246: // Get child's id, if any
0247: final String id = Strings.firstPathComponent(path,
0248: Component.PATH_SEPARATOR);
0249:
0250: // Get child by id
0251: Component child = children_get(id);
0252:
0253: // If the container is transparent, than ask its parent.
0254: // ParentResolver does something quite similar, but because of <head>,
0255: // <body>, <wicket:panel> etc. it is quite common to have transparent
0256: // components. Hence, this is little short cut for a tiny performance
0257: // optimization.
0258: if ((child == null) && isTransparentResolver()
0259: && (getParent() != null)) {
0260: // Special tags like "_body", "_panel" must implement IComponentResolver
0261: // if they want to be transparent.
0262: if (path.startsWith("_") == false) {
0263: child = getParent().get(path);
0264: }
0265: }
0266:
0267: // Found child?
0268: final String path2 = Strings.afterFirstPathComponent(path,
0269: Component.PATH_SEPARATOR);
0270: if (child != null) {
0271: // Recurse on latter part of path
0272: return child.get(path2);
0273: }
0274:
0275: return child;
0276: }
0277:
0278: /**
0279: * Get the type of associated markup for this component.
0280: *
0281: * @return The type of associated markup for this component (for example,
0282: * "html", "wml" or "vxml"). The markup type for a component is
0283: * independent of whether or not the component actually has an
0284: * associated markup resource file (which is determined at runtime).
0285: * If there is no markup type for a component, null may be returned,
0286: * but this means that no markup can be loaded for the class.
0287: */
0288: public String getMarkupType() {
0289: throw new IllegalStateException(
0290: exceptionMessage("You cannot directly subclass Page or MarkupContainer. Instead, subclass a markup-specific class, such as WebPage or WebMarkupContainer"));
0291: }
0292:
0293: /**
0294: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT.
0295: *
0296: * Adds a child component to this container.
0297: *
0298: * @param child
0299: * The child
0300: * @throws IllegalArgumentException
0301: * Thrown if a child with the same id is replaced by the add
0302: * operation.
0303: */
0304: public void internalAdd(final Component child) {
0305: if (log.isDebugEnabled()) {
0306: log.debug("internalAdd " + child.getId() + " to " + this );
0307: }
0308:
0309: // Add to map
0310: addedComponent(child);
0311: put(child);
0312: }
0313:
0314: /**
0315: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR
0316: * OVERRIDE.
0317: *
0318: * Called when a request begins.
0319: */
0320: public void internalAttach() {
0321: // Handle begin request for the container itself
0322: try {
0323: super .internalAttach();
0324:
0325: // Loop through child components
0326: final int size = children_size();
0327: for (int i = 0; i < size; i++) {
0328: // Get next child
0329: final Component child = children_get(i);
0330:
0331: // Ignore feedback as that was done in Page
0332: if (!(child instanceof IFeedback)) {
0333: // Call begin request on the child
0334: child.internalAttach();
0335: }
0336: }
0337: } catch (RuntimeException ex) {
0338: if (ex instanceof WicketRuntimeException)
0339: throw ex;
0340: else
0341: throw new WicketRuntimeException(
0342: "Error attaching this container for rendering: "
0343: + this , ex);
0344: }
0345: }
0346:
0347: /**
0348: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR
0349: * OVERRIDE.
0350: *
0351: * Called when a request ends.
0352: */
0353: public void internalDetach() {
0354: // Handle end request for the container itself
0355: super .internalDetach();
0356:
0357: // Loop through child components
0358: final Iterator iter = iterator();
0359: while (iter.hasNext()) {
0360: // Get next child
0361: final Component child = (Component) iter.next();
0362:
0363: // Call end request on the child
0364: child.internalDetach();
0365: }
0366: }
0367:
0368: /**
0369: * @return Iterator that iterates through children in the order they were
0370: * added
0371: */
0372: public final Iterator iterator() {
0373: return new Iterator() {
0374: int index = 0;
0375:
0376: public boolean hasNext() {
0377: return index < children_size();
0378: }
0379:
0380: public Object next() {
0381: return children_get(index++);
0382: }
0383:
0384: public void remove() {
0385: removedComponent(children_remove(--index));
0386: }
0387: };
0388: }
0389:
0390: /**
0391: * @param comparator
0392: * The comparator
0393: * @return Iterator that iterates over children in the order specified by
0394: * comparator
0395: */
0396: public final Iterator iterator(Comparator comparator) {
0397: final List sorted;
0398: if (children == null) {
0399: sorted = Collections.EMPTY_LIST;
0400: } else {
0401: if (children instanceof Component) {
0402: sorted = new ArrayList(1);
0403: sorted.add(children);
0404: } else {
0405: sorted = Arrays.asList((Component[]) children);
0406: }
0407: }
0408: Collections.sort(sorted, comparator);
0409: return sorted.iterator();
0410: }
0411:
0412: /**
0413: * @param component
0414: * Component to remove from this container
0415: */
0416: public void remove(final Component component) {
0417: if (component == null) {
0418: throw new IllegalArgumentException(
0419: "argument component may not be null");
0420: }
0421:
0422: children_remove(component);
0423: removedComponent(component);
0424: }
0425:
0426: /**
0427: * Removes the given component
0428: *
0429: * @param id
0430: * The id of the component to remove
0431: */
0432: public final void remove(final String id) {
0433: if (id == null) {
0434: throw new IllegalArgumentException(
0435: "argument id may not be null");
0436: }
0437:
0438: final Component component = get(id);
0439: if (component != null) {
0440: remove(component);
0441: } else {
0442: throw new WicketRuntimeException(
0443: "Unable to find a component with id '" + id
0444: + "' to remove");
0445: }
0446: }
0447:
0448: /**
0449: * Removes all children from this container.
0450: * <p>
0451: * Note: implementation does not call
0452: * {@link MarkupContainer#remove(Component) } for each component.
0453: */
0454: public final void removeAll() {
0455: if (children != null) {
0456: addStateChange(new Change() {
0457: private static final long serialVersionUID = 1L;
0458:
0459: final Object removedChildren = MarkupContainer.this .children;
0460:
0461: public void undo() {
0462: MarkupContainer.this .children = removedChildren;
0463: int size = children_size();
0464: for (int i = 0; i < size; i++) {
0465: // Get next child
0466: final Component child = children_get(i);
0467: child.setParent(MarkupContainer.this );
0468: }
0469: }
0470:
0471: public String toString() {
0472: return "RemoveAllChange[component: " + getPath()
0473: + ", removed Children: " + removedChildren
0474: + "]";
0475: }
0476: });
0477:
0478: // Loop through child components
0479: int size = children_size();
0480: for (int i = 0; i < size; i++) {
0481: // Get next child
0482: final Component child = children_get(i);
0483:
0484: // Do not call remove() because the state change would than be
0485: // recorded twice.
0486: child.detachModel();
0487: child.setParent(null);
0488: }
0489:
0490: this .children = null;
0491: }
0492: }
0493:
0494: /**
0495: * Renders the entire associated markup stream for a container such as a
0496: * Border or Panel. Any leading or trailing raw markup in the associated
0497: * markup is skipped.
0498: *
0499: * @param openTagName
0500: * the tag to render the associated markup for
0501: * @param exceptionMessage
0502: * message that will be used for exceptions
0503: */
0504: public final void renderAssociatedMarkup(final String openTagName,
0505: final String exceptionMessage) {
0506: // Get markup associated with Border or Panel component
0507: final MarkupStream originalMarkupStream = getMarkupStream();
0508: final MarkupStream associatedMarkupStream = getAssociatedMarkupStream(true);
0509:
0510: // skip until the targetted tag is found
0511: associatedMarkupStream.skipUntil(openTagName);
0512: setMarkupStream(associatedMarkupStream);
0513:
0514: // Get open tag in associated markup of border component
0515: final ComponentTag associatedMarkupOpenTag = associatedMarkupStream
0516: .getTag();
0517:
0518: // Check for required open tag name
0519: if (!((associatedMarkupOpenTag != null)
0520: && associatedMarkupOpenTag.isOpen() && (associatedMarkupOpenTag instanceof WicketTag))) {
0521: associatedMarkupStream
0522: .throwMarkupException(exceptionMessage);
0523: }
0524:
0525: try {
0526: setIgnoreAttributeModifier(true);
0527: renderComponentTag(associatedMarkupOpenTag);
0528: associatedMarkupStream.next();
0529: renderComponentTagBody(associatedMarkupStream,
0530: associatedMarkupOpenTag);
0531: renderClosingComponentTag(associatedMarkupStream,
0532: associatedMarkupOpenTag, false);
0533: setMarkupStream(originalMarkupStream);
0534: } finally {
0535: setIgnoreAttributeModifier(false);
0536: }
0537: }
0538:
0539: /**
0540: * Replaces a child component of this container with another
0541: *
0542: * @param child
0543: * The child
0544: * @throws IllegalArgumentException
0545: * Thrown if there was no child with the same id.
0546: * @return This
0547: */
0548: public final MarkupContainer replace(final Component child) {
0549: if (child == null) {
0550: throw new IllegalArgumentException(
0551: "argument child must be not null");
0552: }
0553:
0554: if (log.isDebugEnabled()) {
0555: log.debug("Replacing " + child.getId() + " in " + this );
0556: }
0557:
0558: if (child.getParent() != this ) {
0559: // Add to map
0560: final Component replaced = put(child);
0561:
0562: // Look up to make sure it was already in the map
0563: if (replaced == null) {
0564: throw new WicketRuntimeException(
0565: exceptionMessage("Cannot replace a component which has not been added: id='"
0566: + child.getId()
0567: + "', component="
0568: + child));
0569: }
0570:
0571: // first remove the component.
0572: removedComponent(replaced);
0573: // then add the other one.
0574: addedComponent(child);
0575:
0576: // The position of the associated markup remains the same
0577: child.markupIndex = replaced.markupIndex;
0578: }
0579:
0580: return this ;
0581: }
0582:
0583: /**
0584: * @see wicket.Component#setModel(wicket.model.IModel)
0585: */
0586: public Component setModel(final IModel model) {
0587: final IModel previous = getModel();
0588: super .setModel(model);
0589: if (previous instanceof ICompoundModel) {
0590: visitChildren(new IVisitor() {
0591:
0592: public Object component(Component component) {
0593: IModel compModel = component.getModel();
0594: if (compModel == previous) {
0595: component.setModel(null);
0596: } else if (compModel == model) {
0597: component.modelChanged();
0598: }
0599: return IVisitor.CONTINUE_TRAVERSAL;
0600: }
0601:
0602: });
0603: }
0604: return this ;
0605: }
0606:
0607: /**
0608: * Get the number of children in this container.
0609: *
0610: * @return Number of children in this container
0611: */
0612: public final int size() {
0613: return children_size();
0614: }
0615:
0616: /**
0617: * @see wicket.Component#toString()
0618: */
0619: public String toString() {
0620: return toString(false);
0621: }
0622:
0623: /**
0624: * @param detailed
0625: * True if a detailed string is desired
0626: * @return String representation of this container
0627: */
0628: public String toString(final boolean detailed) {
0629: final StringBuffer buffer = new StringBuffer();
0630: buffer.append("[MarkupContainer ");
0631: buffer.append(super .toString(true));
0632: if (detailed) {
0633: if (getMarkupStream() != null) {
0634: buffer.append(", markupStream = " + getMarkupStream());
0635: }
0636:
0637: if (children_size() != 0) {
0638: buffer.append(", children = ");
0639:
0640: // Loop through child components
0641: final int size = children_size();
0642: for (int i = 0; i < size; i++) {
0643: // Get next child
0644: final Component child = children_get(i);
0645: if (i != 0) {
0646: buffer.append(' ');
0647: }
0648: buffer.append(child.toString());
0649: }
0650: }
0651: }
0652: buffer.append(']');
0653: return buffer.toString();
0654: }
0655:
0656: /**
0657: * Traverses all child components of the given class in this container,
0658: * calling the visitor's visit method at each one.
0659: *
0660: * @param clazz
0661: * The class of child to visit, or null to visit all children
0662: * @param visitor
0663: * The visitor to call back to
0664: * @return The return value from a visitor which halted the traversal, or
0665: * null if the entire traversal occurred
0666: */
0667: public final Object visitChildren(final Class clazz,
0668: final IVisitor visitor) {
0669: if (visitor == null) {
0670: throw new IllegalArgumentException(
0671: "argument visitor may not be null");
0672: }
0673:
0674: // Iterate through children of this container
0675: for (int i = 0; i < children_size(); i++) {
0676: // Get next child component
0677: final Component child = children_get(i);
0678: Object value = null;
0679:
0680: // Is the child of the correct class (or was no class specified)?
0681: if (clazz == null || clazz.isInstance(child)) {
0682: // Call visitor
0683: value = visitor.component(child);
0684:
0685: // If visitor returns a non-null value, it halts the traversal
0686: if ((value != IVisitor.CONTINUE_TRAVERSAL)
0687: && (value != IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER)) {
0688: return value;
0689: }
0690: }
0691:
0692: // If child is a container
0693: if ((child instanceof MarkupContainer)
0694: && (value != IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER)) {
0695: // visit the children in the container
0696: value = ((MarkupContainer) child).visitChildren(clazz,
0697: visitor);
0698:
0699: // If visitor returns a non-null value, it halts the traversal
0700: if ((value != IVisitor.CONTINUE_TRAVERSAL)
0701: && (value != IVisitor.CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER)) {
0702: return value;
0703: }
0704: }
0705: }
0706:
0707: return null;
0708: }
0709:
0710: /**
0711: * Traverses all child components in this container, calling the visitor's
0712: * visit method at each one.
0713: *
0714: * @param visitor
0715: * The visitor to call back to
0716: * @return The return value from a visitor which halted the traversal, or
0717: * null if the entire traversal occurred
0718: */
0719: public final Object visitChildren(final IVisitor visitor) {
0720: return visitChildren(null, visitor);
0721: }
0722:
0723: /**
0724: * Get the markup stream for this component.
0725: *
0726: * @return The markup stream for this component, or if it doesn't have one,
0727: * the markup stream for the nearest parent which does have one
0728: */
0729: protected final MarkupStream findMarkupStream() {
0730: // Start here
0731: MarkupContainer c = this ;
0732:
0733: // Walk up hierarchy until markup found
0734: while (c.getMarkupStream() == null) {
0735: // Check parent
0736: c = c.getParent();
0737:
0738: // Are we at the top of the hierarchy?
0739: if (c == null) {
0740: // Failed to find markup stream
0741: throw new WicketRuntimeException(
0742: exceptionMessage("No markup found"));
0743: }
0744: }
0745:
0746: return c.getMarkupStream();
0747: }
0748:
0749: /**
0750: * Gets a fresh markup stream that contains the (immutable) markup resource
0751: * for this class.
0752: *
0753: * @param throwException
0754: * If true, throw an exception, if markup could not be found
0755: * @return A stream of MarkupElement elements
0756: */
0757: public final MarkupStream getAssociatedMarkupStream(
0758: final boolean throwException) {
0759: try {
0760: return getApplication().getMarkupCache().getMarkupStream(
0761: this , throwException);
0762: } catch (MarkupException ex) {
0763: // re-throw it. The exception contains already all the information
0764: // required.
0765: throw ex;
0766: } catch (WicketRuntimeException ex) {
0767: // throw exception since there is no associated markup
0768: throw new MarkupNotFoundException(
0769: exceptionMessage("Markup of type '"
0770: + getMarkupType()
0771: + "' for component '"
0772: + getClass().getName()
0773: + "' not found."
0774: + " Enable debug messages for wicket.util.resource to get a list of all filenames tried"),
0775: ex);
0776: }
0777: }
0778:
0779: /**
0780: * Create a new markup resource stream for the container.
0781: * <p>
0782: * Note: it will only called once, the IResourceStream will be cached by
0783: * MarkupCache.
0784: * <p>
0785: * Note: IResourceStreamLocators should be used in case the strategy to find
0786: * a markup resource should be extended for ALL components of your
0787: * application.
0788: *
0789: * @see wicket.util.resource.locator.IResourceStreamLocator
0790: * @see wicket.markup.MarkupCache
0791: *
0792: * @param containerClass
0793: * The container the markup should be associated with
0794: * @return A IResourceStream if the resource was found
0795: */
0796: public IResourceStream newMarkupResourceStream(Class containerClass) {
0797: // Get locator to search for the resource
0798: final IResourceStreamLocator locator = getApplication()
0799: .getResourceSettings().getResourceStreamLocator();
0800:
0801: // Markup is associated with the containers class. Walk up the class
0802: // hierarchy up to MarkupContainer to find the containers markup
0803: // resource.
0804: while (containerClass != MarkupContainer.class) {
0805: final IResourceStream resourceStream = locator.locate(
0806: containerClass, containerClass.getName().replace(
0807: '.', '/'), getStyle(), getLocale(),
0808: getMarkupType());
0809:
0810: // Did we find it already?
0811: if (resourceStream != null) {
0812: return new MarkupResourceStream(resourceStream,
0813: new ContainerInfo(this ), containerClass);
0814: }
0815:
0816: // Walk up the class hierarchy one level, if markup has not
0817: // yet been found
0818: containerClass = containerClass.getSuperclass();
0819: }
0820:
0821: return null;
0822: }
0823:
0824: /**
0825: * Get the markup stream set on this container.
0826: *
0827: * @return Returns the markup stream set on this container.
0828: */
0829: public final MarkupStream getMarkupStream() {
0830: return markupStream;
0831: }
0832:
0833: /**
0834: * Handle the container's body. If your override of this method does not
0835: * advance the markup stream to the close tag for the openTag, a runtime
0836: * exception will be thrown by the framework.
0837: *
0838: * @param markupStream
0839: * The markup stream
0840: * @param openTag
0841: * The open tag for the body
0842: */
0843: protected void onComponentTagBody(final MarkupStream markupStream,
0844: final ComponentTag openTag) {
0845: renderComponentTagBody(markupStream, openTag);
0846: }
0847:
0848: /**
0849: * Renders this component. This implementation just calls renderComponent.
0850: *
0851: * @param markupStream
0852: */
0853: protected void onRender(final MarkupStream markupStream) {
0854: renderComponent(markupStream);
0855: }
0856:
0857: /**
0858: * Renders this component and all sub-components using the given markup
0859: * stream.
0860: *
0861: * @param markupStream
0862: * The markup stream
0863: */
0864: protected void renderAll(final MarkupStream markupStream) {
0865: // Loop through the markup in this container
0866: while (markupStream.hasMore()) {
0867: // Element rendering is responsible for advancing markup stream!
0868: final int index = markupStream.getCurrentIndex();
0869: renderNext(markupStream);
0870: if (index == markupStream.getCurrentIndex()) {
0871: markupStream
0872: .throwMarkupException("Component at markup stream index "
0873: + index
0874: + " failed to advance the markup stream");
0875: }
0876: }
0877: }
0878:
0879: /**
0880: * Renders markup for the body of a ComponentTag from the current position
0881: * in the given markup stream. If the open tag passed in does not require a
0882: * close tag, nothing happens. Markup is rendered until the closing tag for
0883: * openTag is reached.
0884: *
0885: * @param markupStream
0886: * The markup stream
0887: * @param openTag
0888: * The open tag
0889: */
0890: protected final void renderComponentTagBody(
0891: final MarkupStream markupStream, final ComponentTag openTag) {
0892: // If the open tag requires a close tag
0893: boolean render = openTag.requiresCloseTag();
0894: if (render == false) {
0895: // Tags like <p> do not require a close tag, but they may have.
0896: render = !openTag.hasNoCloseTag();
0897: }
0898: if (render == true) {
0899: // Loop through the markup in this container
0900: while (markupStream.hasMore()
0901: && !markupStream.get().closes(openTag)) {
0902: // Render markup element. Doing so must advance the markup
0903: // stream
0904: final int index = markupStream.getCurrentIndex();
0905: renderNext(markupStream);
0906: if (index == markupStream.getCurrentIndex()) {
0907: markupStream
0908: .throwMarkupException("Markup element at index "
0909: + index
0910: + " failed to advance the markup stream");
0911: }
0912: }
0913: }
0914: }
0915:
0916: /**
0917: * Set markup stream for this container.
0918: *
0919: * @param markupStream
0920: * The markup stream
0921: */
0922: protected final void setMarkupStream(final MarkupStream markupStream) {
0923: this .markupStream = markupStream;
0924: }
0925:
0926: /**
0927: * @return True if this markup container has associated markup
0928: */
0929: final boolean hasAssociatedMarkup() {
0930: return getApplication().getMarkupCache().hasAssociatedMarkup(
0931: this );
0932: }
0933:
0934: /**
0935: * @param component
0936: * Component being added
0937: */
0938: private final void addedComponent(final Component component) {
0939: // Check for degenerate case
0940: if (component == this ) {
0941: throw new IllegalArgumentException(
0942: "Component can't be added to itself");
0943: }
0944:
0945: MarkupContainer parent = component.getParent();
0946: if (parent != null) {
0947: parent.remove(component);
0948: }
0949:
0950: // Set child's parent
0951: component.setParent(this );
0952:
0953: // Tell the page a component was added
0954: final Page page = findPage();
0955: if (page != null) {
0956: page.componentAdded(component);
0957: }
0958: }
0959:
0960: /**
0961: * @param child
0962: * Child to add
0963: */
0964: private final void children_add(final Component child) {
0965: if (this .children == null) {
0966: this .children = child;
0967: } else {
0968: // Get current list size
0969: final int size = children_size();
0970:
0971: // Create array that holds size + 1 elements
0972: final Component[] children = new Component[size + 1];
0973:
0974: // Loop through existing children copying them
0975: for (int i = 0; i < size; i++) {
0976: children[i] = children_get(i);
0977: }
0978:
0979: // Add new child to the end
0980: children[size] = child;
0981:
0982: // Save new children
0983: this .children = children;
0984: }
0985: }
0986:
0987: private final Component children_get(int index) {
0988: if (index == 0) {
0989: if (children instanceof Component) {
0990: return (Component) children;
0991: } else {
0992: return ((Component[]) children)[index];
0993: }
0994: } else {
0995: return ((Component[]) children)[index];
0996: }
0997: }
0998:
0999: private final Component children_get(final String id) {
1000: if (children instanceof Component) {
1001: final Component component = (Component) children;
1002: if (component.getId().equals(id)) {
1003: return component;
1004: }
1005: } else {
1006: if (children != null) {
1007: final Component[] components = (Component[]) children;
1008: for (int i = 0; i < components.length; i++) {
1009: if (components[i].getId().equals(id)) {
1010: return components[i];
1011: }
1012: }
1013: }
1014: }
1015: return null;
1016: }
1017:
1018: private final int children_indexOf(Component child) {
1019: if (children instanceof Component) {
1020: if (((Component) children).getId().equals(child.getId())) {
1021: return 0;
1022: }
1023: } else {
1024: if (children != null) {
1025: final Component[] components = (Component[]) children;
1026: for (int i = 0; i < components.length; i++) {
1027: if (components[i].getId().equals(child.getId())) {
1028: return i;
1029: }
1030: }
1031: }
1032: }
1033: return -1;
1034: }
1035:
1036: private final Component children_remove(Component component) {
1037: int index = children_indexOf(component);
1038: if (index != -1) {
1039: return children_remove(index);
1040: }
1041: return null;
1042: }
1043:
1044: private final Component children_remove(int index) {
1045: if (children instanceof Component) {
1046: if (index == 0) {
1047: final Component removed = (Component) children;
1048: this .children = null;
1049: return removed;
1050: } else {
1051: throw new IndexOutOfBoundsException();
1052: }
1053: } else {
1054: Component[] c = ((Component[]) children);
1055: final Component removed = c[index];
1056: if (c.length == 2) {
1057: if (index == 0) {
1058: this .children = c[1];
1059: } else if (index == 1) {
1060: this .children = c[0];
1061: } else {
1062: throw new IndexOutOfBoundsException();
1063: }
1064: } else {
1065: Component[] newChildren = new Component[c.length - 1];
1066: int j = 0;
1067: for (int i = 0; i < c.length; i++) {
1068: if (i != index) {
1069: newChildren[j++] = c[i];
1070: }
1071: }
1072: this .children = newChildren;
1073: }
1074: return removed;
1075: }
1076: }
1077:
1078: private final Component children_set(int index, Component child) {
1079: final Component replaced;
1080: if (index < children_size()) {
1081: if (children == null || children instanceof Component) {
1082: replaced = (Component) children;
1083: children = child;
1084: } else {
1085: final Component[] children = (Component[]) this .children;
1086: replaced = children[index];
1087: children[index] = child;
1088: }
1089: } else {
1090: throw new IndexOutOfBoundsException();
1091: }
1092: return replaced;
1093: }
1094:
1095: private final int children_size() {
1096: if (children == null) {
1097: return 0;
1098: } else {
1099: if (children instanceof Component) {
1100: return 1;
1101: }
1102: return ((Component[]) children).length;
1103: }
1104: }
1105:
1106: /**
1107: * Ensure that there is space in childForId map for a new entry before
1108: * adding it.
1109: *
1110: * @param child
1111: * The child to put into the map
1112: * @return Any component that was replaced
1113: */
1114: private final Component put(final Component child) {
1115: int index = children_indexOf(child);
1116: if (index == -1) {
1117: children_add(child);
1118: return null;
1119: } else {
1120: return children_set(index, child);
1121: }
1122: }
1123:
1124: /**
1125: * @param component
1126: * Component being removed
1127: */
1128: private final void removedComponent(final Component component) {
1129: // Notify Page that component is being removed
1130: final Page page = component.findPage();
1131: if (page != null) {
1132: page.componentRemoved(component);
1133: }
1134:
1135: // detach children models
1136: if (component instanceof MarkupContainer) {
1137: ((MarkupContainer) component).visitChildren(new IVisitor() {
1138: public Object component(Component component) {
1139: try {
1140: // detach any models of the component
1141: component.detachModels();
1142: } catch (Exception e) // catch anything; we MUST detach all
1143: // models
1144: {
1145: log.error("detaching models of component "
1146: + component + " failed:", e);
1147: }
1148: return IVisitor.CONTINUE_TRAVERSAL;
1149: }
1150: });
1151: }
1152:
1153: // Detach model
1154: component.detachModels();
1155: // Component is removed
1156: component.setParent(null);
1157: }
1158:
1159: /**
1160: * Renders the next element of markup in the given markup stream.
1161: *
1162: * @param markupStream
1163: * The markup stream
1164: */
1165: private final void renderNext(final MarkupStream markupStream) {
1166: // Get the current markup element
1167: final MarkupElement element = markupStream.get();
1168:
1169: // If it a tag like <wicket..> or <span wicket:id="..." >
1170: if ((element instanceof ComponentTag)
1171: && !markupStream.atCloseTag()) {
1172: // Get element as tag
1173: final ComponentTag tag = (ComponentTag) element;
1174:
1175: // Get component id
1176: final String id = tag.getId();
1177:
1178: // Get the component for the id from the given container
1179: final Component component = get(id);
1180:
1181: // Failed to find it?
1182: if (component != null) {
1183: component.render(markupStream);
1184: } else {
1185: // 2rd try: Components like Border and Panel might implement
1186: // the ComponentResolver interface as well.
1187: MarkupContainer container = this ;
1188: while (container != null) {
1189: if (container instanceof IComponentResolver) {
1190: if (((IComponentResolver) container).resolve(
1191: this , markupStream, tag)) {
1192: return;
1193: }
1194: }
1195:
1196: container = container
1197: .findParent(MarkupContainer.class);
1198: }
1199:
1200: // 3rd try: Try application's component resolvers
1201: final List componentResolvers = this .getApplication()
1202: .getPageSettings().getComponentResolvers();
1203: final Iterator iterator = componentResolvers.iterator();
1204: while (iterator.hasNext()) {
1205: final IComponentResolver resolver = (IComponentResolver) iterator
1206: .next();
1207: if (resolver.resolve(this , markupStream, tag)) {
1208: return;
1209: }
1210: }
1211:
1212: if (tag instanceof WicketTag) {
1213: if (((WicketTag) tag).isChildTag()) {
1214: markupStream.throwMarkupException("Found "
1215: + tag.toString()
1216: + " but no <wicket:extend>");
1217: } else {
1218: markupStream
1219: .throwMarkupException("Failed to handle: "
1220: + tag.toString());
1221: }
1222: }
1223:
1224: // No one was able to handle the component id
1225: markupStream
1226: .throwMarkupException("Unable to find component with id '"
1227: + id
1228: + "' in "
1229: + this
1230: + ". This means that you declared wicket:id="
1231: + id
1232: + " in your markup, but that you either did not add the "
1233: + "component to your page at all, or that the hierarchy does not match.");
1234: }
1235: } else {
1236: // Render as raw markup
1237: if (log.isDebugEnabled()) {
1238: log.debug("Rendering raw markup");
1239: }
1240: getResponse().write(element.toCharSequence());
1241: markupStream.next();
1242: }
1243: }
1244:
1245: /**
1246: * Some MarkupContainers (e.g. HtmlHeaderContainer, BodyOnLoadContainer)
1247: * have to be transparent with respect to there child components. A
1248: * transparent container gets its children from its parent container.
1249: * <p>
1250: *
1251: * @see wicket.markup.resolver.ParentResolver
1252: *
1253: * @return false. By default a MarkupContainer is not transparent.
1254: */
1255: public boolean isTransparentResolver() {
1256: return false;
1257: }
1258: }
|