001: /*
002: * Created on May 6, 2005
003: *
004: * TODO To change the template for this generated file go to
005: * Window - Preferences - Java - Code Style - Code Templates
006: */
007: package org.jasig.portal.channels.jsp.tree;
008:
009: import java.util.ArrayList;
010: import java.util.Iterator;
011: import java.util.List;
012: import java.util.Map;
013:
014: import org.apache.commons.logging.Log;
015: import org.apache.commons.logging.LogFactory;
016:
017: /**
018: * @author Mark Boyd
019: *
020: * TODO To change the template for this generated type comment go to
021: * Window - Preferences - Java - Code Style - Code Templates
022: */
023: public class Model {
024: private static final Log LOG = LogFactory.getLog(Model.class);
025: private static final int ACTION_LABEL_TYPE = 0;
026: private static final int ASPECT_LABEL_TYPE = 1;
027: private static final int NODE_LABEL_TYPE = 2;
028:
029: private Config cfg = null;
030: private int unresolvableCount = 0;
031:
032: private ArrayList indentations = new ArrayList();
033:
034: /**
035: * returns the node with the specified id in the root node. if the id passed
036: * in to this method is the root node's id than the root node is returned.
037: *
038: * @param id
039: * the id to search for.
040: * @return the node with the specified id.
041: * @author Alexander Boyd
042: */
043: public Node getNodeForId(String id) {
044: if (LOG.isDebugEnabled())
045: LOG.debug("getNodeForId('" + id + "')");
046: return getNodeForId(id, root);
047: }
048:
049: /**
050: * returns the node with the specified id in the specified node. if the node
051: * does not have children and the node's id is not the specified id
052: * than <code>null</code> is returned. if the specified id is not found in
053: * this node's hierarchy than null is returned. otherwise, the node with the
054: * specified id is returned. if more than one node in this hierarchy is
055: * found, it uses the first one it encounters.
056: *
057: * @param id
058: * the id to search for.
059: * @param nodeToSearch
060: * the node to search for id in.
061: * @return the node with the specified id, or null if none is found.
062: * @author Alexander Boyd
063: */
064: public static Node getNodeForId(String id, Node nodeToSearch) {
065: if (id.equals(nodeToSearch.getId()))
066: return nodeToSearch;
067: if (!nodeToSearch.getHasChildren())
068: return null;
069: Node[] children = nodeToSearch.getChildren();
070: if (children == null)
071: return null;
072: Node childWithId = null;
073:
074: for (int i = 0; childWithId == null && i < children.length; i++)
075: childWithId = getNodeForId(id, children[i]);
076:
077: return childWithId;
078: }
079:
080: /**
081: * sets whether or not the specified node is expanded.
082: *
083: * @param id
084: * the id of the container
085: * @param expanded
086: * whether or not it is expanded.
087: * @throws IllegalArgumentException
088: * if the node specified by id is not a container or if there is no
089: * node with the specified id.
090: * @throws NullPointerException
091: * if id is null.
092: * @author Alexander Boyd
093: */
094: public void setExpandedForId(String id, boolean expanded) {
095: if (LOG.isDebugEnabled())
096: LOG.debug("setExpandedForId('" + id + "', " + expanded
097: + ")");
098: Node n = getNodeForId(id);
099: if (n == null || !n.getHasChildren())
100: return;
101: n.setIsExpanded(expanded);
102: }
103:
104: /**
105: * Sets the root object to be translated into the root node of the tree.
106: *
107: * @param root
108: * The root to set.
109: */
110: public void setRootDomainObject(Object root) {
111: if (LOG.isDebugEnabled()) {
112: if (root == null)
113: LOG.debug("setRootDomainObject(null)");
114: else
115: LOG.debug("setRootDomainObject("
116: + root.getClass().getName() + ")");
117: }
118: this .root = resolveChild(root);
119: this .root.setIsExpanded(true);
120: }
121:
122: public Model(Config cfg) {
123: this .cfg = cfg;
124: }
125:
126: public Config getConfig() {
127: if (LOG.isDebugEnabled())
128: LOG.debug("getConfig()");
129: return cfg;
130: }
131:
132: /**
133: * Translates a domain object into the TreeNode that represents it if a
134: * suitable surrogate is found. Called by the containing node to resolve
135: * its child nodes.
136: *
137: * @param o
138: * @return
139: */
140: Node resolveChild(Object o) {
141: return resolveObject(o, false);
142: }
143:
144: /**
145: * Translates a domain object's aspect into the TreeNode that represents
146: * that aspect in the tree if a suitable surrogate is found. Called by the
147: * containing node to resolve its aspect nodes.
148: *
149: * @param o
150: * @return
151: */
152: Node resolveAspect(Object o) {
153: return resolveObject(o, true);
154: }
155:
156: /**
157: * Performs the surrogate lookup and instantiation of corresponding
158: * TreeNode objects.
159: *
160: * @param o
161: * @param isAspect
162: * @return
163: */
164: private Node resolveObject(Object o, boolean isAspect) {
165: ISurrogate s = null;
166: for (Iterator itr = cfg.getSurrogates().iterator(); s == null
167: && itr.hasNext();) {
168: ISurrogate sgt = (ISurrogate) itr.next();
169: if (sgt.canResolve(o))
170: s = sgt;
171: }
172: if (s == null) {
173: if (cfg.getIncludeUnresolveables()) {
174: String id = "tun_" + unresolvableCount++;
175: Node node = new Node(this , id, o);
176: if (isAspect)
177: node.setIsAspect(true);
178: return node;
179: }
180: return null;
181: }
182: String id = s.getId(o);
183: Node node = new Node(this , id, o, s);
184:
185: if (isAspect)
186: node.setIsAspect(true);
187: else if (!cfg.getLazilyLoad()) {
188: node.loadChildren();
189: node.loadAspects();
190: }
191: return node;
192: }
193:
194: //////////////////// Methods accessed by the JSP renderer ///////////////////
195:
196: public List getIndentImages() {
197: if (LOG.isDebugEnabled())
198: LOG.debug("getIndentImages() --> list.size()="
199: + indentations.size());
200: return indentations;
201: }
202:
203: public void setPushIndent(String indentType) {
204: if (LOG.isDebugEnabled())
205: LOG.debug("setPushIndent('" + indentType + "')");
206: indentations.add(indentType);
207: }
208:
209: public String getPopIndent() {
210: if (indentations.size() > 0) {
211: String indent = (String) indentations.remove(indentations
212: .size() - 1);
213: if (LOG.isDebugEnabled())
214: LOG.debug("getPopIndent() --> '" + indent + "'");
215: return indent;
216: }
217: if (LOG.isDebugEnabled())
218: LOG.debug("getPopIndent() --> 'garbage' since empty");
219: return "garbage"; // not important since not used but can't be empty
220: }
221:
222: /**
223: * Returns the object acquired from implementations of one of two plugged-in
224: * interfaces depending on the label type being rendered. If label type is
225: * the same as the value returned by getNodeLabelType() or
226: * getAspectLabelType() then the returned object is acquired from the
227: * instance of ISurrogate that resolved the Node represented by getNode().
228: * If the label type is the same as the value returned by
229: * getActionLabelType() then the returned object is acquired from the
230: * instance of IDomainActionSet.
231: *
232: * @return
233: */
234: public Object getLabelData() {
235: Node node = getNode();
236: if (getLabelType() == getActionLabelType()) {
237: IDomainActionSet set = cfg.getActionSet();
238:
239: if (set == null || node == null) {
240: if (LOG.isDebugEnabled())
241: LOG
242: .debug("getLabelData() --> null (for actionLabelType)");
243: return null;
244: }
245: Object obj = node.getDomainObject();
246: String action = getDomainAction();
247: if (LOG.isDebugEnabled())
248: LOG
249: .debug("getLabelData() --> IDomainActionSet.getLabelData('"
250: + action
251: + "', domainObject) for node id="
252: + node.getId());
253: return set.getLabelData(action, obj);
254: }
255: if (node == null) {
256: if (LOG.isDebugEnabled())
257: LOG.debug("getLabelData() --> null (for null node)");
258: return null;
259: }
260: if (LOG.isDebugEnabled())
261: LOG.debug("getLabelData() --> node.getLabelData() for "
262: + "node id=" + node.getId());
263: return node.getLabelData();
264: }
265:
266: /**
267: * Used in the tree rendering JSP to translate supported JSP Map semantics
268: * to dynamic lookup of an expansion and collapse URLs for a passed in node
269: * id. The following pattern in the JSP will cause this method to be called
270: * to obtain the Map represented by "treeUrls".
271: *
272: * <c:out value="${requestScope.model.treeUrls.expand[node.id]}"/>
273: *
274: * @author Mark Boyd
275: *
276: */
277: public Map getTreeUrls() {
278: if (LOG.isDebugEnabled())
279: LOG.debug("getTreeUrls()");
280: return cfg.getTreeUrlResolvers();
281: }
282:
283: /**
284: * The first time called this method marks this model as being in the midst
285: * of rendering and returns true. Thereafter, it returns false until
286: * stopRendering has been called. This method is called by the JSP to
287: * learn if rendering started as a result of making this call. The method
288: * name was chosen for the resulting access in the JSP and is misleading
289: * when viewed here in the code.
290: *
291: * @return Returns true if the JSP is rendering the model.
292: */
293: public synchronized boolean getStartRendering() {
294: if (isRendering) {
295: if (LOG.isDebugEnabled())
296: LOG.debug("getStartRendering() --> false");
297: return false;
298: }
299: if (LOG.isDebugEnabled())
300: LOG.debug("getStartRendering() --> true");
301: isRendering = true;
302: return isRendering;
303: }
304:
305: /**
306: * Marks this model as not being in the midst of rendering. This method is
307: * called by the JSP to indicate that rendering has completed The method
308: * name was chosen for the resulting access in the JSP and is misleading
309: * when viewed here in the code since it does have side affects from being
310: * called.
311: *
312: */
313: public synchronized void setIsRendering(boolean b) {
314: this .isRendering = b;
315: if (LOG.isDebugEnabled())
316: LOG.debug("setIsRenderering(" + b + ")");
317: }
318:
319: /**
320: * Returns the root node of the tree.
321: *
322: * @return Returns the root.
323: */
324: public Node getRoot() {
325: if (LOG.isDebugEnabled()) {
326: if (root == null)
327: LOG.debug("getRoot() --> null");
328: else
329: LOG.debug("getRoot() --> id=" + root.getId());
330: }
331: return root;
332: }
333:
334: /**
335: * Returns the currently rendering node. Called by the JSP renderer.
336: *
337: * @return Returns the node.
338: */
339: public Node getNode() {
340: if (LOG.isDebugEnabled()) {
341: if (node == null)
342: LOG.debug("getNode() --> null");
343: else
344: LOG.debug("getNode() --> id=" + node.getId());
345: }
346: return node;
347: }
348:
349: /**
350: * Sets the currently rendering node. Called by the JSP renderer.
351: *
352: * @param node
353: * The node to set.
354: */
355: public void setNode(Node node) {
356: if (LOG.isDebugEnabled()) {
357: if (node == null)
358: LOG.debug("setNode(null)");
359: else
360: LOG.debug("setNode(id=" + node.getId() + ")");
361: }
362: this .node = node;
363: }
364:
365: /**
366: * Returns the current domain action being rendered by the tree. This is
367: * called by the required label renderer JSP identified via
368: * getLabelRenderer().
369: *
370: * @return
371: */
372: public String getDomainAction() {
373: if (LOG.isDebugEnabled())
374: LOG.debug("getDomainAction() --> '" + action + "'");
375: return action;
376: }
377:
378: /**
379: * Sets the current domain action being rendered by the tree. This is called
380: * by tree renderer to pass to the label renderer the action whose label is
381: * to be rendered. The required label renderer JSP is identified via
382: * getLabelRenderer().
383: *
384: * @return
385: */
386: public void setDomainAction(String action) {
387: if (LOG.isDebugEnabled())
388: LOG.debug("setDomainAction('" + action + "')");
389: this .action = action;
390: }
391:
392: /**
393: * Returns the current label type rendering being requested of the label
394: * renderer by the tree renderer. This is called by
395: * the required label renderer JSP identified via getLabelRenderer().
396: *
397: * @return
398: */
399: public int getLabelType() {
400: if (LOG.isDebugEnabled()) {
401: String type = "" + labelType + "?";
402: if (labelType == NODE_LABEL_TYPE)
403: type = "NODE";
404: else if (labelType == ASPECT_LABEL_TYPE)
405: type = "ASPECT";
406: else if (labelType == ACTION_LABEL_TYPE)
407: type = "ACTION_LABEL_TYPE";
408: LOG.debug("getLabelType() --> " + type);
409: }
410: return labelType;
411: }
412:
413: // the following three accessors are added so that the rendering JSP can
414: // access the static lable type fields.
415: public int getActionLabelType() {
416: if (LOG.isDebugEnabled())
417: LOG.debug("getActionLabelType()");
418: return ACTION_LABEL_TYPE;
419: }
420:
421: public int getNodeLabelType() {
422: if (LOG.isDebugEnabled())
423: LOG.debug("getNodeLabelType()");
424: return NODE_LABEL_TYPE;
425: }
426:
427: public int getAspectLabelType() {
428: if (LOG.isDebugEnabled())
429: LOG.debug("getAspectLabelType()");
430: return ASPECT_LABEL_TYPE;
431: }
432:
433: /**
434: * Sets the current label type being requested of the label renderer by the
435: * tree renderer. This is called by
436: * tree renderer to pass to the label renderer the label type to
437: * be rendered. The required label renderer JSP is identified via
438: * getLabelRenderer().
439: *
440: * @return
441: */
442: public void setLabelType(int labelType) {
443: if (LOG.isDebugEnabled()) {
444: String type = "" + labelType + "?";
445: if (labelType == NODE_LABEL_TYPE)
446: type = "NODE";
447: else if (labelType == ASPECT_LABEL_TYPE)
448: type = "ASPECT";
449: else if (labelType == ACTION_LABEL_TYPE)
450: type = "ACTION_LABEL_TYPE";
451: LOG.debug("setLabelType(" + type + ")");
452:
453: }
454: this .labelType = labelType;
455: }
456:
457: ///////// reviewed variables /////////
458:
459: private Node root = null;
460: private boolean isRendering = false;
461: private Node node = null;
462: private String action = null;
463: private int labelType = 0;
464: }
|