001: /*
002: * Created on Nov 11, 2005
003: */
004: package uk.org.ponder.rsf.components;
005:
006: import java.util.ArrayList;
007: import java.util.HashMap;
008: import java.util.Iterator;
009: import java.util.List;
010: import java.util.Map;
011:
012: import uk.org.ponder.rsf.util.RSFUtil;
013: import uk.org.ponder.rsf.util.SplitID;
014: import uk.org.ponder.stringutil.CharWrap;
015:
016: public abstract class UIContainer extends UIParameterHolder {
017: /**
018: * The localID allows clients to distinguish between multiple instantiations
019: * of the "same" (by rsf:id) component within the same scope. It forms part of
020: * the global path constructed by getFullID() which uniquely identifies the
021: * component.
022: */
023: public String localID = "";
024:
025: /** When set to <code>true</code>, this container will be allocated
026: * no ID segment at all in the fullID derived for components contained
027: * inside it.
028: */
029: public boolean noID = false;
030: // This is a map to either the single component with a given ID prefix, or a
031: // list in the case of a repetitive domain (non-null suffix)
032: private Map childmap = new HashMap();
033:
034: // this is created by the first call to flatChildren() which is assumed to
035: // occur during the render phase. Implicit model that component tree is
036: // i) constructed, ii) rendered, iii) discarded.
037: // It is worth caching this since it is iterated over up to 4n times during
038: // rendering, for each HTMLLump headlump that matches the requested call
039: // in the 4 scopes.
040: private transient UIComponent[] flatchildren;
041:
042: /**
043: * Return the single component with the given ID. This should be an ID without
044: * colon designating a leaf child.
045: */
046: public UIComponent getComponent(String id) {
047: if (childmap == null)
048: return null;
049: Object togo = childmap.get(id);
050: if (togo != null && !(togo instanceof UIComponent)) {
051: throw new IllegalArgumentException(
052: "Error in view tree: component with id "
053: + id
054: + " was expected to be a leaf component but was a branch."
055: + "\n (did you forget to use a colon in the view template?)");
056: }
057: return (UIComponent) togo;
058: }
059:
060: /**
061: * Return all child components with the given prefix. This may be either a
062: * List if the component genuinely represents a branch structure, or a single
063: * component if a leaf.
064: */
065: public Object getComponents(String id) {
066: return childmap.get(id);
067: }
068:
069: public String debugChildren() {
070: CharWrap togo = new CharWrap();
071: togo.append("Child IDs: (");
072: UIComponent[] children = flatChildren();
073: for (int i = 0; i < children.length; ++i) {
074: if (i != 0) {
075: togo.append(", ");
076: }
077: togo.append(children[i].ID);
078: }
079: togo.append(")");
080: return togo.toString();
081: }
082:
083: /**
084: * Returns a flattened array of all children of this container. Note that this
085: * method will trigger the creation of a cached internal array on its first
086: * use, which cannot be recreated. It is essential therefore that it only be
087: * used once ALL modifications to the component tree have concluded (i.e. once
088: * rendering starts).
089: */
090: public UIComponent[] flatChildren() {
091: if (flatchildren == null) {
092: ComponentList children = flattenChildren();
093: flatchildren = (UIComponent[]) children
094: .toArray(new UIComponent[children.size()]);
095: }
096: return flatchildren;
097: }
098:
099: /**
100: * Returns a list of all CURRENT children of this container. This method is
101: * safe to use at any time.
102: */
103: // There are now two calls to this in the codebase, firstly from ViewProcessor
104: // and then from BasicFormFixer. The VP call is necessary since it needs to
105: // fossilize
106: // the list up front, but if another call arises as in BFF we ought to write a
107: // multi-iterator.
108: public ComponentList flattenChildren() {
109: ComponentList children = new ComponentList();
110: for (Iterator childit = childmap.values().iterator(); childit
111: .hasNext();) {
112: Object child = childit.next();
113: if (child instanceof UIComponent) {
114: children.add(child);
115: } else if (child instanceof List) {
116: children.addAll((List) child);
117: }
118: }
119: return children;
120: }
121:
122: /** Add a component as a new child of this container */
123:
124: public void addComponent(UIComponent toadd) {
125: toadd.parent = this ;
126:
127: SplitID split = new SplitID(toadd.ID);
128: String childkey = split.prefix;
129: if (toadd.ID != null && split.suffix == null) {
130: childmap.put(childkey, toadd);
131: } else {
132: List children = (List) childmap.get(childkey);
133: if (children == null) {
134: children = new ArrayList();
135: childmap.put(childkey, children);
136: } else if (!children.isEmpty()
137: && toadd instanceof UIBranchContainer) {
138: UIBranchContainer addbranch = (UIBranchContainer) toadd;
139: if (addbranch.localID == "") {
140: addbranch.localID = Integer.toString(children
141: .size());
142: }
143: }
144: children.add(toadd);
145: }
146: }
147:
148: public void remove(UIComponent tomove) {
149: SplitID split = new SplitID(tomove.ID);
150: String childkey = split.prefix;
151: if (split.suffix == null) {
152: Object tomovetest = childmap.remove(childkey);
153: if (tomove != tomovetest) {
154: RSFUtil.failRemove(tomove);
155: }
156: } else {
157: List children = (List) childmap.get(childkey);
158: if (children == null) {
159: RSFUtil.failRemove(tomove);
160: }
161: boolean removed = children.remove(tomove);
162: if (!removed)
163: RSFUtil.failRemove(tomove);
164: }
165: tomove.updateFullID(null); // remove cached ID
166: }
167:
168: }
|