KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openlaszlo > compiler > NodeModel


1 /* ***************************************************************************
2  * NodeModel.java
3  * ***************************************************************************/

4
5 /* J_LZ_COPYRIGHT_BEGIN *******************************************************
6 * Copyright 2001-2004 Laszlo Systems, Inc. All Rights Reserved. *
7 * Use is subject to license terms. *
8 * J_LZ_COPYRIGHT_END *********************************************************/

9
10 package org.openlaszlo.compiler;
11
12 import java.io.*;
13 import java.text.ChoiceFormat JavaDoc;
14 import java.util.*;
15
16 import org.openlaszlo.compiler.ViewSchema.ColorFormatException;
17 import org.openlaszlo.css.CSSParser;
18 import org.openlaszlo.sc.Function;
19 import org.openlaszlo.sc.ScriptCompiler;
20 import org.openlaszlo.server.*;
21 import org.openlaszlo.utils.ChainedException;
22 import org.openlaszlo.utils.ListFormat;
23 import org.openlaszlo.utils.ComparisonMap;
24 import org.openlaszlo.xml.internal.MissingAttributeException;
25 import org.openlaszlo.xml.internal.Schema;
26 import org.openlaszlo.xml.internal.XMLUtils;
27 import org.apache.oro.text.regex.*;
28 import org.apache.commons.collections.CollectionUtils;
29 import org.jdom.Attribute;
30 import org.jdom.Element;
31 import org.jdom.Namespace;
32
33 /** Models a runtime LzNode. */
34 public class NodeModel implements Cloneable JavaDoc {
35     public static final String JavaDoc FONTSTYLE_ATTRIBUTE = "fontstyle";
36     public static final String JavaDoc WHEN_IMMEDIATELY = "immediately";
37     public static final String JavaDoc WHEN_ONCE = "once";
38     public static final String JavaDoc WHEN_ALWAYS = "always";
39     public static final String JavaDoc WHEN_PATH = "path";
40     private static final String JavaDoc SOURCE_LOCATION_ATTRIBUTE_NAME = "__LZsourceLocation";
41
42     protected final ViewSchema schema;
43     protected final Element element;
44     protected String JavaDoc className;
45     protected String JavaDoc id = null;
46     protected ComparisonMap attrs = new ComparisonMap();
47     protected List children = new Vector();
48     /** A set {eventName: String -> True) of names of event handlers
49      * declared with <method event="xxx"/>. */

50     protected ComparisonMap delegates = new ComparisonMap();
51     protected ComparisonMap events = new ComparisonMap();
52     protected ComparisonMap references = new ComparisonMap();
53     protected ComparisonMap paths = new ComparisonMap();
54     protected ComparisonMap setters = new ComparisonMap();
55     /** [eventName: String, methodName: String, Function] */
56     protected List delegateList = new Vector();
57     protected ClassModel parentClassModel;
58     protected String JavaDoc initstage = null;
59     protected int totalSubnodes = 1;
60     protected final CompilationEnvironment env;
61
62     public Object JavaDoc clone() {
63         NodeModel copy;
64         try {
65             copy = (NodeModel) super.clone();
66         } catch (CloneNotSupportedException JavaDoc e) {
67             throw new RuntimeException JavaDoc(e);
68         }
69         copy.attrs = new ComparisonMap(copy.attrs);
70         copy.delegates = new ComparisonMap(copy.delegates);
71         copy.events = new ComparisonMap(copy.events);
72         copy.references = new ComparisonMap(copy.references);
73         copy.paths = new ComparisonMap(copy.paths);
74         copy.setters = new ComparisonMap(copy.setters);
75         copy.delegateList = new Vector(copy.delegateList);
76         copy.children = new Vector();
77         for (Iterator iter = children.iterator(); iter.hasNext(); ) {
78             copy.children.add(((NodeModel) iter.next()).clone());
79         }
80         return copy;
81     }
82
83     NodeModel(Element element, ViewSchema schema, CompilationEnvironment env) {
84         this.element = element;
85         this.schema = schema;
86         this.env = env;
87
88         this.className = element.getName();
89         // Cache ClassModel for parent
90
this.parentClassModel = this.getParentClassModel();
91         this.initstage =
92             this.element.getAttributeValue("initstage");
93         if (this.initstage != null) {
94             this.initstage = this.initstage.intern();
95         }
96
97         // Get initial node count from superclass
98
// TODO: [2003-05-04] Extend this mechanism to cache/model
99
// all relevant superclass info
100
// TODO: [2003-05-04 ptw] How can we get this info for
101
// instances of built-in classes?
102
if (this.parentClassModel != null) {
103             ElementWithLocationInfo parentElement =
104                 (ElementWithLocationInfo) this.parentClassModel.definition;
105             // TODO: [2003-05-04 ptw] As above, but a class
106
// that extends a built-in class
107
if (parentElement != null) {
108                 // TODO: [2003-05-04 ptw] An instantiation of
109
// a class that has not been modelled yet --
110
// do enough modelling to get what is needed.
111
if (parentElement.model != null) {
112                     this.totalSubnodes =
113                         parentElement.model.classSubnodes();
114                     if (this.initstage == null) {
115                         this.initstage =
116                             parentElement.model.initstage;
117                     }
118                 }
119             }
120         }
121     }
122
123     private static final String JavaDoc DEPRECATED_METHODS_PROPERTY_FILE = (
124         LPS.getMiscDirectory() + File.separator + "lzx-deprecated-methods.properties"
125         );
126     private static final Properties sDeprecatedMethods = new Properties();
127
128     static {
129         try {
130             InputStream is = new FileInputStream(DEPRECATED_METHODS_PROPERTY_FILE);
131             try {
132                 sDeprecatedMethods.load(is);
133             } finally {
134                 is.close();
135             }
136         } catch (java.io.IOException JavaDoc e) {
137             throw new ChainedException(e);
138         }
139     }
140
141
142     /* List of flash builtins to warn about if the user tries to redefine them */
143     private static final String JavaDoc FLASH6_BUILTINS_PROPERTY_FILE = (
144         LPS.getMiscDirectory() + File.separator + "flash6-builtins.properties"
145         );
146
147
148     private static final String JavaDoc FLASH7_BUILTINS_PROPERTY_FILE = (
149         LPS.getMiscDirectory() + File.separator + "flash7-builtins.properties"
150         );
151
152     public static final Properties sFlash6Builtins = new Properties();
153     public static final Properties sFlash7Builtins = new Properties();
154
155     static {
156         try {
157             InputStream is6 = new FileInputStream(FLASH6_BUILTINS_PROPERTY_FILE);
158             try {
159                 sFlash6Builtins.load(is6);
160             } finally {
161                 is6.close();
162             }
163
164             InputStream is7 = new FileInputStream(FLASH7_BUILTINS_PROPERTY_FILE);
165             try {
166                 sFlash7Builtins.load(is7);
167             } finally {
168                 is7.close();
169             }
170         } catch (java.io.IOException JavaDoc e) {
171             throw new ChainedException(e);
172         }
173     }
174
175     static class CompiledAttribute {
176         static final int ATTRIBUTE = 0;
177         static final int EVENT = 1;
178         static final int REFERENCE = 2;
179         static final int PATH = 3;
180
181         final int type;
182         final Object JavaDoc value;
183
184         CompiledAttribute(int type, Object JavaDoc value) {
185             this.type = type;
186             this.value = value;
187         }
188         CompiledAttribute(Object JavaDoc value) {
189             this(ATTRIBUTE, value);
190         }
191     }
192
193     public String JavaDoc toString() {
194         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
195         buffer.append("{NodeModel class=" + className);
196         if (!attrs.isEmpty())
197             buffer.append(" attrs=" + attrs.keySet());
198         if (!delegates.isEmpty())
199             buffer.append(" delegates=" + delegates.keySet());
200         if (!events.isEmpty())
201             buffer.append(" events=" + events.keySet());
202         if (!references.isEmpty())
203             buffer.append(" references=" + references.keySet());
204         if (!paths.isEmpty())
205             buffer.append(" paths=" + paths.keySet());
206         if (!setters.isEmpty())
207             buffer.append(" setters=" + setters.keySet());
208         if (!delegateList.isEmpty())
209             buffer.append(" delegateList=" + delegateList);
210         if (!children.isEmpty())
211             buffer.append(" children=" + children);
212         buffer.append("}");
213         return buffer.toString();
214     }
215
216     List getChildren() {
217         return children;
218     }
219
220     public static boolean isPropertyElement(Element elt) {
221         String JavaDoc name = elt.getName();
222         return name.equals("attribute") || name.equals("method");
223     }
224
225     /** Returns a name that is used to report this element in warning
226      * messages. */

227     String JavaDoc getMessageName() {
228         return "element " + element.getName();
229     }
230
231     /**
232      * Returns a script that creates a runtime representation of a
233      * model. The format of this representation is specified <a
234      * HREF="../../../../doc/compiler/views.html">here</a>.
235      *
236      * @param elt an element
237      * @param schema a schema, used to encode attribute values
238      * @param env the CompilationEnvironment
239      * @return see doc
240      */

241     public String JavaDoc asJavascript() {
242         try {
243             java.io.Writer JavaDoc writer = new java.io.StringWriter JavaDoc();
244             ScriptCompiler.writeObject(this.asMap(), writer);
245             return writer.toString();
246         } catch (java.io.IOException JavaDoc e) {
247             throw new ChainedException(e);
248         }
249     }
250
251     /** Returns true iff clickable should default to true. */
252     private static boolean computeDefaultClickable(ViewSchema schema,
253                                                    Map attrs,
254                                                    Map events,
255                                                    Map delegates) {
256         if ("true".equals(attrs.get("cursor"))) {
257             return true;
258         }
259         for (Iterator iter = events.keySet().iterator(); iter.hasNext();) {
260             String JavaDoc eventName = (String JavaDoc) iter.next();
261             if (schema.isMouseEventAttribute(eventName)) {
262                 return true;
263             }
264         }
265         for (Iterator iter = delegates.keySet().iterator(); iter.hasNext();) {
266             String JavaDoc eventName = (String JavaDoc) iter.next();
267             if (schema.isMouseEventAttribute(eventName)) {
268                 return true;
269             }
270         }
271         return false;
272     }
273
274     /**
275      * Returns a NodeModel that represents an Element, including the
276      * element's children
277      *
278      * @param elt an element
279      * @param schema a schema, used to encode attribute values
280      * @param env the CompilationEnvironment
281      */

282     public static NodeModel elementAsModel(Element elt, ViewSchema schema,
283                                            CompilationEnvironment env) {
284         return elementAsModelInternal(elt, schema, true, env);
285     }
286
287     /**
288      * Returns a NodeModel that represents an Element, excluding the
289      * element's children
290      *
291      * @param elt an element
292      * @param schema a schema, used to encode attribute values
293      * @param env the CompilationEnvironment
294      */

295     public static NodeModel elementOnlyAsModel(Element elt, ViewSchema schema,
296                                                CompilationEnvironment env) {
297         return elementAsModelInternal(elt, schema, false, env);
298     }
299
300     /** Returns a NodeModel that represents an Element
301      *
302      * @param elt an element
303      * @param schema a schema, used to encode attribute values
304      * @param includeChildren whether or not to include children
305      * @param env the CompilationEnvironment
306      */

307     private static NodeModel elementAsModelInternal(
308         Element elt, ViewSchema schema,
309         boolean includeChildren, CompilationEnvironment env)
310     {
311             NodeModel model = new NodeModel(elt, schema, env);
312             ComparisonMap attrs = model.attrs;
313             Map events = model.events;
314             Map delegates = model.delegates;
315             model.addAttributes(env);
316             if (includeChildren) {
317                 model.addChildren(env);
318                 model.addText();
319                 if (!attrs.containsKey("clickable")
320                     && computeDefaultClickable(schema, attrs, events, delegates)) {
321                     attrs.put("clickable", "true");
322                 }
323             }
324             // Record the model in the element for classes
325
((ElementWithLocationInfo) elt).model = model;
326             return model;
327         }
328
329     // Calculate how many nodes this object will put on the
330
// instantiation queue.
331
int totalSubnodes() {
332         // A class does not instantiate its subnodes.
333
// States override LzNode.thaw to delay subnodes.
334
// FIXME [2004-06-3 ows]: This won't work for subclasses
335
// of state.
336
if (ClassCompiler.isElement(element) ||
337             className.equals("state")) {
338             return 1;
339         }
340         // initstage late, defer delay subnodes
341
if (this.initstage != null &&
342             (this.initstage == "late" ||
343              this.initstage == "defer")) {
344             return 0;
345         }
346         return this.totalSubnodes;
347     }
348
349     // How many nodes will be inherited from this class
350
int classSubnodes() {
351         if (ClassCompiler.isElement(element)) {
352             return this.totalSubnodes;
353         }
354         return 0;
355     }
356
357     ClassModel getClassModel() {
358         return schema.getClassModel(this.className);
359     }
360
361     /** Gets the ClassModel for this element's parent class. If this
362      * element is a <class> definition, the superclass; otherwise the
363      * class of the tag of this element. */

364     ClassModel getParentClassModel() {
365         String JavaDoc parentName = this.className;
366         return
367             parentName.equals("class")?
368             schema.getClassModel(element.getAttributeValue("extends")):
369             schema.getClassModel(parentName);
370     }
371
372     void setClassName(String JavaDoc name) {
373         this.className = name;
374         this.parentClassModel = getParentClassModel();
375     }
376
377     // Should only be called on a <class> definition element.
378
ViewSchema.Type getAttributeTypeInfoFromSuperclass(
379         Element classDefElement, String JavaDoc attrname)
380         throws UnknownAttributeException
381     {
382         String JavaDoc superclassname = classDefElement.getAttributeValue("extends", ClassCompiler.DEFAULT_SUPERCLASS_NAME);
383         ClassModel superclassModel = schema.getClassModel(superclassname);
384
385         if (superclassModel == null) {
386             throw new CompilationError("Could not find superclass info for class " + superclassname, classDefElement);
387         }
388
389         // Check if this attribute is defined on the parent class, if
390
// so, return that type
391
AttributeSpec attr = superclassModel.getAttribute(attrname);
392         if (attr != null) {
393             return attr.type;
394         }
395         // Otherwise, check if it's defined on the "class" element
396
// (e.g., 'name' or 'extends')
397
superclassModel = schema.getClassModel("class");
398         return superclassModel.getAttributeTypeOrException(attrname);
399     }
400
401     // Get an attribute value, defaulting to the
402
// inherited value, or ultimately the supplied default
403
String JavaDoc getAttributeValueDefault(String JavaDoc attribute,
404                                     String JavaDoc name,
405                                     String JavaDoc defaultValue) {
406         // Look for an inherited value
407
if (this.parentClassModel != null) {
408             AttributeSpec attrSpec =
409                 this.parentClassModel.getAttribute(attribute);
410             if (attrSpec != null) {
411                 Element source = attrSpec.source;
412                 if (source != null) {
413                     return XMLUtils.getAttributeValue(source, name, defaultValue);
414                 }
415             }
416         }
417
418         return defaultValue;
419     }
420
421     /** Is this element a direct child of the canvas? */
422     // FIXME [2004-06-03 ows]: Use CompilerUtils.isTopLevel instead.
423
// This implementation misses children of <library> and <switch>.
424
// Since it's only used for compiler warnings about duplicate
425
// names this doesn't break program compilation.
426
boolean topLevelDeclaration() {
427         Element parent = element.getParent();
428         if (parent == null) {
429             return false;
430         }
431         return ("canvas".equals(parent.getName()));
432     }
433
434     void addAttributes(CompilationEnvironment env) {
435         boolean swf7 = env.getSWFVersion().equals("swf7");
436         // Add source locators, if requested. Added here because it
437
// is not in the schema
438
if (env.getBooleanProperty(env.SOURCELOCATOR_PROPERTY)) {
439             String JavaDoc location = "document(" +
440                 ScriptCompiler.quote(Parser.getSourceMessagePathname(element)) +
441                 ")" +
442                 XMLUtils.getXPathTo(element);
443             CompiledAttribute cattr = compileAttribute(
444                 element, SOURCE_LOCATION_ATTRIBUTE_NAME,
445                 location, ViewSchema.STRING_TYPE,
446                 WHEN_IMMEDIATELY);
447             addAttribute(cattr, SOURCE_LOCATION_ATTRIBUTE_NAME,
448                          attrs, events, references, paths);
449         }
450
451         // Encode the attributes
452
for (Iterator iter = element.getAttributes().iterator(); iter.hasNext(); ) {
453             Attribute attr = (Attribute) iter.next();
454             Namespace ns = attr.getNamespace();
455             String JavaDoc name = attr.getName();
456             String JavaDoc value = element.getAttributeValue(name, ns);
457
458             if (name.equals(FONTSTYLE_ATTRIBUTE)) {
459                 // "bold italic", "italic bold" -> "bolditalic"
460
value = FontInfo.normalizeStyleString(value, false);
461             }
462
463             if (name.toLowerCase().equals("datacontrolsvisibility")) {
464                 env.warn("The attribute \"datacontrolsvisibility\" is deprecated. "+
465                          "Use visible=\"null\" instead. "+
466                          "For future compatibility you should make this change to your source code.",
467                          element);
468             }
469
470             if (name.toLowerCase().equals("defaultplacement")) {
471                 if (value != null && value.matches("\\s*['\"]\\S*['\"]\\s*")) {
472                     String JavaDoc oldValue = value;
473                     // strip off start and ending quotes;
474
value = value.trim();
475                     value = value.substring(1, value.length()-1);
476                     env.warn(
477                         "Replacing defaultPlacement=\"" + oldValue +
478                         "\" by \"" + value + "\". For future compatability" +
479                         ", you should make this change to your source code.",
480                         element);
481                 }
482             }
483
484             
485
486             // Warn for redefine of a flash builtin
487
if ((name.equals("id") || name.equals("name")) &&
488                  (value != null &&
489                   (swf7 ?
490                    sFlash7Builtins.containsKey(value) :
491                    sFlash6Builtins.containsKey(value.toLowerCase())))) {
492                 env.warn(
493                     "You have given the "+getMessageName()+
494                     " an attribute "+name+"=\""+value+"\", "+
495                     "which may overwrite the Flash builtin class named \""+value+"\".",
496                     element);
497
498             }
499
500             // Catch duplicated id/name attributes which may shadow
501
// each other or overwrite each other. An id/name will be
502
// global there is "id='foo'" or if "name='foo'" at the
503
// top level (immediate child of the canvas).
504
//
505
// NB: since this finds class names via a lookup from
506
// elements in the schema, it will give some false
507
// positives on class-name collisions, such as tag names
508
// like "audio" which do not actually correspond to a LFC
509
// class at runtime.
510
if ((name.equals("id")) ||
511                 (name.equals("name") &&
512                  topLevelDeclaration() && !className.equals("class"))) {
513
514                 ClassModel superclassModel = schema.getClassModel(value);
515                 if (superclassModel != null && !superclassModel.isBuiltin()) {
516                     env.warn(
517                         "You have given the "+getMessageName()+
518                         " an attribute "+name+"=\""+value+"\", "+
519                         "which may overwrite the class \""+value+"\".",
520                         element);
521                 } else {
522                     ElementWithLocationInfo dup =
523                         (ElementWithLocationInfo) env.getId(value);
524                     // we don't want to give a warning in the case
525
// where the id and name are on the same element,
526
// i.e., <view id="foo" name="foo"/>
527
if (dup != null && dup != element) {
528                         String JavaDoc locstring =
529                             CompilerUtils.sourceLocationPrettyString(dup);
530                         env.warn(
531                             "Duplicate id attribute \""+value+"\" at "+locstring,
532                             element);
533                     } else {
534                         // TODO: [07-18-03 hqm] We will canonicalize
535
// all id's to lowercase, because actionscript
536
// is not case sensitive. but in the future,
537
// we should preserve case.
538
env.addId(value, element);
539                     }
540                 }
541             }
542
543             // Special case, if we are compiling a "class" tag,
544
// then get the type of attributes from the
545
// superclass.
546
Schema.Type type;
547             try {
548                 if (className.equals("class")) {
549                     type = getAttributeTypeInfoFromSuperclass(element, name);
550                 } else {
551                     type = schema.getAttributeType(element, name);
552                 }
553
554             } catch (UnknownAttributeException e) {
555                 String JavaDoc solution;
556                 AttributeSpec alt = schema.findSimilarAttribute(className, name);
557                 if (alt != null) {
558                     String JavaDoc classmessage = "";
559                     if (alt.source != null) {
560                         classmessage = " on class "+alt.source.getName()+"\"";
561                     } else {
562                         classmessage = " on class "+getMessageName();
563                     }
564                     solution = "found an unknown attribute named \""+name
565                         +"\" on "+getMessageName()+", however there is an attribute named \""
566                         +alt.name+"\""+ classmessage+ ", did you mean to use that?";
567                 } else {
568                     solution = "found an unknown attribute named \""+name+"\" on "+getMessageName()+
569                         ", check the spelling of this attribute name";
570                 }
571                 env.warn(solution, element);
572                 type = ViewSchema.EXPRESSION_TYPE;
573             }
574
575             if (type == schema.ID_TYPE) {
576                 this.id = value;
577             } else {
578                 String JavaDoc when = this.getAttributeValueDefault(
579                     name, "when", WHEN_IMMEDIATELY);
580                 try {
581                     CompiledAttribute cattr = compileAttribute(
582                         element, name, value, type, when);
583                     addAttribute(cattr, name, attrs, events,
584                                  references, paths);
585                     // Check if we are aliasing another 'name'
586
// attribute of a sibling
587
if (name.equals("name")) {
588                         Element parent = element.getParent();
589                         if (parent != null) {
590                             for (Iterator iter2 = parent.getChildren().iterator(); iter2.hasNext();
591                                  ) {
592                                 Element e = (Element) iter2.next();
593                                 if (!e.getName().equals("resource") && !e.getName().equals("font")
594                                     && e != element && value.equals(e.getAttributeValue("name"))) {
595                                     String JavaDoc dup_location =
596                                         CompilerUtils.sourceLocationPrettyString(e);
597                                     env.warn(
598                                         getMessageName() + " has the same name=\""+
599                                         value+"\" attribute as a sibling element at "+dup_location,
600                                         element);
601                                 }
602                             }
603                         }
604                     }
605                 } catch (CompilationError e) {
606                     env.warn(e);
607                 }
608             }
609         }
610     }
611
612     void addAttribute(CompiledAttribute cattr, String JavaDoc name,
613                       ComparisonMap attrs, ComparisonMap events,
614                       ComparisonMap references, ComparisonMap paths) {
615         if (cattr.type == cattr.ATTRIBUTE) {
616             if (attrs.containsKey(name)) {
617                 env.warn(
618                     "an attribute or method named '"+name+
619                     "' already is defined on "+getMessageName(),
620                     element);
621             }
622             attrs.put(name, cattr.value);
623         } else if (cattr.type == cattr.EVENT) {
624             if (events.containsKey(name)) {
625                 env.warn(
626                     "redefining event '"+name+
627                     "' which has already been defined on "+getMessageName(),
628                     element);
629             }
630             events.put(name, cattr.value);
631         } else if (cattr.type == cattr.REFERENCE) {
632             if (references.containsKey(name)) {
633                 env.warn(
634                     "redefining reference '"+name+
635                     "' which has already been defined on "+getMessageName(),
636                     element);
637             }
638             references.put(name, cattr.value);
639         } else if (cattr.type == cattr.PATH) {
640             references.put(name, cattr.value);
641         }
642     }
643
644     void addChildren(CompilationEnvironment env) {
645         // Encode the children
646
for (Iterator iter = element.getChildren().iterator(); iter.hasNext(); ) {
647             ElementWithLocationInfo child = (ElementWithLocationInfo) iter.next();
648             try {
649                 if (isPropertyElement(child)) {
650                     addPropertyElement(child);
651                 } else if (schema.isHTMLElement(child)) {
652                     ; // ignore; the text compiler wiil handle this
653
} else {
654                     NodeModel childModel = elementAsModel(child, schema, env);
655                     children.add(childModel);
656                     totalSubnodes += childModel.totalSubnodes();
657                 }
658             } catch (CompilationError e) {
659                 env.warn(e);
660             }
661         }
662     }
663
664     void addPropertyElement(Element element) {
665         String JavaDoc tagName = element.getName();
666         if (tagName.equals("method")) {
667             addMethodElement(element);
668         } else if (tagName.equals("attribute")) {
669             addAttributeElement(element);
670         }
671     }
672
673     void addMethodElement(Element element) {
674         String JavaDoc srcloc =
675             CompilerUtils.sourceLocationDirective(element, true);
676         String JavaDoc name = element.getAttributeValue("name");
677         String JavaDoc event = element.getAttributeValue("event");
678         String JavaDoc args = CompilerUtils.attributeLocationDirective(element, "args") +
679             XMLUtils.getAttributeValue(element, "args", "");
680         if ((name == null || !ScriptCompiler.isIdentifier(name)) &&
681             (event == null || !ScriptCompiler.isIdentifier(event))) {
682             env.warn("method needs a non-null name or event attribute");
683             return;
684         }
685         if (name != null && sDeprecatedMethods.containsKey(name)) {
686             String JavaDoc oldName = name;
687             String JavaDoc newName = (String JavaDoc) sDeprecatedMethods.get(name);
688             name = newName;
689             env.warn(
690                 oldName + " is deprecated. " +
691                 "This method will be compiled as <method name='" + newName + "' instead. " +
692                 "Please update your sources.",
693                 element);
694         }
695
696         String JavaDoc parent_name =
697             element.getParent().getAttributeValue("id");
698         String JavaDoc name_loc =
699             (name == null ?
700              CompilerUtils.attributeLocationDirective(element, "event") :
701              CompilerUtils.attributeLocationDirective(element, "name"));
702         if (parent_name == null) {
703             parent_name =
704                 (name == null ?
705                  CompilerUtils.attributeUniqueName(element, "event") :
706                  CompilerUtils.attributeUniqueName(element, "name"));
707         }
708         if (event != null) {
709             if (name == null) {
710                 // Note, this could be optimized again to try to
711
// reuse names where they don't conflict, but if
712
// we create a new symbol generator for each view,
713
// and restart it at $m1, we have this screw case
714
// of an unnamed method in a class getting the
715
// same gensym'ed name as an unnamed method in an
716
// instance.
717
// Could use $cn with a global generator for classes,
718
// and a restarted $m1 generator for each instance.
719
name = env.methodNameGenerator.next();
720             }
721             String JavaDoc reference = element.getAttributeValue("reference");
722             Object JavaDoc referencefn = "null";
723             if (reference != null) {
724                 String JavaDoc ref_loc =
725                     CompilerUtils.attributeLocationDirective(element, "reference");
726                 referencefn = new Function(
727                     ref_loc +
728                     parent_name + "_" + name + "_reference",
729                     args,
730                     "\n#pragma 'withThis'\n" +
731                     "return (" +
732                     "#beginAttribute\n" + ref_loc +
733                     reference + "\n#endAttribute\n)");
734             }
735             // delegates is only used to determine whether to
736
// default clickable to true. Clickable should only
737
// default to true if the event handler is attached to
738
// this view.
739
if (reference == null)
740                 delegates.put(event, Boolean.TRUE);
741             delegateList.add(ScriptCompiler.quote(event));
742             delegateList.add(ScriptCompiler.quote(name));
743             delegateList.add(referencefn);
744         }
745         String JavaDoc body = element.getText();
746
747         String JavaDoc childcontentloc =
748             CompilerUtils.sourceLocationDirective(element, true);
749         Function fndef = new
750             // Use "mangled" name, so it will be unique
751
Function(name_loc +
752                      parent_name + "_" + name,
753                      //"#beginAttribute\n" +
754
CompilerUtils.attributeLocationDirective(element, "args") + args,
755                      "\n#beginContent\n" +
756                      "\n#pragma 'methodName=" + name + "'\n" +
757                      "\n#pragma 'withThis'\n" +
758                      childcontentloc +
759                      body + "\n#endContent");
760
761         if (attrs.containsKey(name)) {
762             env.warn(
763                 "an attribute or method named '"+name+
764                 "' already is defined on "+getMessageName(),
765                 element);
766         }
767
768         attrs.put(name, fndef);
769     }
770
771     CompiledAttribute compileAttribute(
772         Element source, String JavaDoc name,
773         String JavaDoc value, Schema.Type type,
774         String JavaDoc when)
775         {
776             String JavaDoc srcloc = CompilerUtils.sourceLocationDirective(source, true);
777             String JavaDoc parent_name = source.getAttributeValue("id");
778             if (parent_name == null) {
779                 parent_name = CompilerUtils.attributeUniqueName(source, name);
780             }
781             // Some values are not canonicalized to String
782
Object JavaDoc canonicalValue = null;
783             boolean warnOnDeprecatedConstraints = true;
784
785             if (value == null) {
786                 throw new RuntimeException JavaDoc("value is null in " + source);
787             }
788
789             if (value.startsWith("$(")) {
790                 env.warn(
791                     "The syntax '$(...)' is not valid, "
792                     + "you probably meant to use curly-braces instead '${...}'",
793                     source);
794             } else if (value.startsWith("$") && value.endsWith("}")) {
795                 // Canonicalize values
796
int brace = value.indexOf('{');
797                 // Expressions override any when value, default is
798
// constraint
799
if (brace >= 1) {
800                     when = value.substring(1, brace);
801                     value = value.substring(brace + 1, value.length() - 1);
802                     if (when.equals(""))
803                         when = WHEN_ALWAYS;
804                 }
805             } else if (type == ViewSchema.COLOR_TYPE) {
806                 if (when.equals(WHEN_IMMEDIATELY)) {
807                     try {
808                         value = "0x" +
809                             Integer.toHexString(ViewSchema.parseColor(value));
810                     } catch (ColorFormatException e) {
811                         // Or just set when to WHEN_ONCE and fall
812
// through to TODO?
813
throw new CompilationError(source, name, e);
814                     }
815                 }
816                 // TODO: [2003-05-02 ptw] Wrap non-constant colors in
817
// runtime parser
818
} else if (type == ViewSchema.CSS_TYPE) {
819                 if (when.equals(WHEN_IMMEDIATELY)) {
820                     try {
821                         Map cssProperties = new CSSParser
822                             (new AttributeStream(source, name, value)).Parse();
823                         for (Iterator i2 = cssProperties.entrySet().iterator(); i2.hasNext(); ) {
824                             Map.Entry entry = (Map.Entry) i2.next();
825                             Object JavaDoc mv = entry.getValue();
826                             if (mv instanceof String JavaDoc) {
827                                 entry.setValue(ScriptCompiler.quote((String JavaDoc) mv));
828                             }
829                         }
830                         canonicalValue = cssProperties;
831                     } catch (org.openlaszlo.css.ParseException e) {
832                         // Or just set when to WHEN_ONCE and fall
833
// through to TODO?
834
throw new CompilationError(e);
835                     } catch (org.openlaszlo.css.TokenMgrError e) {
836                         // Or just set when to WHEN_ONCE and fall
837
// through to TODO?
838
throw new CompilationError(e);
839                     }
840                 }
841                 // TODO: [2003-05-02 ptw] Wrap non-constant styles in
842
// runtime parser
843
} else if (type == ViewSchema.STRING_TYPE
844                        || type == ViewSchema.TOKEN_TYPE
845                        ) {
846                 // Immediate string attributes are auto-quoted
847
if (when.equals(WHEN_IMMEDIATELY)) {
848                     value = ScriptCompiler.quote(value);
849                 }
850             } else if (type == ViewSchema.EXPRESSION_TYPE) {
851                 // No change currently, possibly analyze expressions
852
// and default non-constant to when="once" in the
853
// future
854
} else if (type == ViewSchema.INHERITABLE_BOOLEAN_TYPE) {
855                 // change "inherit" to null and pass true/false through as expression
856
if ("inherit".equals(value)) {
857                     value = "null";
858                 }
859             } else if (type == ViewSchema.NUMBER_TYPE) {
860                 // No change currently, possibly analyze expressions
861
// and default non-constant to when="once" in the
862
// future
863
} else if (type == ViewSchema.NUMBER_EXPRESSION_TYPE ||
864                        type == ViewSchema.SIZE_EXPRESSION_TYPE) {
865                 // if it's a number that ends in percent:
866
if (value.trim().endsWith("%")) {
867                     String JavaDoc numstr = value.trim();
868                     numstr = numstr.substring(0, numstr.length() - 1);
869                     try {
870                         double scale = new Float JavaDoc(numstr).floatValue() / 100.0;
871                         warnOnDeprecatedConstraints = false;
872                         String JavaDoc referenceAttribute = name;
873                         if (name.equals("x")) {
874                             referenceAttribute = "width";
875                         } else if (name.equals("y")) {
876                             referenceAttribute = "height";
877                         }
878                         value = "immediateparent." + referenceAttribute;
879                         if (scale != 1.0) {
880                             // This special case doesn't change the
881
// semantics, but it generates shorter (since
882
// the sc doesn't fold constants) and more
883
// debuggable code
884
value += "\n * " + scale;
885                         }
886                         // fall through to the reference case
887
} catch (NumberFormatException JavaDoc e) {
888                         // fall through
889
}
890                 }
891                 // if it's a literal, treat it the same as a number
892
try {
893                     new Float JavaDoc(value); // for effect, to generate the exception
894
when = WHEN_IMMEDIATELY;
895                 } catch (NumberFormatException JavaDoc e) {
896                     // It's not a constant, unless when has been
897
// specified, default to a constraint
898
if (when.equals(WHEN_IMMEDIATELY)) {
899                         if (warnOnDeprecatedConstraints) {
900                             env.warn(
901                                 "The value of the '" + name + "' attribute uses a deprecated syntax. " +
902                                 "Use " + name + "=\"${" + value + "}\" instead.",
903                                 element);
904                         }
905                         when = WHEN_ALWAYS;
906                     }
907                 }
908             } else if (type == ViewSchema.EVENT_TYPE) {
909                 // Oddball case -- short-circuit when altogether
910
return new CompiledAttribute(
911                     CompiledAttribute.EVENT,
912                     "function " +
913                     parent_name + "_" + name + "_event" +
914                     " () {" +
915                     "\n#pragma 'withThis'\n" +
916                     "{\n#beginAttributeStatements\n" +
917                     srcloc + value + "\n#endAttributeStatements\n}}");
918             } else if (type == ViewSchema.REFERENCE_TYPE) {
919                 // type="reference" is defined to imply when="once"
920
// since reference expressions are unlikely to
921
// evaluate correctly at when="immediate" time
922
if (when.equals(WHEN_IMMEDIATELY)) {
923                     when = WHEN_ONCE;
924                 }
925             } else {
926                 throw new RuntimeException JavaDoc("unknown schema datatype " + type);
927             }
928
929             if (canonicalValue == null)
930                 canonicalValue = value;
931
932             // Handle when cases
933
if (when.equals(WHEN_PATH)) {
934                 return new CompiledAttribute(
935                     CompiledAttribute.PATH,
936                     srcloc + ScriptCompiler.quote(value) + "\n");
937             } else if (when.equals(WHEN_ONCE)) {
938                 return new CompiledAttribute(
939                     CompiledAttribute.REFERENCE,
940                     "function " +
941                     parent_name + "_" + name + "_once" +
942                     " () {" +
943                     "\n#pragma 'withThis'\n" +
944                     // Use this.setAttribute so that the compiler
945
// will recognize it for inlining.
946
"this.setAttribute(" +
947                     ScriptCompiler.quote(name) + " , " +
948                     "\n#beginAttribute\n" + srcloc + canonicalValue + "\n#endAttribute\n)}");
949             } else if (when.equals(WHEN_ALWAYS)) {
950                 return new CompiledAttribute(
951                     CompiledAttribute.REFERENCE,
952                     "function " +
953                     parent_name + "_" + name + "_always" +
954                     " () {" +
955                     "\n#pragma 'constraintFunction'\n" +
956                     "\n#pragma 'withThis'\n" +
957                     // Use this.setAttribute so that the compiler
958
// will recognize it for inlining.
959
"this.setAttribute(" +
960                     ScriptCompiler.quote(name) + ", " +
961                     "\n#beginAttribute\n" + srcloc + canonicalValue +
962                     "\n#endAttribute\n)}");
963             } else if (when.equals(WHEN_IMMEDIATELY)) {
964                 if (type == ViewSchema.EXPRESSION_TYPE) {
965                     return new CompiledAttribute("\n#beginAttribute\n" + srcloc + canonicalValue + "\n#endAttribute");
966                 } else {
967                     // it's already an object, compiled from a CSS list
968
return new CompiledAttribute(canonicalValue);
969                 }
970             } else {
971                 throw new CompilationError("invalid when value '" +
972                                            when + "'", source);
973             }
974         }
975
976     void addAttributeElement(Element element) {
977         String JavaDoc name;
978         try {
979             name = ElementCompiler.requireIdentifierAttributeValue(element, "name");
980         } catch (MissingAttributeException e) {
981             throw new CompilationError("'name' is a required attribute of <" +
982                                        element.getName() + "> and must be a valid identifier", element);
983         }
984
985         String JavaDoc value = element.getAttributeValue("value");
986         String JavaDoc when = element.getAttributeValue("when");
987         String JavaDoc typestr = element.getAttributeValue("type");
988         Element parent = element.getParent();
989         String JavaDoc parent_name = parent.getAttributeValue("id");
990
991         if (parent_name == null) {
992             parent_name = CompilerUtils.attributeUniqueName(element, name);
993         }
994
995         // Default when according to parent
996
if (when == null) {
997             when = this.getAttributeValueDefault(
998                 name, "when", WHEN_IMMEDIATELY);
999         }
1000
1001        Schema.Type type = null;
1002        Schema.Type parenttype = null;
1003
1004        try {
1005            if (parent.getName().equals("class")) {
1006                parenttype = getAttributeTypeInfoFromSuperclass(parent, name);
1007            } else {
1008                parenttype = schema.getAttributeType(parent, name);
1009            }
1010        } catch (UnknownAttributeException e) {
1011            // If attribute type is not defined on parent, leave
1012
// parenttype null. The user can define it however they
1013
// like.
1014
}
1015
1016        if (typestr == null) {
1017            // Did user supply an explicit attribute type?
1018
// No. Default to parent type if there is one, else
1019
// EXPRESSION type.
1020
if (parenttype == null) {
1021                type = ViewSchema.EXPRESSION_TYPE;
1022            } else {
1023                type = parenttype;
1024            }
1025        } else {
1026            // parse attribute type and compare to parent type
1027
type = schema.getTypeForName(typestr);
1028            if (type == null) {
1029                throw new CompilationError("unknown attribute type: " + typestr, element);
1030            }
1031            // If we are trying to declare the attribute with a
1032
// conflicting type to the parent, throw an error
1033
if (parenttype != null && type != parenttype) {
1034                env.warn(
1035                    new CompilationError(
1036                        element,
1037                        name,
1038                        new Throwable JavaDoc(
1039                            "In element '" + parent.getName() +
1040                            "' attribute '"+ name + "' with type '"+type.toString() +
1041                            "' is overriding parent class attribute with same name but different type: "+
1042                            parenttype.toString())
1043                        )
1044                    );
1045            }
1046        }
1047
1048        // Don't initialize an attribute that is only declared.
1049
if (value != null) {
1050            CompiledAttribute cattr = compileAttribute(element, name,
1051                                                       value, type,
1052                                                       when);
1053            addAttribute(cattr, name, attrs, events, references, paths);
1054        }
1055
1056        // Add entry for attribute setter function
1057
String JavaDoc setter = element.getAttributeValue("setter");
1058        // Backward compatibility
1059
if (setter == null) {
1060            setter = element.getAttributeValue("onset");
1061        }
1062        if (setter != null) {
1063            String JavaDoc srcloc =
1064                CompilerUtils.sourceLocationDirective(element, true);
1065            // Maybe we need a new type for "function"?
1066
String JavaDoc setterfn =
1067                srcloc + "function " +
1068                parent_name + "_" + name + "_onset" +
1069                " (" + name + ") {" +
1070                "\n#pragma 'withThis'\n" +
1071                srcloc + setter + "\n}";
1072
1073            if (setters.get(name) != null) {
1074                env.warn(
1075                    "a setter for attribute named '"+name+
1076                    "' is already defined on "+getMessageName(),
1077                    element);
1078            }
1079
1080            setters.put(name, setterfn);
1081        }
1082    }
1083
1084    boolean hasAttribute(String JavaDoc name) {
1085        return attrs.containsKey(name);
1086    }
1087
1088    void removeAttribute(String JavaDoc name) {
1089        attrs.remove(name);
1090    }
1091
1092    void setAttribute(String JavaDoc name, Object JavaDoc value) {
1093        attrs.put(name, value);
1094    }
1095
1096    void addText() {
1097        if (schema.hasHTMLContent(element)) {
1098            String JavaDoc text = TextCompiler.getHTMLContent(element);
1099            if (text.length() != 0) {
1100                if (!attrs.containsKey("text")) {
1101                    attrs.put("text", ScriptCompiler.quote(text));
1102                }
1103            }
1104        } else if (schema.hasTextContent(element)) {
1105            String JavaDoc text;
1106            // The current inputtext component doesn't understand
1107
// HTML, but we'd like to have some way to enter
1108
// linebreaks in the source.
1109
text = TextCompiler.getInputText(element);
1110            if (text.length() != 0) {
1111                if (!attrs.containsKey("text")) {
1112                    attrs.put("text", ScriptCompiler.quote(text));
1113                }
1114            }
1115        }
1116    }
1117
1118    void updateAttrs() {
1119        if (!setters.isEmpty()) {
1120            attrs.put("$setters", setters);
1121        }
1122        if (!delegateList.isEmpty()) {
1123            attrs.put("$delegates", delegateList);
1124        }
1125        if (!events.isEmpty()) {
1126            attrs.put("$events", events);
1127        }
1128        if (!references.isEmpty()) {
1129            attrs.put("$refs", references);
1130        }
1131        if (!paths.isEmpty()) {
1132            attrs.put("$paths", paths);
1133        }
1134    }
1135
1136    Map asMap() {
1137        Map map = new HashMap();
1138        updateAttrs();
1139        map.put("name", ScriptCompiler.quote(className));
1140        map.put("attrs", attrs);
1141        if (id != null) {
1142            map.put("id", ScriptCompiler.quote(id));
1143        }
1144        if (!children.isEmpty()) {
1145            List childMaps = new Vector(children.size());
1146            for (Iterator iter = children.iterator(); iter.hasNext(); )
1147                childMaps.add(((NodeModel) iter.next()).asMap());
1148            map.put("children", childMaps);
1149        }
1150        return map;
1151    }
1152
1153    /** Expand eligible instances by replacing the instance by the
1154     * merge of its class definition with the instance content
1155     * (attributes and children). An eligible instance is an instance
1156     * of a compile-time class, that doesn't contain any merge
1157     * stoppers. If the class and the instance contain a member with
1158     * the same name, this is a merge stopper. In the future, this
1159     * restriction may be relaxed, but will probably always include
1160     * the case where a class and instance have a member with the same
1161     * name and the instance name calls a superclass method. */

1162    NodeModel expandClassDefinitions() {
1163        NodeModel model = this;
1164        while (true) {
1165            ClassModel classModel = schema.getClassModel(model.className);
1166            if (classModel == null)
1167                break;
1168            if (classModel.getSuperclassName() == null)
1169                break;
1170            if (!classModel.getInline())
1171                break;
1172            model = classModel.applyClass(model);
1173            // Iterate to allow for the original classes superclass to
1174
// be expanded as well.
1175
}
1176        // Recurse. Make a copy so we can replace the child list.
1177
// TODO [2004-0604]: As an optimization, only do this if one
1178
// of the children changed.
1179
model = (NodeModel) model.clone();
1180        for (ListIterator iter = model.children.listIterator();
1181             iter.hasNext(); ) {
1182            NodeModel child = (NodeModel) iter.next();
1183            iter.set(child.expandClassDefinitions());
1184        }
1185        return model;
1186    }
1187
1188    /** Replace members of this with like-named members of source. */
1189    void updateMembers(NodeModel source) {
1190        final String JavaDoc OPTIONS_ATTR_NAME = "options";
1191
1192        // FIXME [2004-06-04]: only compare events with the same reference
1193
if (CollectionUtils.containsAny(
1194                events.normalizedKeySet(), source.events.normalizedKeySet())) {
1195            Collection sharedEvents = CollectionUtils.intersection(
1196                events.normalizedKeySet(), source.events.normalizedKeySet());
1197            throw new CompilationError(
1198                "Both the class and the instance or subclass define the " +
1199                new ChoiceFormat JavaDoc("1#event |1<events ").format(sharedEvents.size()) +
1200                new ListFormat("and").format(sharedEvents));
1201        }
1202
1203        // Check for duplicate methods. Collect all the keys that name
1204
// a Function in both the source and target.
1205
List sharedMethods = new Vector();
1206        for (Iterator iter = attrs.normalizedKeySet().iterator();
1207             iter.hasNext(); ) {
1208            String JavaDoc key = (String JavaDoc) iter.next();
1209            if (attrs.get(key) instanceof Function &&
1210                source.attrs.get(key) instanceof Function)
1211                sharedMethods.add(key);
1212        }
1213        if (!sharedMethods.isEmpty())
1214            throw new CompilationError(
1215                "Both the class and the instance or subclass define the method" +
1216                new ChoiceFormat JavaDoc("1# |1<s ").format(sharedMethods.size()) +
1217                new ListFormat("and").format(sharedMethods));
1218
1219        // Check for attributes that have a value in this and
1220
// a setter in the source. These can't be merged.
1221
Collection overriddenAttributes = CollectionUtils.intersection(
1222            attrs.normalizedKeySet(), source.setters.normalizedKeySet());
1223        if (!overriddenAttributes.isEmpty())
1224            throw new CompilationError(
1225                "A class that defines a value can't be inlined against a " +
1226                "subclass or instance that defines a setter. The following " +
1227                new ChoiceFormat JavaDoc("1#attribute violates|1<attributes violate")
1228                .format(overriddenAttributes.size()) +
1229                " this condition: " +
1230                new ListFormat("and").format(overriddenAttributes));
1231
1232        // Do the actual merge.
1233
id = source.id;
1234        if (source.initstage != null)
1235            initstage = source.initstage;
1236        Object JavaDoc options = attrs.get(OPTIONS_ATTR_NAME);
1237        Object JavaDoc sourceOptions = source.attrs.get(OPTIONS_ATTR_NAME);
1238        attrs.putAll(source.attrs);
1239        if (options instanceof Map && sourceOptions instanceof Map) {
1240            System.err.println(options);
1241            System.err.println(sourceOptions);
1242            Map newOptions = new HashMap((Map) options);
1243            newOptions.putAll((Map) sourceOptions);
1244            attrs.put(OPTIONS_ATTR_NAME, newOptions);
1245        }
1246        delegates.putAll(source.delegates);
1247        events.putAll(source.events);
1248        references.putAll(source.references);
1249        paths.putAll(source.paths);
1250        setters.putAll(source.setters);
1251        delegateList.addAll(source.delegateList);
1252        // TBD: warn on children that share a name?
1253
// TBD: update the node count
1254
children.addAll(source.children);
1255    }
1256}
1257
Popular Tags