001: /*
002: * Created on Nov 14, 2005
003: */
004: package uk.org.ponder.rsf.renderer;
005:
006: import java.util.HashMap;
007: import java.util.Map;
008:
009: import org.apache.log4j.Level;
010:
011: import uk.org.ponder.rsf.components.UIBranchContainer;
012: import uk.org.ponder.rsf.components.UIComponent;
013: import uk.org.ponder.rsf.components.UIContainer;
014: import uk.org.ponder.rsf.components.UIJointContainer;
015: import uk.org.ponder.rsf.template.XMLLump;
016: import uk.org.ponder.rsf.template.XMLLumpList;
017: import uk.org.ponder.rsf.template.XMLLumpMMap;
018: import uk.org.ponder.rsf.util.SplitID;
019: import uk.org.ponder.rsf.view.ViewRoot;
020: import uk.org.ponder.util.Logger;
021:
022: /**
023: * Performs a first "light" pass of the template and component tree to resolve
024: * references by UIBranchContainer components to the correct tag targets.
025: *
026: * @author Antranig Basman (antranig@caret.cam.ac.uk)
027: *
028: */
029:
030: public class BranchResolver {
031: private HashMap branchmap = new HashMap();
032:
033: private XMLLumpMMap globalmap;
034:
035: private static class BestMatch {
036: public XMLLump bestlump;
037: public int deficit = Integer.MAX_VALUE;
038: }
039:
040: public BranchResolver(XMLLumpMMap globalmap) {
041: this .globalmap = globalmap;
042: }
043:
044: /** Returns a map of UIBranchContainer to XMLLump */
045: public static Map resolveBranches(XMLLumpMMap globalmap,
046: UIBranchContainer basecontainer, XMLLump parentlump) {
047: boolean debug = false;
048: Level oldlevel = null;
049: if (basecontainer instanceof ViewRoot) {
050: debug = ((ViewRoot) basecontainer).debug;
051: }
052: if (debug) {
053: oldlevel = Logger.log.getLevel();
054: Logger.log.setLevel(Level.DEBUG);
055: }
056: try {
057: BranchResolver resolver = new BranchResolver(globalmap);
058: resolver.branchmap.put(basecontainer, parentlump);
059: resolver.resolveRecurse(basecontainer, parentlump);
060: return resolver.branchmap;
061: } finally {
062: if (debug) {
063: Logger.log.setLevel(oldlevel);
064: }
065: }
066: }
067:
068: private void resolveRecurse(UIContainer basecontainer,
069: XMLLump parentlump) {
070: UIComponent[] flatchildren = basecontainer.flatChildren();
071: for (int i = 0; i < flatchildren.length; ++i) {
072: if (flatchildren[i] instanceof UIContainer) {
073: UIContainer branch = (UIContainer) flatchildren[i];
074: // ups! Do not resolve here if does not actually occur in parentlump.
075: XMLLump resolved = resolveCall(parentlump, branch);
076: if (Logger.log.isDebugEnabled()) {
077: Logger.log.debug("Resolving call for component "
078: + branch.getClass().getName() + " fullID "
079: + branch.getFullID());
080: if (resolved == null) {
081: Logger.log.debug("No target found!");
082: } else {
083: Logger.log.debug(resolved.toDebugString());
084: }
085: // Logger.log.info("for component with ID " + child.ID + " to ");
086: // System.out.println(debugLump(resolved));
087: }
088: if (resolved != null) {
089: branchmap.put(branch, resolved);
090: resolveRecurse(branch, resolved);
091: }
092: }
093: }
094: }
095:
096: private void resolveInScope(String searchID, String defprefix,
097: BestMatch bestmatch, XMLLumpMMap scope, UIContainer child) {
098: XMLLumpList scopelumps = scope.headsForID(searchID);
099: passDeficit(bestmatch, child, scopelumps);
100: if (bestmatch.deficit == 0)
101: return;
102: if (!defprefix.equals(searchID)) {
103: XMLLumpList scopedeflumps = scope.headsForID(defprefix);
104: passDeficit(bestmatch, child, scopedeflumps);
105: }
106: }
107:
108: private XMLLump resolveCall(XMLLump sourcescope, UIContainer child) {
109: String searchID = child instanceof UIJointContainer ? ((UIJointContainer) child).jointID
110: : child.ID;
111: SplitID split = new SplitID(searchID);
112: String defprefix = split.prefix + SplitID.SEPARATOR;
113: BestMatch bestmatch = new BestMatch();
114: if (Logger.log.isDebugEnabled()) {
115: Logger.log.debug("Resolving call for ID " + searchID
116: + " from container " + child.debugChildren());
117: }
118: // first get lumps in THIS SCOPE with EXACTLY MATCHING ID.
119: resolveInScope(searchID, defprefix, bestmatch,
120: sourcescope.downmap, child);
121: if (bestmatch.deficit == 0) {
122: return bestmatch.bestlump;
123: }
124: // only enable global resolution if it is a branch
125: if (child instanceof UIBranchContainer) {
126: if (sourcescope.parent.isstatictemplate) {
127: // make sure we can resolve local (intra-template) branches in the static case
128: resolveInScope(searchID, defprefix, bestmatch,
129: sourcescope.parent.globalmap, child);
130: }
131: resolveInScope(searchID, defprefix, bestmatch, globalmap,
132: child);
133: }
134: return bestmatch.bestlump;
135:
136: }
137:
138: private void passDeficit(BestMatch bestmatch,
139: UIContainer container, XMLLumpList tocheck) {
140: if (tocheck == null)
141: return;
142: for (int i = 0; i < tocheck.size(); ++i) {
143: XMLLump lump = tocheck.lumpAt(i);
144: int deficit = evalDeficit(container, lump);
145: if (deficit < bestmatch.deficit) {
146: bestmatch.deficit = deficit;
147: bestmatch.bestlump = lump;
148: if (deficit == 0)
149: return;
150: }
151: }
152: }
153:
154: private static final int DEFICIT_PRIORITY = 1000001;
155: private static final int REPETITIVE_DEFICIT_PRIORITY = 1001;
156:
157: // plus one, since a deficit indicates one of the concrete lump children
158: // must count as an extra unficit.
159: HashMap doneprefix = new HashMap(8);
160:
161: // the "deficit" is the number of (requested) children issued by the producer
162: // that are not found within children of the target lump.
163: // A match is either an exact match in prefix and suffix, or else a "default
164: // match" produced by looking for a "default" member in the template with the
165: // name "prefix:" for the issued component prefix.
166: private int evalDeficit(UIContainer container, XMLLump lump) {
167: int deficit = 0;
168: UIComponent[] children = container.flatChildren();
169: doneprefix.clear();
170: for (int i = 0; i < children.length; ++i) {
171: UIComponent child = children[i];
172: String prefix = SplitID.getPrefixColon(child.ID);
173: boolean matches = lump.downmap != null
174: && lump.downmap.hasID(child.ID);
175: if (matches) {
176: if (prefix != null)
177: doneprefix.put(prefix, Boolean.TRUE);
178: continue;
179: }
180: int penalty = DEFICIT_PRIORITY;
181: if (prefix != null) {
182: boolean matchesdef = lump.downmap != null
183: && lump.downmap.hasID(prefix);
184: if (matchesdef) {
185: doneprefix.put(prefix, Boolean.TRUE);
186: continue;
187: }
188: if (doneprefix.containsKey(prefix)) {
189: penalty = REPETITIVE_DEFICIT_PRIORITY;
190: }
191: }
192: deficit += penalty;
193: }
194: deficit += (lump.downmap == null ? 0 : lump.downmap
195: .numConcretes())
196: - children.length;
197: // think about also penalising "unficit" - children appearing in template
198: // that are NOT ISSUED by producer. This is a balance between resolution
199: // cost here (extra hashmap) and cost of searching for each template child
200: // later. There may also be some layout issues.
201: if (Logger.log.isDebugEnabled()) {
202: Logger.log.debug("Call to " + lump.toDebugString()
203: + " deficit " + deficit);
204: }
205: return deficit;
206: }
207:
208: }
|