1 // Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
5 import java.util.zip.*;
12 * Encapsulates a template node (the <template/> element of a
13 * .xwt file, or any child element thereof).
15 * Note that the Template instance corresponding to the
16 * <template/> node carries all the header information -- hence
17 * some of the instance members are not meaningful on non-root
18 * Template instances. We refer to these non-root instances as
19 * <i>anonymous templates</i>.
21 * See the XWT reference for information on the order in which
22 * templates are applied, attributes are put, and scripts are run.
26 public class Template {
28 // Instance Members ///////////////////////////////////////////////////////
30 /** the id of this box */
33 /** the id of the redirect target; only meaningful on a root node */
34 String redirect = null;
36 /** templates that should be preapplied (in the order of application); only meaningful on a root node */
37 private Template[] preapply;
39 /** templates that should be postapplied (in the order of application); only meaningful on a root node */
40 private Template[] postapply;
42 /** keys to be "put" to instances of this template; elements correspond to those of vals */
43 private String[] keys;
45 /** values to be "put" to instances of this template; elements correspond to those of keys */
46 private Object[] vals;
48 /** child template objects */
49 private Template[] children;
51 /** see numUnits(); -1 means that this value has not yet been computed */
52 private int numunits = -1;
54 /** the scope in which the static block is executed */
55 private JS.Scope staticScope = null;
57 /** the script on the static node of this template, null if it has already been executed */
58 private JS.CompiledFunction staticscript = null;
60 /** the script on this node */
61 private JS.CompiledFunction script = null;
63 /** the filename this node came from; used only for debugging */
64 private String fileName = "unknown";
67 // Only used during parsing /////////////////////////////////////////////////////////////////
69 /** during XML parsing, this holds the list of currently-parsed children; null otherwise */
70 private Vec childvect = new Vec();
72 /** during XML parsing, this holds partially-read character data; null otherwise */
73 private StringBuffer content = null;
75 /** line number of the first line of <tt>content</tt> */
76 private int content_start = 0;
78 /** number of lines in <tt>content</tt> */
79 private int content_lines = 0;
81 /** the line number that this element starts on */
82 private int startLine = -1;
85 // Static data/methods ///////////////////////////////////////////////////////////////////
87 private Template(String fileName) { this.fileName = fileName; }
89 public static Template getTemplate(Res r) {
91 if (r.t != null) return r.t;
92 r.t = new Template(r.getDescriptiveName());
93 new TemplateHelper().parseit(r.getInputStream(), r.t);
95 } catch (XML.SchemaException e) {
96 if (Log.on) Log.log(Template.class, "error parsing template " + r.t.fileName);
97 if (Log.on) Log.log(Template.class, e.getMessage());
99 } catch (XML.XMLException e) {
100 if (Log.on) Log.log(Template.class, "error parsing template at " + r.t.fileName + ":" + e.getLine() + "," + e.getCol());
101 if (Log.on) Log.log(Template.class, e.getMessage());
103 } catch (IOException e) {
104 if (Log.on) Log.log(Template.class, "IOException while parsing template " + r.t.fileName + " -- this should never happen");
105 if (Log.on) Log.log(Template.class, e);
111 // Methods to apply templates ////////////////////////////////////////////////////////
113 /** calculates, caches, and returns an integer approximation of how long it will take to apply this template,
114 * including pre/post and children */
116 if (numunits != -1) return numunits;
118 for(int i=0; preapply != null && i<preapply.length; i++) numunits += preapply[i].numUnits();
119 for(int i=0; postapply != null && i<postapply.length; i++) numunits += postapply[i].numUnits();
120 if (script != null) numunits += 10;
121 numunits += keys == null ? 0 : keys.length;
122 for(int i=0; children != null && i<children.length; i++) numunits += children[i].numUnits();
126 /** called before this template is applied or its static object can be externally referenced */
127 JS.Scope getStatic() {
128 if (staticScope == null) staticScope = new JS.Scope(null);
129 if (staticscript == null) return staticScope;
130 JS.CompiledFunction temp = staticscript;
132 temp.call(new JS.Array(), staticScope);
136 /** Applies the template to Box b
137 * @param pboxes a vector of all box parents on which to put $-references
138 * @param ptemplates a vector of the fileNames to recieve private references on the pboxes
140 // FIXME: $-vars not dealt with
141 void apply(Box b, JS.Callable callback, int numerator, int denominator, Res resourceRoot) {
144 int original_numerator = numerator;
146 for(int i=0; preapply != null && i<preapply.length; i++) {
147 preapply[i].apply(b, callback, numerator, denominator, resourceRoot);
148 numerator += preapply[i].numUnits();
151 for (int i=0; children != null && i<children.length; i++) {
152 children[i].apply(new Box(), callback, numerator, denominator, resourceRoot);
153 numerator += children[i].numUnits();
156 // whom to redirect to; doesn't take effect until after script runs
157 Box redir = (redirect != null && !"self".equals(redirect)) ? (Box)b.get("$" + redirect) : null;
159 if (script != null) script.call(new JS.Array(), new PerInstantiationScope(b, resourceRoot));
161 for(int i=0; keys != null && i<keys.length; i++) b.put(keys[i], vals[i]);
163 if (redirect != null && !"self".equals(redirect)) b.redirect = redir;
165 for(int i=0; postapply != null && i<postapply.length; i++) {
166 postapply[i].apply(b, callback, numerator, denominator, resourceRoot);
167 numerator += postapply[i].numUnits();
170 numerator = original_numerator + numUnits();
172 if (callback != null) try {
173 JS.Array args = new JS.Array();
174 args.addElement(new Double(numerator));
175 args.addElement(new Double(denominator));
177 } catch (JS.Exn e) { if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e); }
179 if (Thread.currentThread() instanceof ThreadMessage) XWT.sleep(0);
184 // XML Parsing /////////////////////////////////////////////////////////////////
186 /** handles XML parsing; builds a Template tree as it goes */
187 static final class TemplateHelper extends XML {
191 /** parse an XML input stream, building a Template tree off of <tt>root</tt> */
192 void parseit(InputStream is, Template root) throws XML.XMLException, IOException {
193 rootNodeHasBeenEncountered = false;
194 templateNodeHasBeenEncountered = false;
195 staticNodeHasBeenEncountered = false;
196 templateNodeHasBeenFinished = false;
197 nameOfHeaderNodeBeingProcessed = null;
199 nodeStack.setSize(0);
201 postapply.setSize(0);
204 parse(new InputStreamReader(is));
207 /** parsing state: true iff we have already encountered the <xwt> open-tag */
208 boolean rootNodeHasBeenEncountered = false;
210 /** parsing state: true iff we have already encountered the <template> open-tag */
211 boolean templateNodeHasBeenEncountered = false;
213 /** parsing state: true iff we have already encountered the <static> open-tag */
214 boolean staticNodeHasBeenEncountered = false;
216 /** parsing state: true iff we have already encountered the <template> close-tag */
217 boolean templateNodeHasBeenFinished = false;
219 /** parsing state: If we have encountered the open tag of a header node, but not the close tag, this is the name of
220 * that tag; otherwise, it is null. */
221 String nameOfHeaderNodeBeingProcessed = null;
223 /** stack of Templates whose XML elements we have seen open-tags for but not close-tags */
224 Vec nodeStack = new Vec();
226 /** builds up the list of preapplies */
227 Vec preapply = new Vec();
229 /** builds up the list of postapplies */
230 Vec postapply = new Vec();
232 /** the template we're currently working on */
235 public void startElement(XML.Element c) throws XML.SchemaException {
236 if (templateNodeHasBeenFinished) {
237 throw new XML.SchemaException("no elements may appear after the <template> node");
239 } else if (!rootNodeHasBeenEncountered) {
240 if (!"xwt".equals(c.localName)) throw new XML.SchemaException("root element was not <xwt>");
241 if (c.len != 0) throw new XML.SchemaException("root element must not have attributes");
242 rootNodeHasBeenEncountered = true;
245 } else if (!templateNodeHasBeenEncountered) {
246 if (nameOfHeaderNodeBeingProcessed != null) throw new XML.SchemaException("can't nest header nodes");
247 nameOfHeaderNodeBeingProcessed = c.localName;
249 if (c.localName.equals("import")) {
250 if (c.len != 1 || !c.keys[0].equals("name"))
251 throw new XML.SchemaException("<import> node must have exactly one attribute, which must be called 'name'");
252 String importpackage = c.vals[0].toString();
253 if (importpackage.endsWith(".*")) importpackage = importpackage.substring(0, importpackage.length() - 2);
256 } else if (c.localName.equals("redirect")) {
257 if (c.len != 1 || !c.keys[0].equals("target"))
258 throw new XML.SchemaException("<redirect> node must have exactly one attribute, which must be called 'target'");
259 if (t.redirect != null)
260 throw new XML.SchemaException("the <redirect> header element may not appear more than once");
261 t.redirect = c.vals[0].toString();
262 if(t.redirect.equals("null")) t.redirect = null;
265 } else if (c.localName.equals("preapply")) {
266 if (c.len != 1 || !c.keys[0].equals("name"))
267 throw new XML.SchemaException("<preapply> node must have exactly one attribute, which must be called 'name'");
268 preapply.addElement(c.vals[0]);
271 } else if (c.localName.equals("postapply")) {
272 if (c.len != 1 || !c.keys[0].equals("name"))
273 throw new XML.SchemaException("<postapply> node must have exactly one attribute, which must be called 'name'");
274 postapply.addElement(c.vals[0]);
277 } else if (c.localName.equals("static")) {
278 if (staticNodeHasBeenEncountered)
279 throw new XML.SchemaException("the <static> header node may not appear more than once");
281 throw new XML.SchemaException("the <static> node may not have attributes");
282 staticNodeHasBeenEncountered = true;
285 } else if (c.localName.equals("template")) {
286 // finalize importlist/preapply/postapply, since they can't change from here on
287 t.startLine = getLine();
288 if (preapply.size() > 0) preapply.copyInto(t.preapply = new Template[preapply.size()]);
289 if (postapply.size() > 0) postapply.copyInto(t.postapply = new Template[postapply.size()]);
290 templateNodeHasBeenEncountered = true;
293 throw new XML.SchemaException("unrecognized header node \"" + c.localName + "\"");
299 // push the last node we were in onto the stack
300 nodeStack.addElement(t);
302 // instantiate a new node, and set its fileName/importlist/preapply
303 Template t2 = new Template(t.fileName);
304 t2.startLine = getLine();
305 if (!c.localName.equals("box")) t2.preapply = new Template[] { /*c.localName FIXME */ };
307 // make the new node the current node
312 // TODO: Sort contents straight from one array to another
313 // FIXME: height must come after image
314 // FIXME: use Vec here
315 t.keys = new String[c.len];
316 t.vals = new Object[c.len];
317 System.arraycopy(c.keys, 0, t.keys, 0, c.len);
318 System.arraycopy(c.vals, 0, t.vals, 0, c.len);
319 quickSortAttributes(0, t.keys.length - 1);
321 for(int i=0; i<t.keys.length; i++) {
322 if (t.keys[i].equals("id")) {
323 t.id = t.vals[i].toString().intern();
328 t.keys[i] = t.keys[i].intern();
330 String valString = t.vals[i].toString();
332 if (valString.equals("true")) t.vals[i] = Boolean.TRUE;
333 else if (valString.equals("false")) t.vals[i] = Boolean.FALSE;
334 else if (valString.equals("null")) t.vals[i] = null;
336 boolean hasNonNumeral = false;
337 boolean periodUsed = false;
338 for(int j=0; j<valString.length(); j++)
339 if (j == 0 && valString.charAt(j) == '-') {
340 } else if (valString.charAt(j) == '.' && !periodUsed && j != valString.length() - 1) {
342 } else if (!Character.isDigit(valString.charAt(j))) {
343 hasNonNumeral = true;
346 if (valString.length() > 0 && !hasNonNumeral) t.vals[i] = new Double(valString);
347 else t.vals[i] = valString.intern();
350 // bump thisbox to the front of the pack
351 if (t.keys[i].equals("thisbox")) {
352 t.keys[i] = t.keys[0];
353 t.keys[0] = "thisbox";
354 Object o = t.vals[0];
355 t.vals[0] = t.vals[i];
361 /** simple quicksort, from http://sourceforge.net/snippet/detail.php?type=snippet&id=100240 */
362 private int partitionAttributes(int left, int right) {
364 middle = (left + right) / 2;
365 String s = t.keys[right]; t.keys[right] = t.keys[middle]; t.keys[middle] = s;
366 Object o = t.vals[right]; t.vals[right] = t.vals[middle]; t.vals[middle] = o;
367 for (i = left - 1, j = right; ; ) {
368 while (t.keys[++i].compareTo(t.keys[right]) < 0);
369 while (j > left && t.keys[--j].compareTo(t.keys[right]) > 0);
371 s = t.keys[i]; t.keys[i] = t.keys[j]; t.keys[j] = s;
372 o = t.vals[i]; t.vals[i] = t.vals[j]; t.vals[j] = o;
374 s = t.keys[right]; t.keys[right] = t.keys[i]; t.keys[i] = s;
375 o = t.vals[right]; t.vals[right] = t.vals[i]; t.vals[i] = o;
379 /** simple quicksort, from http://sourceforge.net/snippet/detail.php?type=snippet&id=100240 */
380 private void quickSortAttributes(int left, int right) {
381 if (left >= right) return;
382 int p = partitionAttributes(left, right);
383 quickSortAttributes(left, p - 1);
384 quickSortAttributes(p + 1, right);
387 public void endElement(XML.Element c) throws XML.SchemaException {
388 if (rootNodeHasBeenEncountered && !templateNodeHasBeenEncountered) {
389 if ("static".equals(nameOfHeaderNodeBeingProcessed) && t.content != null) t.staticscript = genscript(true);
390 nameOfHeaderNodeBeingProcessed = null;
392 } else if (templateNodeHasBeenEncountered && !templateNodeHasBeenFinished) {
393 // turn our childvect into a Template[]
394 t.childvect.copyInto(t.children = new Template[t.childvect.size()]);
396 if (t.content != null) t.script = genscript(false);
398 if (nodeStack.size() == 0) {
400 templateNodeHasBeenFinished = true;
403 // add this template as a child of its parent
405 t = (Template)nodeStack.lastElement();
406 nodeStack.setSize(nodeStack.size() - 1);
407 t.childvect.addElement(oldt);
413 private JS.CompiledFunction genscript(boolean isstatic) {
414 JS.CompiledFunction thisscript = null;
416 thisscript = JS.parse(t.fileName + (isstatic ? "._" : ""), t.content_start, new StringReader(t.content.toString()));
417 } catch (IOException ioe) {
418 if (Log.on) Log.log(this, " ERROR: " + ioe.getMessage());
428 public void characters(char[] ch, int start, int length) throws XML.SchemaException {
429 // invoke the no-tab crusade
430 for (int i=0; length >i; i++) if (ch[start+i] == '\t') throw new XML.SchemaException(
431 t.fileName+ ":" + getLine() + "," + getCol() + ": tabs are not allowed in XWT files");
433 if ("static".equals(nameOfHeaderNodeBeingProcessed) || templateNodeHasBeenEncountered) {
434 if (t.content == null) {
435 t.content_start = getLine();
437 t.content = new StringBuffer();
440 t.content.append(ch, start, length);
443 } else if (nameOfHeaderNodeBeingProcessed != null) {
444 throw new XML.SchemaException("header node <" + nameOfHeaderNodeBeingProcessed + "> cannot have text content");
448 public void whitespace(char[] ch, int start, int length) throws XML.SchemaException {
452 private static class PerInstantiationScope extends JS.Scope {
453 Res resourceRoot = null;
454 public PerInstantiationScope(Scope parentScope, Res resourceRoot) {
456 this.resourceRoot = resourceRoot;
458 public boolean isTransparent() { return true; }
459 public boolean has(Object key) { return false; }
460 public void declare(String s) { super.declare(s); }
461 public Object get(Object key) {
462 // FIXME: access statics here
463 if (Box.SpecialBoxProperty.specialBoxProperties.get(key) == null &&
465 Object ret = resourceRoot.get(key);
466 if (ret != null) return ret;
467 throw new JS.Exn("must declare " + key + " before using it!");
469 return super.get(key);
471 public void put(Object key, Object val) {
472 // FIXME: access statics here
473 if (Box.SpecialBoxProperty.specialBoxProperties.get(key) == null &&
475 throw new JS.Exn("must declare " + key + " before using it!");