001: /*
002: * Created on Aug 7, 2005
003: */
004: package uk.org.ponder.rsf.renderer;
005:
006: import java.util.HashMap;
007: import java.util.HashSet;
008: import java.util.Iterator;
009: import java.util.List;
010: import java.util.Map;
011: import java.util.Set;
012:
013: import uk.org.ponder.arrayutil.ListUtil;
014: import uk.org.ponder.messageutil.TargettedMessageList;
015: import uk.org.ponder.rsf.components.UIBranchContainer;
016: import uk.org.ponder.rsf.components.UIComponent;
017: import uk.org.ponder.rsf.components.UIContainer;
018: import uk.org.ponder.rsf.content.ContentTypeInfo;
019: import uk.org.ponder.rsf.renderer.decorator.DecoratorManager;
020: import uk.org.ponder.rsf.template.XMLCompositeViewTemplate;
021: import uk.org.ponder.rsf.template.XMLLump;
022: import uk.org.ponder.rsf.template.XMLLumpList;
023: import uk.org.ponder.rsf.template.XMLLumpMMap;
024: import uk.org.ponder.rsf.template.XMLViewTemplate;
025: import uk.org.ponder.rsf.util.RSFUtil;
026: import uk.org.ponder.rsf.util.SplitID;
027: import uk.org.ponder.rsf.view.View;
028: import uk.org.ponder.rsf.view.ViewTemplate;
029: import uk.org.ponder.streamutil.write.PrintOutputStream;
030: import uk.org.ponder.stringutil.CharWrap;
031: import uk.org.ponder.util.Logger;
032: import uk.org.ponder.xml.XMLUtil;
033: import uk.org.ponder.xml.XMLWriter;
034:
035: /**
036: * Encapsulates the request-specific process of rendering a view - a
037: * request-scope bean containing the implementation of the IKAT rendering
038: * algorithm.
039: *
040: * @author Antranig Basman (antranig@caret.cam.ac.uk)
041: *
042: */
043: public class ViewRender {
044: private XMLViewTemplate roott;
045: private XMLLumpMMap globalmap;
046:
047: private View view;
048: private RenderSystem renderer;
049: private PrintOutputStream pos;
050: private XMLWriter xmlw;
051:
052: // a map of UIBranchContainer to XMLLump
053: private Map branchmap;
054: private XMLLumpMMap collected = new XMLLumpMMap();
055:
056: private XMLLump messagelump;
057:
058: private TargettedMessageList messagelist;
059: // a map of HTMLLumps to StringList of messages due to be delivered to
060: // that component when it is reached (registered with a FORID prefix)
061: private MessageTargetMap messagetargets;
062: private MessageRenderer messagerenderer;
063: private String globalmessagetarget;
064: private boolean rendereddeadletters;
065: private ContentTypeInfo contenttypeinfo;
066: private IDAssigner IDassigner;
067: private DecoratorManager decoratormanager;
068: private boolean debugrender;
069: private RenderSystemContext rsc;
070:
071: public void setViewTemplate(ViewTemplate viewtemplateo) {
072: if (viewtemplateo instanceof XMLCompositeViewTemplate) {
073: XMLCompositeViewTemplate viewtemplate = (XMLCompositeViewTemplate) viewtemplateo;
074: roott = viewtemplate.roottemplate;
075: globalmap = viewtemplate.globalmap;
076: collected.aggregate(viewtemplate.mustcollectmap);
077: } else {
078: roott = (XMLViewTemplate) viewtemplateo;
079: globalmap = roott.globalmap;
080: }
081:
082: }
083:
084: public void setView(View view) {
085: this .view = view;
086: }
087:
088: public void setRenderSystem(RenderSystem renderer) {
089: this .renderer = renderer;
090: }
091:
092: public void setContentTypeInfo(ContentTypeInfo contenttypeinfo) {
093: this .contenttypeinfo = contenttypeinfo;
094: }
095:
096: public void setMessages(TargettedMessageList messages) {
097: this .messagelist = messages;
098: }
099:
100: public void setGlobalMessageTarget(String globalmessagetarget) {
101: this .globalmessagetarget = globalmessagetarget;
102: }
103:
104: public void setMessageRenderer(MessageRenderer messagerenderer) {
105: this .messagerenderer = messagerenderer;
106: }
107:
108: public void setDecoratorManager(DecoratorManager decoratormanager) {
109: this .decoratormanager = decoratormanager;
110: }
111:
112: public void setDebugRender(boolean debugrender) {
113: this .debugrender = debugrender;
114: }
115:
116: private void collectContributions() {
117: Set seenset = new HashSet();
118: for (Iterator lumpit = branchmap.values().iterator(); lumpit
119: .hasNext();) {
120: XMLLump headlump = (XMLLump) lumpit.next();
121: if (!seenset.contains(headlump.parent)) {
122: collected.aggregate(headlump.parent.collectmap);
123: seenset.add(headlump.parent);
124: }
125: }
126: }
127:
128: private void debugGlobalTargets() {
129: renderer.renderDebugMessage(rsc,
130: "All global branch targets in resolution set:");
131: for (Iterator globalit = globalmap.iterator(); globalit
132: .hasNext();) {
133: String key = (String) globalit.next();
134: if (key.indexOf(':') != -1) {
135: renderer.renderDebugMessage(rsc, "Branch key " + key);
136: XMLLumpList res = globalmap.headsForID(key);
137: for (int i = 0; i < res.size(); ++i) {
138: renderer.renderDebugMessage(rsc, "\t"
139: + res.lumpAt(i).toString());
140: }
141: }
142: }
143: renderer.renderDebugMessage(rsc, "");
144: }
145:
146: public void render(PrintOutputStream pos) {
147: IDassigner = new IDAssigner(
148: debugrender ? ContentTypeInfo.ID_FORCE
149: : contenttypeinfo.IDStrategy);
150: UIBranchContainer messagecomponent = UIBranchContainer.make(
151: view.viewroot, MessageTargetter.RSF_MESSAGES);
152: branchmap = BranchResolver.resolveBranches(globalmap,
153: view.viewroot, roott.rootlump);
154: view.viewroot.remove(messagecomponent);
155: messagelump = (XMLLump) branchmap.get(messagecomponent);
156: collectContributions();
157: messagetargets = MessageTargetter.targetMessages(branchmap,
158: view, messagelist, globalmessagetarget);
159: String declaration = contenttypeinfo.get().declaration;
160: if (declaration != null)
161: pos.print(declaration);
162: this .pos = pos;
163: this .xmlw = new XMLWriter(pos);
164: rsc = new RenderSystemContext(debugrender, view, pos, xmlw,
165: IDassigner, collected);
166: rendereddeadletters = false;
167: if (debugrender) {
168: debugGlobalTargets();
169: }
170: renderRecurse(view.viewroot, roott.rootlump,
171: roott.lumps[roott.roottagindex]);
172: }
173:
174: private void renderContainer(UIContainer child, XMLLump targetlump) {
175: // may have jumped template file
176: XMLViewTemplate t2 = targetlump.parent;
177: XMLLump firstchild = t2.lumps[targetlump.open_end.lumpindex + 1];
178: if (child instanceof UIBranchContainer) {
179: dumpBranchHead((UIBranchContainer) child, targetlump);
180: } else {
181: renderer.renderComponent(rsc, child, targetlump);
182: }
183: renderRecurse(child, targetlump, firstchild);
184: }
185:
186: private void renderRecurse(UIContainer basecontainer,
187: XMLLump parentlump, XMLLump baselump) {
188:
189: int renderindex = baselump.lumpindex;
190: int basedepth = parentlump.nestingdepth;
191: XMLViewTemplate tl = parentlump.parent;
192: Set rendered = null;
193: if (debugrender) {
194: rendered = new HashSet();
195: }
196:
197: while (true) {
198: // continue scanning along this template section until we either each
199: // the last lump, or the recursion level.
200: renderindex = RenderUtil.dumpScan(tl.lumps, renderindex,
201: basedepth, pos, true, false);
202: if (renderindex == tl.lumps.length)
203: break;
204: XMLLump lump = tl.lumps[renderindex];
205: if (lump.nestingdepth < basedepth)
206: break;
207:
208: String id = lump.rsfID;
209: if (id == null) {
210: throw new IllegalArgumentException(
211: "Fatal internal error during rendering - no rsf:id found on stopping tag "
212: + lump);
213: }
214: if (id.startsWith(XMLLump.ELISION_PREFIX)) {
215: id = id.substring(XMLLump.ELISION_PREFIX.length());
216: }
217: boolean ismessagefor = id.startsWith(XMLLump.FORID_PREFIX);
218:
219: if (!ismessagefor && SplitID.isSplit(id)) {
220: // we have entered a repetitive domain, by diagnosis of the template.
221: // Seek in the component tree for the child list that must be here
222: // at this component, and process them in order, looking them up in
223: // the forward map, which must ALSO be here.
224: String prefix = SplitID.getPrefix(id);
225: List children = fetchComponents(basecontainer, prefix);
226: // these are all children with the same prefix, which will be rendered
227: // synchronously.
228: if (children != null) {
229: for (int i = 0; i < children.size(); ++i) {
230: UIComponent child = (UIComponent) children
231: .get(i);
232: if (child instanceof UIContainer) {
233: XMLLump targetlump = (XMLLump) branchmap
234: .get(child);
235: if (targetlump != null) {
236: if (debugrender) {
237: renderComment("Branching for "
238: + child.getFullID()
239: + " from " + lump + " to "
240: + targetlump);
241: }
242: renderContainer((UIContainer) child,
243: targetlump);
244: if (debugrender) {
245: renderComment("Branch returned for "
246: + child.getFullID()
247: + " to "
248: + lump
249: + " from "
250: + targetlump);
251: }
252: } else {
253: if (debugrender) {
254: renderer
255: .renderDebugMessage(
256: rsc,
257: "No matching template branch found for branch container with full ID "
258: + child
259: .getFullID()
260: + " rendering from parent template branch "
261: + baselump
262: .toString());
263: }
264: }
265: } else { // repetitive leaf
266: XMLLump targetlump = findChild(parentlump,
267: child);
268: // this case may trigger if there are suffix-specific renderers
269: // but no fallback.
270: if (targetlump == null) {
271: renderer
272: .renderDebugMessage(
273: rsc,
274: "Repetitive leaf with full ID "
275: + child
276: .getFullID()
277: + " could not be rendered from parent template branch "
278: + baselump
279: .toString());
280: continue;
281: }
282: int renderend = renderer.renderComponent(
283: rsc, child, targetlump);
284: boolean wasopentag = tl.lumps[renderend].nestingdepth >= targetlump.nestingdepth;
285: UIContainer newbase = child instanceof UIContainer ? (UIContainer) child
286: : basecontainer;
287: if (wasopentag) {
288: renderRecurse(newbase, targetlump,
289: tl.lumps[renderend]);
290: renderend = targetlump.close_tag.lumpindex + 1;
291: }
292: if (i != children.size() - 1) {
293: // at this point, magically locate any "glue" that matches the
294: // transition
295: // from this component to the next in the template, and scan
296: // along
297: // until we reach the next component with a matching id prefix.
298: // NB transition matching is not implemented and may never be.
299: RenderUtil.dumpScan(tl.lumps,
300: renderend,
301: targetlump.nestingdepth - 1,
302: pos, false, false);
303: // we discard any index reached by this dump, continuing the
304: // controlled sequence as long as there are any children.
305: // given we are in the middle of a sequence here, we expect to
306: // see nothing perverse like components or forms, at most static
307: // things (needing rewriting?)
308: // TODO: split of beginning logic from renderComponent that
309: // deals
310: // with static rewriting, and somehow fix this call to dumpScan
311: // so that it can invoke it. Not urgent, we currently only have
312: // the TINIEST text forming repetition glue.
313: } else {
314: RenderUtil.dumpScan(tl.lumps,
315: renderend,
316: targetlump.nestingdepth, pos,
317: true, false);
318: }
319: }
320:
321: } // end for each repetitive child
322: } else {
323: if (debugrender) {
324: renderer
325: .renderDebugMessage(
326: rsc,
327: "No branch container with prefix "
328: + prefix
329: + ": found at "
330: + RSFUtil
331: .reportPath(basecontainer)
332: + " at template position "
333: + baselump.toString()
334: + ", skipping");
335: }
336: }
337: // at this point, magically locate the "postamble" from lump, and
338: // reset the index.
339:
340: XMLLump finallump = lump.uplump.getFinal(prefix);
341: //parentlump.downmap.getFinal(prefix);
342: XMLLump closefinal = finallump.close_tag;
343: renderindex = closefinal.lumpindex + 1;
344: if (debugrender) {
345: renderComment("Stack returned from branch for ID "
346: + id + " to " + baselump.toString()
347: + ": skipping from " + lump.toString()
348: + " to " + closefinal.toString());
349: }
350: } else if (ismessagefor) {
351: TargettedMessageList messages = messagetargets
352: .getMessages(lump);
353: if (messages == null)
354: messages = new TargettedMessageList();
355: if (!rendereddeadletters) {
356: rendereddeadletters = true;
357: TargettedMessageList deadmessages = messagetargets
358: .getMessages(MessageTargetter.DEAD_LETTERS);
359: if (deadmessages != null) {
360: messages.addMessages(deadmessages);
361: }
362: }
363: if (messages.size() != 0) {
364: if (messagelump == null) {
365: Logger.log
366: .warn("No message template is configured (containing branch with rsf id rsf-messages:)");
367: } else {
368: UIBranchContainer messagebranch = messagerenderer
369: .renderMessageList(messages);
370: renderContainer(messagebranch, messagelump);
371: }
372: }
373: XMLLump closelump = lump.close_tag;
374: renderindex = closelump.lumpindex + 1;
375: } else {
376: // no colon - continue template-driven.
377: // it is a single, irrepitable component - just render it, and skip
378: // on, or skip completely if there is no peer in the component tree.
379: UIComponent component = null;
380: if (id != null) {
381: if (debugrender) {
382: rendered.add(id);
383: }
384: component = fetchComponent(basecontainer, id);
385: }
386: // Form rendering is now subject to "fairly normal" branch rendering logic
387: // That is, a UIContainer may now also be a leaf
388: if (component instanceof UIContainer) {
389: renderContainer((UIContainer) component, lump);
390: renderindex = lump.close_tag.lumpindex + 1;
391: } else {
392: // if we find a leaf component, render it.
393: renderindex = renderer.renderComponent(rsc,
394: component, lump);
395: }
396: } // end if unrepeatable component.
397: if (renderindex == tl.lumps.length) {
398: // deal with the case where component was root element - Ryan of
399: // 11/10/06
400: break;
401: }
402: }
403: if (debugrender) {
404: UIComponent[] flatchildren = basecontainer.flatChildren();
405: for (int i = 0; i < flatchildren.length; ++i) {
406: UIComponent child = flatchildren[i];
407: if (!(child.ID.indexOf(':') != -1)
408: && !rendered.contains(child.ID)) {
409: renderer
410: .renderDebugMessage(
411: rsc,
412: "Leaf child component "
413: + child.getClass()
414: .getName()
415: + " with full ID "
416: + child.getFullID()
417: + " could not be found within template "
418: + baselump.toString());
419: }
420: }
421: }
422: }
423:
424: private void renderComment(String string) {
425: pos.print("<!-- " + string + "-->");
426:
427: }
428:
429: private UIComponent fetchComponent(UIContainer basecontainer,
430: String id) {
431: if (id.startsWith(XMLLump.MSG_PREFIX)) {
432: String key = id.substring(XMLLump.MSG_PREFIX.length());
433: return messagerenderer.renderMessage(key);
434: }
435: while (basecontainer != null) {
436: UIComponent togo = basecontainer.getComponent(id);
437: if (togo != null)
438: return togo;
439: basecontainer = basecontainer.parent;
440: }
441: return null;
442: }
443:
444: private static List fetchComponents(UIContainer basecontainer,
445: String id) {
446: Object togo = null;
447: while (basecontainer != null) {
448: togo = basecontainer.getComponents(id);
449: if (togo != null)
450: break;
451: basecontainer = basecontainer.parent;
452: }
453: return togo == null ? null
454: : (togo instanceof List ? (List) togo : ListUtil
455: .instance(togo));
456: }
457:
458: private XMLLump findChild(XMLLump sourcescope, UIComponent child) {
459: // if child is not a container, there can be no lookahead in resolution,
460: // and it must resolve to a component in THIS container which either
461: // matches exactly or in prefix.
462: SplitID split = new SplitID(child.ID);
463: XMLLumpList headlumps = sourcescope.downmap
464: .headsForID(child.ID);
465: if (headlumps == null) {
466: headlumps = sourcescope.downmap.headsForID(split.prefix
467: + SplitID.SEPARATOR);
468: // if (headlumps.size() == 0) {
469: // throw UniversalRuntimeException.accumulate(new IOException(),
470: // "Error in template file: peer for component with ID " + child.ID
471: // + " not found in scope " + sourcescope.toDebugString());
472: // }
473: }
474: return headlumps == null ? null : headlumps.lumpAt(0);
475: }
476:
477: private void dumpBranchHead(UIBranchContainer branch,
478: XMLLump targetlump) {
479: HashMap attrcopy = new HashMap();
480: attrcopy.putAll(targetlump.attributemap);
481: IDassigner.adjustForID(attrcopy, branch);
482: decoratormanager.decorate(branch.decorators, targetlump
483: .getTag(), attrcopy);
484: // TODO: normalise this silly space business
485: pos.write(targetlump.parent.buffer, targetlump.start,
486: targetlump.length - 1);
487: XMLUtil.dumpAttributes(attrcopy, xmlw);
488: pos.print(">");
489: }
490:
491: public static String debugLump(XMLLump debug) {
492: XMLLump[] lumps = debug.parent.lumps;
493: CharWrap message = new CharWrap();
494: message.append("Lump index " + debug.lumpindex + " (line "
495: + debug.line + " column " + debug.column + ") ");
496: int frontpoint = debug.lumpindex - 5;
497: if (frontpoint < 0)
498: frontpoint = 0;
499: int endpoint = debug.lumpindex + 5;
500: if (frontpoint > lumps.length)
501: frontpoint = lumps.length;
502: for (int i = frontpoint; i < endpoint; ++i) {
503: if (i == debug.lumpindex) {
504: message.append("(*)");
505: }
506: XMLLump lump = lumps[i];
507: message.append(lump.parent.buffer, lump.start, lump.length);
508: }
509: if (debug.downmap != null) {
510: message.append("\nDownmap here: ").append(
511: debug.downmap.getHeadsDebug());
512: }
513: return message.toString();
514: }
515:
516: }
|