0001: /* -*- mode: Java; c-basic-offset: 4; -*- */
0002: /* ***************************************************************************
0003: * NodeModel.java
0004: * ***************************************************************************/
0005:
0006: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
0007: * Copyright 2001-2008 Laszlo Systems, Inc. All Rights Reserved. *
0008: * Use is subject to license terms. *
0009: * J_LZ_COPYRIGHT_END *********************************************************/
0010:
0011: package org.openlaszlo.compiler;
0012:
0013: import java.io.*;
0014: import java.text.ChoiceFormat;
0015: import java.util.*;
0016: import java.util.regex.*;
0017:
0018: import org.openlaszlo.compiler.ViewSchema.ColorFormatException;
0019: import org.openlaszlo.css.CSSParser;
0020: import org.openlaszlo.sc.Function;
0021: import org.openlaszlo.sc.ScriptCompiler;
0022: import org.openlaszlo.server.*;
0023: import org.openlaszlo.utils.ChainedException;
0024: import org.openlaszlo.utils.ListFormat;
0025: import org.openlaszlo.utils.ComparisonMap;
0026: import org.openlaszlo.xml.internal.MissingAttributeException;
0027: import org.openlaszlo.xml.internal.Schema;
0028: import org.openlaszlo.xml.internal.XMLUtils;
0029: import org.apache.commons.collections.CollectionUtils;
0030: import org.jdom.Attribute;
0031: import org.jdom.Element;
0032: import org.jdom.Text;
0033: import org.jdom.Content;
0034: import org.jdom.Namespace;
0035:
0036: /** Models a runtime LzNode. */
0037: public class NodeModel implements Cloneable {
0038:
0039: public static final String FONTSTYLE_ATTRIBUTE = "fontstyle";
0040: public static final String WHEN_IMMEDIATELY = "immediately";
0041: public static final String WHEN_ONCE = "once";
0042: public static final String WHEN_ALWAYS = "always";
0043: public static final String WHEN_PATH = "path";
0044: public static final String WHEN_STYLE = "style";
0045: private static final String SOURCE_LOCATION_ATTRIBUTE_NAME = "__LZsourceLocation";
0046:
0047: protected final ViewSchema schema;
0048: protected final Element element;
0049: protected String className;
0050: protected String id = null;
0051: protected ComparisonMap attrs = new ComparisonMap();
0052: protected List children = new Vector();
0053: /** A set {eventName: String -> True) of names of event handlers
0054: * declared with <method event="xxx"/>. */
0055: protected ComparisonMap delegates = new ComparisonMap();
0056: /* Unused */
0057: protected ComparisonMap events = new ComparisonMap();
0058: protected ComparisonMap references = new ComparisonMap();
0059: /* Unused */
0060: protected ComparisonMap paths = new ComparisonMap();
0061: protected ComparisonMap setters = new ComparisonMap();
0062: protected ComparisonMap styles = new ComparisonMap();
0063:
0064: protected NodeModel datapath = null;
0065:
0066: /** [eventName: String, methodName: String, Function] */
0067: protected List delegateList = new Vector();
0068: protected ClassModel parentClassModel;
0069: protected String initstage = null;
0070: protected int totalSubnodes = 1;
0071: protected final CompilationEnvironment env;
0072:
0073: public Object clone() {
0074: NodeModel copy;
0075: try {
0076: copy = (NodeModel) super .clone();
0077: } catch (CloneNotSupportedException e) {
0078: throw new RuntimeException(e);
0079: }
0080: copy.attrs = new ComparisonMap(copy.attrs);
0081: copy.delegates = new ComparisonMap(copy.delegates);
0082: copy.events = new ComparisonMap(copy.events);
0083: copy.references = new ComparisonMap(copy.references);
0084: copy.paths = new ComparisonMap(copy.paths);
0085: copy.setters = new ComparisonMap(copy.setters);
0086: copy.styles = new ComparisonMap(copy.styles);
0087: copy.delegateList = new Vector(copy.delegateList);
0088: copy.children = new Vector();
0089: for (Iterator iter = children.iterator(); iter.hasNext();) {
0090: copy.children.add(((NodeModel) iter.next()).clone());
0091: }
0092: return copy;
0093: }
0094:
0095: private boolean caseSensitive = true;
0096:
0097: NodeModel(Element element, ViewSchema schema,
0098: CompilationEnvironment env) {
0099: this .element = element;
0100: this .schema = schema;
0101: this .env = env;
0102:
0103: if ("swf6".equals(env.getRuntime())) {
0104: this .caseSensitive = false;
0105: }
0106: this .className = element.getName();
0107: // Cache ClassModel for parent
0108: this .parentClassModel = this .getParentClassModel();
0109: this .initstage = this .element.getAttributeValue("initstage");
0110: if (this .initstage != null) {
0111: this .initstage = this .initstage.intern();
0112: }
0113:
0114: // Get initial node count from superclass
0115: // TODO: [2003-05-04] Extend this mechanism to cache/model
0116: // all relevant superclass info
0117: // TODO: [2003-05-04 ptw] How can we get this info for
0118: // instances of built-in classes?
0119: if (this .parentClassModel != null) {
0120: ElementWithLocationInfo parentElement = (ElementWithLocationInfo) this .parentClassModel.definition;
0121: // TODO: [2003-05-04 ptw] As above, but a class
0122: // that extends a built-in class
0123: if (parentElement != null) {
0124: // TODO: [2003-05-04 ptw] An instantiation of
0125: // a class that has not been modelled yet --
0126: // do enough modelling to get what is needed.
0127: if (parentElement.model != null) {
0128: this .totalSubnodes = parentElement.model
0129: .classSubnodes();
0130: if (this .initstage == null) {
0131: this .initstage = parentElement.model.initstage;
0132: }
0133: }
0134: }
0135: }
0136: }
0137:
0138: private static final String DEPRECATED_METHODS_PROPERTY_FILE = (LPS
0139: .getMiscDirectory()
0140: + File.separator + "lzx-deprecated-methods.properties");
0141: private static final Properties sDeprecatedMethods = new Properties();
0142:
0143: static {
0144: try {
0145: InputStream is = new FileInputStream(
0146: DEPRECATED_METHODS_PROPERTY_FILE);
0147: try {
0148: sDeprecatedMethods.load(is);
0149: } finally {
0150: is.close();
0151: }
0152: } catch (java.io.IOException e) {
0153: throw new ChainedException(e);
0154: }
0155: }
0156:
0157: /* List of flash builtins to warn about if the user tries to redefine them */
0158: private static final String FLASH6_BUILTINS_PROPERTY_FILE = (LPS
0159: .getMiscDirectory()
0160: + File.separator + "flash6-builtins.properties");
0161:
0162: private static final String FLASH7_BUILTINS_PROPERTY_FILE = (LPS
0163: .getMiscDirectory()
0164: + File.separator + "flash7-builtins.properties");
0165:
0166: public static final Properties sFlash6Builtins = new Properties();
0167: public static final Properties sFlash7Builtins = new Properties();
0168:
0169: static {
0170: try {
0171: InputStream is6 = new FileInputStream(
0172: FLASH6_BUILTINS_PROPERTY_FILE);
0173: try {
0174: sFlash6Builtins.load(is6);
0175: } finally {
0176: is6.close();
0177: }
0178:
0179: InputStream is7 = new FileInputStream(
0180: FLASH7_BUILTINS_PROPERTY_FILE);
0181: try {
0182: sFlash7Builtins.load(is7);
0183: } finally {
0184: is7.close();
0185: }
0186: } catch (java.io.IOException e) {
0187: throw new ChainedException(e);
0188: }
0189: }
0190:
0191: static class CompiledAttribute {
0192: static final int ATTRIBUTE = 0;
0193: static final int EVENT = 1;
0194: static final int REFERENCE = 2;
0195: static final int PATH = 3;
0196: static final int STYLE = 4;
0197:
0198: final int type;
0199: final Object value;
0200:
0201: CompiledAttribute(int type, Object value) {
0202: this .type = type;
0203: this .value = value;
0204: }
0205:
0206: CompiledAttribute(Object value) {
0207: this (ATTRIBUTE, value);
0208: }
0209: }
0210:
0211: public String toString() {
0212: StringBuffer buffer = new StringBuffer();
0213: buffer.append("{NodeModel class=" + className);
0214: if (!attrs.isEmpty())
0215: buffer.append(" attrs=" + attrs.keySet());
0216: if (!delegates.isEmpty())
0217: buffer.append(" delegates=" + delegates.keySet());
0218: if (!events.isEmpty())
0219: buffer.append(" events=" + events.keySet());
0220: if (!references.isEmpty())
0221: buffer.append(" references=" + references.keySet());
0222: if (!paths.isEmpty())
0223: buffer.append(" paths=" + paths.keySet());
0224: if (!setters.isEmpty())
0225: buffer.append(" setters=" + setters.keySet());
0226: if (!styles.isEmpty())
0227: buffer.append(" styles=" + styles.keySet());
0228: if (!delegateList.isEmpty())
0229: buffer.append(" delegateList=" + delegateList);
0230: if (!children.isEmpty())
0231: buffer.append(" children=" + children);
0232: buffer.append("}");
0233: return buffer.toString();
0234: }
0235:
0236: List getChildren() {
0237: return children;
0238: }
0239:
0240: public static boolean isPropertyElement(Element elt) {
0241: String name = elt.getName();
0242: return name.equals("attribute") || name.equals("method")
0243: || name.equals("handler") || name.equals("event");
0244: }
0245:
0246: /** Returns a name that is used to report this element in warning
0247: * messages. */
0248: String getMessageName() {
0249: return "element " + element.getName();
0250: }
0251:
0252: /**
0253: * Returns a script that creates a runtime representation of a
0254: * model. The format of this representation is specified <a
0255: * href="../../../../doc/compiler/views.html">here</a>.
0256: *
0257: */
0258: public String asJavascript() {
0259: try {
0260: java.io.Writer writer = new java.io.StringWriter();
0261: ScriptCompiler.writeObject(this .asMap(), writer);
0262: return writer.toString();
0263: } catch (java.io.IOException e) {
0264: throw new ChainedException(e);
0265: }
0266: }
0267:
0268: /** Returns true iff clickable should default to true. */
0269: private static boolean computeDefaultClickable(ViewSchema schema,
0270: Map attrs, Map events, Map delegates) {
0271: if ("true".equals(attrs.get("cursor"))) {
0272: return true;
0273: }
0274: for (Iterator iter = events.keySet().iterator(); iter.hasNext();) {
0275: String eventName = (String) iter.next();
0276: if (schema.isMouseEventAttribute(eventName)) {
0277: return true;
0278: }
0279: }
0280: for (Iterator iter = delegates.keySet().iterator(); iter
0281: .hasNext();) {
0282: String eventName = (String) iter.next();
0283: if (schema.isMouseEventAttribute(eventName)) {
0284: return true;
0285: }
0286: }
0287: return false;
0288: }
0289:
0290: /**
0291: * Returns a NodeModel that represents an Element, including the
0292: * element's children
0293: *
0294: * @param elt an element
0295: * @param schema a schema, used to encode attribute values
0296: * @param env the CompilationEnvironment
0297: */
0298: public static NodeModel elementAsModel(Element elt,
0299: ViewSchema schema, CompilationEnvironment env) {
0300: return elementAsModelInternal(elt, schema, true, env);
0301: }
0302:
0303: /**
0304: * Returns a NodeModel that represents an Element, excluding the
0305: * element's children
0306: *
0307: * @param elt an element
0308: * @param schema a schema, used to encode attribute values
0309: * @param env the CompilationEnvironment
0310: */
0311: public static NodeModel elementOnlyAsModel(Element elt,
0312: ViewSchema schema, CompilationEnvironment env) {
0313: return elementAsModelInternal(elt, schema, false, env);
0314: }
0315:
0316: /** Returns a NodeModel that represents an Element
0317: *
0318: * @param elt an element
0319: * @param schema a schema, used to encode attribute values
0320: * @param includeChildren whether or not to include children
0321: * @param env the CompilationEnvironment
0322: */
0323: private static NodeModel elementAsModelInternal(Element elt,
0324: ViewSchema schema, boolean includeChildren,
0325: CompilationEnvironment env) {
0326: NodeModel model = new NodeModel(elt, schema, env);
0327: ComparisonMap attrs = model.attrs;
0328: Map events = model.events;
0329: Map delegates = model.delegates;
0330: model.addAttributes(env);
0331:
0332: // This emits a local dataset node, so only process
0333: // <dataset> tags that are not top level datasets.
0334:
0335: if (elt.getName().equals("dataset")) {
0336: boolean contentIsLiteralXMLData = true;
0337: String datafromchild = elt
0338: .getAttributeValue("datafromchild");
0339: String src = elt.getAttributeValue("src");
0340: String type = elt.getAttributeValue("type");
0341:
0342: if ((type != null && (type.equals("soap") || type
0343: .equals("http")))
0344: || (src != null && XMLUtils.isURL(src))
0345: || "true".equals(datafromchild)) {
0346: contentIsLiteralXMLData = false;
0347: }
0348:
0349: if (contentIsLiteralXMLData) {
0350: // Default to legacy behavior, treat all children as XML literal data.
0351: attrs.put("initialdata", getDatasetContent(elt, env));
0352: includeChildren = false;
0353: }
0354: }
0355:
0356: if (includeChildren) {
0357: model.addChildren(env);
0358: model.addText();
0359: if (!attrs.containsKey("clickable")
0360: && computeDefaultClickable(schema, attrs, events,
0361: delegates)) {
0362: attrs.put("clickable", "true");
0363: }
0364: }
0365: // Record the model in the element for classes
0366: ((ElementWithLocationInfo) elt).model = model;
0367: return model;
0368: }
0369:
0370: // Calculate how many nodes this object will put on the
0371: // instantiation queue.
0372: int totalSubnodes() {
0373: // A class does not instantiate its subnodes.
0374: // States override LzNode.thaw to delay subnodes.
0375: // FIXME [2004-06-3 ows]: This won't work for subclasses
0376: // of state.
0377: if (ClassCompiler.isElement(element)
0378: || className.equals("state")) {
0379: return 1;
0380: }
0381: // initstage late, defer delay subnodes
0382: if (this .initstage != null
0383: && (this .initstage == "late" || this .initstage == "defer")) {
0384: return 0;
0385: }
0386: return this .totalSubnodes;
0387: }
0388:
0389: // How many nodes will be inherited from this class
0390: int classSubnodes() {
0391: if (ClassCompiler.isElement(element)) {
0392: return this .totalSubnodes;
0393: }
0394: return 0;
0395: }
0396:
0397: ClassModel getClassModel() {
0398: return schema.getClassModel(this .className);
0399: }
0400:
0401: /** Gets the ClassModel for this element's parent class. If this
0402: * element is a <class> definition, the superclass; otherwise the
0403: * class of the tag of this element. */
0404: ClassModel getParentClassModel() {
0405: String parentName = this .className;
0406: return parentName.equals("class") ? schema
0407: .getClassModel(element.getAttributeValue("extends"))
0408: : schema.getClassModel(parentName);
0409: }
0410:
0411: void setClassName(String name) {
0412: this .className = name;
0413: this .parentClassModel = getParentClassModel();
0414: }
0415:
0416: ViewSchema.Type getAttributeTypeInfoFromParent(Element elt,
0417: String attrname) throws UnknownAttributeException {
0418: Element parent = elt.getParentElement();
0419: return schema.getAttributeType(parent, attrname);
0420: }
0421:
0422: // Should only be called on a <class> definition element.
0423: ViewSchema.Type getAttributeTypeInfoFromSuperclass(
0424: Element classDefElement, String attrname)
0425: throws UnknownAttributeException {
0426: String super classname = classDefElement.getAttributeValue(
0427: "extends", ClassCompiler.DEFAULT_SUPERCLASS_NAME);
0428: ClassModel super classModel = schema
0429: .getClassModel(super classname);
0430:
0431: if (super classModel == null) {
0432: throw new CompilationError(
0433: /* (non-Javadoc)
0434: * @i18n.test
0435: * @org-mes="Could not find superclass info for class " + p[0]
0436: */
0437: org.openlaszlo.i18n.LaszloMessages.getMessage(
0438: NodeModel.class.getName(), "051018-411",
0439: new Object[] { super classname, classDefElement }));
0440: }
0441:
0442: // Check if this attribute is defined on the parent class, if
0443: // so, return that type
0444: AttributeSpec attr = super classModel.getAttribute(attrname);
0445: if (attr != null) {
0446: return attr.type;
0447: }
0448: // Otherwise, check if it's defined on the "class" element
0449: // (e.g., 'name' or 'extends')
0450: super classModel = schema.getClassModel("class");
0451: return super classModel.getAttributeTypeOrException(attrname);
0452: }
0453:
0454: // Get an attribute value, defaulting to the
0455: // inherited value, or ultimately the supplied default
0456: String getAttributeValueDefault(String attribute, String name,
0457: String defaultValue) {
0458: // Look for an inherited value
0459: if (this .parentClassModel != null) {
0460: AttributeSpec attrSpec = this .parentClassModel
0461: .getAttribute(attribute);
0462: if (attrSpec != null) {
0463: Element source = attrSpec.source;
0464: if (source != null) {
0465: return XMLUtils.getAttributeValue(source, name,
0466: defaultValue);
0467: }
0468: }
0469: }
0470:
0471: return defaultValue;
0472: }
0473:
0474: /** Is this element a direct child of the canvas? */
0475: // FIXME [2004-06-03 ows]: Use CompilerUtils.isTopLevel instead.
0476: // This implementation misses children of <library> and <switch>.
0477: // Since it's only used for compiler warnings about duplicate
0478: // names this doesn't break program compilation.
0479: boolean topLevelDeclaration() {
0480: Element parent = element.getParentElement();
0481: if (parent == null) {
0482: return false;
0483: }
0484: return ("canvas".equals(parent.getName()));
0485: }
0486:
0487: void addAttributes(CompilationEnvironment env) {
0488: // Add source locators, if requested. Added here because it
0489: // is not in the schema
0490: if (env.getBooleanProperty(env.SOURCELOCATOR_PROPERTY)) {
0491: String location = "document("
0492: + ScriptCompiler.quote(Parser
0493: .getSourceMessagePathname(element)) + ")"
0494: + XMLUtils.getXPathTo(element);
0495: CompiledAttribute cattr = compileAttribute(element,
0496: SOURCE_LOCATION_ATTRIBUTE_NAME, location,
0497: ViewSchema.STRING_TYPE, WHEN_IMMEDIATELY);
0498: addAttribute(cattr, SOURCE_LOCATION_ATTRIBUTE_NAME, attrs,
0499: events, references, paths, styles);
0500: }
0501:
0502: // Add file/line information if debugging
0503: if (env.getBooleanProperty(env.DEBUG_PROPERTY)) {
0504: // File/line stored separately for string sharing
0505: String name = "_dbg_filename";
0506: String filename = Parser.getSourceMessagePathname(element);
0507: CompiledAttribute cattr = compileAttribute(element, name,
0508: filename, ViewSchema.STRING_TYPE, WHEN_IMMEDIATELY);
0509: addAttribute(cattr, name, attrs, events, references, paths,
0510: styles);
0511: name = "_dbg_lineno";
0512: Integer lineno = Parser.getSourceLocation(element,
0513: Parser.LINENO);
0514: cattr = compileAttribute(element, name, lineno.toString(),
0515: ViewSchema.NUMBER_TYPE, WHEN_IMMEDIATELY);
0516: addAttribute(cattr, name, attrs, events, references, paths,
0517: styles);
0518: }
0519:
0520: ClassModel classModel = getClassModel();
0521: if (classModel == null) {
0522: throw new CompilationError(
0523: "Could not find class definition for tag `"
0524: + className + "`", element);
0525: }
0526: // Encode the attributes
0527: for (Iterator iter = element.getAttributes().iterator(); iter
0528: .hasNext();) {
0529: Attribute attr = (Attribute) iter.next();
0530: Namespace ns = attr.getNamespace();
0531: String name = attr.getName();
0532: String value = element.getAttributeValue(name, ns);
0533:
0534: if (name.equals(FONTSTYLE_ATTRIBUTE)) {
0535: // "bold italic", "italic bold" -> "bolditalic"
0536: value = FontInfo.normalizeStyleString(value, false);
0537: }
0538:
0539: if (name.toLowerCase().equals("defaultplacement")) {
0540: if (value != null
0541: && value.matches("\\s*['\"]\\S*['\"]\\s*")) {
0542: String oldValue = value;
0543: // strip off start and ending quotes;
0544: value = value.trim();
0545: value = value.substring(1, value.length() - 1);
0546: env.warn(
0547: /* (non-Javadoc)
0548: * @i18n.test
0549: * @org-mes="Replacing defaultPlacement=\"" + p[0] + "\" by \"" + p[1] + "\". For future compatibility" + ", you should make this change to your source code."
0550: */
0551: org.openlaszlo.i18n.LaszloMessages.getMessage(
0552: NodeModel.class.getName(), "051018-513",
0553: new Object[] { oldValue, value }), element);
0554: }
0555: }
0556:
0557: // Warn for redefine of a flash builtin
0558: // TODO: [2006-01-23 ptw] What about colliding with DHTML globals?
0559: if ((name.equals("id") || name.equals("name"))
0560: && (value != null
0561: && (env.getRuntime().indexOf("swf") == 0) && ("swf6"
0562: .equals(env.getRuntime()) ? sFlash6Builtins
0563: .containsKey(value.toLowerCase())
0564: : sFlash7Builtins.containsKey(value)))) {
0565: env.warn(
0566: /* (non-Javadoc)
0567: * @i18n.test
0568: * @org-mes="You have given the " + p[0] + " an attribute " + p[1] + "=\"" + p[2] + "\", " + "which may overwrite the Flash builtin class named \"" + p[3] + "\"."
0569: */
0570: org.openlaszlo.i18n.LaszloMessages.getMessage(
0571: NodeModel.class.getName(), "051018-532",
0572: new Object[] { getMessageName(), name, value,
0573: value }), element);
0574:
0575: }
0576:
0577: // Catch duplicated id/name attributes which may shadow
0578: // each other or overwrite each other. An id/name will be
0579: // global there is "id='foo'" or if "name='foo'" at the
0580: // top level (immediate child of the canvas).
0581: //
0582: if ((name.equals("id"))
0583: || (name.equals("name") && topLevelDeclaration() && !className
0584: .equals("class"))) {
0585:
0586: ClassModel super classModel = schema
0587: .getClassModel(value);
0588: if (super classModel != null
0589: && !super classModel.isBuiltin()) {
0590: env.warn(
0591: /* (non-Javadoc)
0592: * @i18n.test
0593: * @org-mes="You have given the " + p[0] + " an attribute " + p[1] + "=\"" + p[2] + "\", " + "which may overwrite the class \"" + p[3] + "\"."
0594: */
0595: org.openlaszlo.i18n.LaszloMessages.getMessage(
0596: NodeModel.class.getName(), "051018-559",
0597: new Object[] { getMessageName(), name,
0598: value, value }), element);
0599: } else {
0600: ElementWithLocationInfo dup = (ElementWithLocationInfo) env
0601: .getId(value);
0602: // we don't want to give a warning in the case
0603: // where the id and name are on the same element,
0604: // i.e., <view id="foo" name="foo"/>
0605: if (dup != null && dup != element) {
0606: String locstring = CompilerUtils
0607: .sourceLocationPrettyString(dup);
0608: env.warn(
0609: /* (non-Javadoc)
0610: * @i18n.test
0611: * @org-mes="Duplicate id attribute \"" + p[0] + "\" at " + p[1]
0612: */
0613: org.openlaszlo.i18n.LaszloMessages.getMessage(
0614: NodeModel.class.getName(),
0615: "051018-576", new Object[] { value,
0616: locstring }), element);
0617: } else {
0618: // TODO: [07-18-03 hqm] We will canonicalize
0619: // all id's to lowercase, because actionscript
0620: // is not case sensitive. but in the future,
0621: // we should preserve case.
0622: env.addId(value, element);
0623: }
0624: }
0625: }
0626:
0627: Schema.Type type;
0628: try {
0629: if (className.equals("class")) {
0630: // Special case, if we are compiling a "class"
0631: // tag, then get the type of attributes from the
0632: // superclass.
0633: type = getAttributeTypeInfoFromSuperclass(element,
0634: name);
0635: } else if (className.equals("state")) {
0636: // Special case for "state", it can have any attribute
0637: // which belongs to the parent.
0638: try {
0639: type = schema.getAttributeType(element, name);
0640: } catch (UnknownAttributeException e) {
0641: type = getAttributeTypeInfoFromParent(element,
0642: name);
0643: }
0644: } else {
0645: // NOTE [2007-06-14 ptw]: Querying the classModel
0646: // directly will NOT work, because the schema
0647: // method has some special kludges in it for canvas
0648: // width and height!
0649: type = schema.getAttributeType(element, name);
0650: }
0651:
0652: } catch (UnknownAttributeException e) {
0653: String solution;
0654: AttributeSpec alt = schema.findSimilarAttribute(
0655: className, name);
0656: if (alt != null) {
0657: String classmessage = "";
0658: if (alt.source != null) {
0659: classmessage = " on class "
0660: + alt.source.getName() + "\"";
0661: } else {
0662: classmessage = " on class " + getMessageName();
0663: }
0664: solution =
0665: /* (non-Javadoc)
0666: * @i18n.test
0667: * @org-mes="found an unknown attribute named \"" + p[0] + "\" on " + p[1] + ", however there is an attribute named \"" + p[2] + "\"" + p[3] + ", did you mean to use that?"
0668: */
0669: org.openlaszlo.i18n.LaszloMessages.getMessage(
0670: NodeModel.class.getName(), "051018-616",
0671: new Object[] { name, getMessageName(),
0672: alt.name, classmessage });
0673: } else {
0674: solution =
0675: /* (non-Javadoc)
0676: * @i18n.test
0677: * @org-mes="found an unknown attribute named \"" + p[0] + "\" on " + p[1] + ", check the spelling of this attribute name"
0678: */
0679: org.openlaszlo.i18n.LaszloMessages.getMessage(
0680: NodeModel.class.getName(), "051018-624",
0681: new Object[] { name, getMessageName() });
0682: }
0683: env.warn(solution, element);
0684: type = ViewSchema.EXPRESSION_TYPE;
0685: }
0686:
0687: if (type == schema.ID_TYPE) {
0688: this .id = value;
0689: } else if (type == schema.EVENT_HANDLER_TYPE) {
0690: addHandlerFromAttribute(element, name, value);
0691: } else {
0692: String when = this .getAttributeValueDefault(name,
0693: "when", WHEN_IMMEDIATELY);
0694: try {
0695: CompiledAttribute cattr = compileAttribute(element,
0696: name, value, type, when);
0697: addAttribute(cattr, name, attrs, events,
0698: references, paths, styles);
0699: // Check if we are aliasing another 'name'
0700: // attribute of a sibling
0701: if (name.equals("name")) {
0702: Element parent = element.getParentElement();
0703: if (parent != null) {
0704: for (Iterator iter2 = parent.getChildren()
0705: .iterator(); iter2.hasNext();) {
0706: Element e = (Element) iter2.next();
0707: if (!e.getName().equals("resource")
0708: && !e.getName().equals("font")
0709: && e != element
0710: && value
0711: .equals(e
0712: .getAttributeValue("name"))) {
0713: String dup_location = CompilerUtils
0714: .sourceLocationPrettyString(e);
0715: env
0716: .warn(
0717: /* (non-Javadoc)
0718: * @i18n.test
0719: * @org-mes=p[0] + " has the same name=\"" + p[1] + "\" attribute as a sibling element at " + p[2]
0720: */
0721: org.openlaszlo.i18n.LaszloMessages
0722: .getMessage(
0723: NodeModel.class
0724: .getName(),
0725: "051018-658",
0726: new Object[] {
0727: getMessageName(),
0728: value,
0729: dup_location }),
0730: element);
0731: }
0732: }
0733: }
0734: }
0735: } catch (CompilationError e) {
0736: env.warn(e);
0737: }
0738: }
0739: }
0740: }
0741:
0742: void addAttribute(CompiledAttribute cattr, String name,
0743: ComparisonMap attrs, ComparisonMap events,
0744: ComparisonMap references, ComparisonMap paths,
0745: ComparisonMap styles) {
0746: if (cattr.type == cattr.ATTRIBUTE || cattr.type == cattr.EVENT) {
0747: if (attrs.containsKey(name, caseSensitive)) {
0748: env.warn(
0749: /* (non-Javadoc)
0750: * @i18n.test
0751: * @org-mes="an attribute or method named '" + p[0] + "' already is defined on " + p[1]
0752: */
0753: org.openlaszlo.i18n.LaszloMessages.getMessage(
0754: NodeModel.class.getName(), "051018-682",
0755: new Object[] { name, getMessageName() }),
0756: element);
0757: }
0758: attrs.put(name, cattr.value);
0759: } else if ((cattr.type == cattr.REFERENCE)
0760: || (cattr.type == cattr.PATH)) {
0761: if (references.containsKey(name, caseSensitive)) {
0762: env.warn(
0763: /* (non-Javadoc)
0764: * @i18n.test
0765: * @org-mes="redefining reference '" + p[0] + "' which has already been defined on " + p[1]
0766: */
0767: org.openlaszlo.i18n.LaszloMessages.getMessage(
0768: NodeModel.class.getName(), "051018-706",
0769: new Object[] { name, getMessageName() }),
0770: element);
0771: }
0772: references.put(name, cattr.value);
0773: } else if (cattr.type == cattr.STYLE) {
0774: if (styles.containsKey(name, caseSensitive)) {
0775: env.warn(
0776: // TODO [2006-08-22 ptw] i18n this
0777: "duplicate $style binding for '" + name
0778: + "' in " + getMessageName(), element);
0779: }
0780: styles.put(name, cattr.value);
0781: }
0782: }
0783:
0784: static String getDatasetContent(Element element,
0785: CompilationEnvironment env) {
0786: return getDatasetContent(element, env, false);
0787: }
0788:
0789: // For a dataset (well really for any Element), writes out the
0790: // child literal content as an escaped string, which could be used
0791: // to initialize the dataset at runtime.
0792: static String getDatasetContent(Element element,
0793: CompilationEnvironment env, boolean trimwhitespace) {
0794: String srcloc = CompilerUtils.sourceLocationDirective(element,
0795: true);
0796:
0797: // If type='http' or the path starts with http: or https:,
0798: // then don't attempt to include the data at compile
0799: // time. The runtime will have to recognize it as runtime
0800: // loaded URL data.
0801:
0802: if ("http".equals(element.getAttributeValue("type"))) {
0803: return "null";
0804: }
0805:
0806: boolean nsprefix = false;
0807: if ("true".equals(element.getAttributeValue("nsprefix"))) {
0808: nsprefix = true;
0809: }
0810:
0811: Element content = new Element("data");
0812:
0813: String src = element.getAttributeValue("src");
0814: // If 'src' attribute is a URL or null, don't try to expand it now,
0815: // just return. The LFC will have to interpret it as a runtime
0816: // loadable data URL.
0817: if ((src != null)
0818: && (src.startsWith("http:") || src.startsWith("https:"))) {
0819: return "null";
0820: } else if (src != null) {
0821: // Expands the src file content inline
0822: File file = env.resolveReference(element);
0823: try {
0824: Element literaldata = new org.jdom.input.SAXBuilder(
0825: false).build(file).getRootElement();
0826: if (literaldata == null) {
0827: return "null";
0828: }
0829: literaldata.detach();
0830: // add the expanded file contents as child node
0831: content.addContent(literaldata);
0832: } catch (org.jdom.JDOMException e) {
0833: throw new CompilationError(e);
0834: } catch (IOException e) {
0835: throw new CompilationError(e);
0836: } catch (java.lang.OutOfMemoryError e) {
0837: // The runtime gc is necessary in order for subsequent
0838: // compiles to succeed. The System gc is for good
0839: // luck.
0840: throw new CompilationError(
0841: /* (non-Javadoc)
0842: * @i18n.test
0843: * @org-mes="out of memory"
0844: */
0845: org.openlaszlo.i18n.LaszloMessages.getMessage(
0846: NodeModel.class.getName(), "051018-761"),
0847: element);
0848: }
0849: } else {
0850: // no 'src' attribute, use element inline content
0851: content.addContent(element.cloneContent());
0852: }
0853:
0854: // Serialize the child elements, as the local data
0855: org.jdom.output.XMLOutputter xmloutputter = new org.jdom.output.XMLOutputter();
0856: // strip the <dataset> wrapper
0857:
0858: // If nsprefix is false, remove namespace from elements before
0859: // serializing, or XMLOutputter puts a "xmlns" attribute on
0860: // the top element in the serialized string.
0861:
0862: if (!nsprefix) {
0863: removeNamespaces(content);
0864: }
0865:
0866: if (trimwhitespace) {
0867: trimWhitespace(content);
0868: }
0869:
0870: String body = null;
0871: if (content != null) {
0872: body = xmloutputter.outputString(content);
0873: return ScriptCompiler.quote(body);
0874: } else {
0875: return "null";
0876: }
0877:
0878: }
0879:
0880: // Recursively null out the namespace on elt and children
0881: static void removeNamespaces(Element elt) {
0882: elt.setNamespace(null);
0883: for (Iterator iter = elt.getChildren().iterator(); iter
0884: .hasNext();) {
0885: Element child = (Element) iter.next();
0886: removeNamespaces(child);
0887: }
0888: }
0889:
0890: // Recursively trim out the whitespace on text nodes
0891: static void trimWhitespace(Content elt) {
0892: if (elt instanceof Text) {
0893: ((Text) elt).setText(((Text) elt).getTextTrim());
0894: } else if (elt instanceof Element) {
0895: for (Iterator iter = ((Element) elt).getContent()
0896: .iterator(); iter.hasNext();) {
0897: Content child = (Content) iter.next();
0898: trimWhitespace(child);
0899: }
0900: }
0901: }
0902:
0903: boolean isDatapathElement(Element child) {
0904: return (child.getName().equals("datapath"));
0905: }
0906:
0907: /** Warn if named child tag conflicts with a declared attribute in the parent class.
0908: */
0909: void checkChildNameConflict(String parentName, Element child,
0910: CompilationEnvironment env) {
0911: String attrName = child.getAttributeValue("name");
0912: if (attrName != null) {
0913: AttributeSpec attrSpec = schema.getClassAttribute(
0914: parentName, attrName);
0915: // Only warn if the attribute we are shadowing has a declared initial value.
0916: if (attrSpec != null && attrSpec.defaultValue != null) {
0917: // TODO [2007-09-26 hqm] i18n this
0918: env.warn("Child tag '" + child.getName()
0919: + "' with attribute '" + attrName
0920: + "' conflicts with attribute named '"
0921: + attrName + "' of type '" + attrSpec.type
0922: + "' on parent tag '" + element.getName()
0923: + "'.", element);
0924: }
0925: }
0926: }
0927:
0928: void addChildren(CompilationEnvironment env) {
0929: // Encode the children
0930: for (Iterator iter = element.getChildren().iterator(); iter
0931: .hasNext();) {
0932: ElementWithLocationInfo child = (ElementWithLocationInfo) iter
0933: .next();
0934: if (!schema.canContainElement(element.getName(), child
0935: .getName())) {
0936: // If this element is allowed to contain HTML content, then
0937: // we don't want to warn about encountering an HTML child element.
0938: if (!(schema.hasTextContent(element) && schema
0939: .isHTMLElement(child))) {
0940: env.warn(
0941: // TODO [2007-09-26 hqm] i18n this
0942: "The tag '" + child.getName()
0943: + "' cannot be used as a child of "
0944: + element.getName(), element);
0945: }
0946: }
0947: try {
0948: if (child.getName().equals("data")) {
0949: checkChildNameConflict(element.getName(), child,
0950: env);
0951: // literal data
0952: addLiteralDataElement(child);
0953: } else if (isPropertyElement(child)) {
0954: addPropertyElement(child);
0955: } else if (schema.isHTMLElement(child)) {
0956: ; // ignore; the text compiler wiil handle this
0957: } else if (schema.isDocElement(child)) {
0958: ; // ignore doc nodes.
0959: } else if (isDatapathElement(child)) {
0960: checkChildNameConflict(element.getName(), child,
0961: env);
0962: NodeModel dpnode = elementAsModel(child, schema,
0963: env);
0964: this .datapath = dpnode;
0965: } else {
0966: checkChildNameConflict(element.getName(), child,
0967: env);
0968: NodeModel childModel = elementAsModel(child,
0969: schema, env);
0970: children.add(childModel);
0971: totalSubnodes += childModel.totalSubnodes();
0972: }
0973: } catch (CompilationError e) {
0974: env.warn(e);
0975: }
0976: }
0977: }
0978:
0979: void addPropertyElement(Element element) {
0980: String tagName = element.getName();
0981: if (tagName.equals("method")) {
0982: addMethodElement(element);
0983: } else if (tagName.equals("handler")) {
0984: addHandlerElement(element);
0985: } else if (tagName.equals("event")) {
0986: addEventElement(element);
0987: } else if (tagName.equals("attribute")) {
0988: addAttributeElement(element);
0989: }
0990: }
0991:
0992: /** Defines an event handler.
0993: *
0994: * <handler name="eventname" [method="methodname"]>
0995: * [function body]
0996: * </handler>
0997: *
0998: * This can do a compile time check to see if eventname is declared or
0999: * if there is an attribute FOO such that name="onFOO".
1000: */
1001:
1002: void addHandlerElement(Element element) {
1003: String srcloc = CompilerUtils.sourceLocationDirective(element,
1004: true);
1005: String method = element.getAttributeValue("method");
1006: // event name
1007: String event = element.getAttributeValue("name");
1008: String args = CompilerUtils.attributeLocationDirective(element,
1009: "args")
1010: + XMLUtils.getAttributeValue(element, "args", "");
1011: if ((event == null || !ScriptCompiler.isIdentifier(event))) {
1012: env.warn("handler needs a non-null name attribute");
1013: return;
1014: }
1015:
1016: String parent_name = element.getParentElement()
1017: .getAttributeValue("id");
1018: String name_loc = CompilerUtils.attributeLocationDirective(
1019: element, "event");
1020: if (parent_name == null) {
1021: parent_name = CompilerUtils.attributeUniqueName(element,
1022: "event");
1023: }
1024: // Names have to be unique across binary libraries, so append
1025: // parent name
1026: String name = env.methodNameGenerator.next() + "_"
1027: + parent_name + "_reference";
1028: String reference = element.getAttributeValue("reference");
1029: Object referencefn = "null";
1030: if (reference != null) {
1031: String ref_loc = CompilerUtils.attributeLocationDirective(
1032: element, "reference");
1033: referencefn = new Function(name, args,
1034: "\n#pragma 'withThis'\n" + "return ("
1035: + "#beginAttribute\n" + ref_loc + reference
1036: + "\n#endAttribute\n)", ref_loc);
1037: }
1038:
1039: // delegates is only used to determine whether to
1040: // default clickable to true. Clickable should only
1041: // default to true if the event handler is attached to
1042: // this view.
1043: if (reference == null) {
1044: delegates.put(event, Boolean.TRUE);
1045: }
1046: delegateList.add(ScriptCompiler.quote(event));
1047: if (method != null) {
1048: delegateList.add(ScriptCompiler.quote(method));
1049: } else {
1050: delegateList.add(ScriptCompiler.quote(name));
1051: }
1052: delegateList.add(referencefn);
1053:
1054: String body = element.getText();
1055:
1056: String childcontentloc = CompilerUtils.sourceLocationDirective(
1057: element, true);
1058:
1059: if (attrs.containsKey(name, caseSensitive)) {
1060: env.warn("an attribute or method named '" + name
1061: + "' already is defined on " + getMessageName(),
1062: element);
1063: }
1064:
1065: // If non-empty body AND method name are specified, flag an error
1066: // If non-empty body, pack it up as a function definition
1067: if (body != null && body.trim().length() > 0) {
1068: if (method != null) {
1069: env
1070: .warn(
1071: "you cannot declare both a 'method' attribute "
1072: + "*and* a function body on a handler element",
1073: element);
1074: }
1075: }
1076: Function fndef = new Function(name,
1077: //"#beginAttribute\n" +
1078: CompilerUtils.attributeLocationDirective(element,
1079: "args")
1080: + args, "\n#beginContent\n"
1081: + "\n#pragma 'methodName=" + name + "'\n"
1082: + "\n#pragma 'withThis'\n" + childcontentloc
1083: + body + "\n#endContent", name_loc);
1084:
1085: attrs.put(name, fndef);
1086: }
1087:
1088: void addHandlerFromAttribute(Element element, String event,
1089: String body) {
1090: String srcloc = CompilerUtils.sourceLocationDirective(element,
1091: true);
1092: String parent_name = element.getAttributeValue("id");
1093: if (parent_name == null) {
1094: parent_name = CompilerUtils.attributeUniqueName(element,
1095: "event");
1096: }
1097: String name = env.methodNameGenerator.next();
1098: Object referencefn = "null";
1099:
1100: // delegates is only used to determine whether to
1101: // default clickable to true. Clickable should only
1102: // default to true if the event handler is attached to
1103: // this view.
1104: delegates.put(event, Boolean.TRUE);
1105: delegateList.add(ScriptCompiler.quote(event));
1106: delegateList.add(ScriptCompiler.quote(name));
1107: delegateList.add(referencefn);
1108:
1109: String childcontentloc = CompilerUtils.sourceLocationDirective(
1110: element, true);
1111:
1112: Function fndef = new
1113: // Use "mangled" name, so it will be unique
1114: Function(srcloc + parent_name + "_" + name,
1115: //"#beginAttribute\n" +
1116: "", "\n#beginContent\n" + "\n#pragma 'methodName="
1117: + name + "'\n" + "\n#pragma 'withThis'\n"
1118: + childcontentloc + body + "\n#endContent");
1119:
1120: attrs.put(name, fndef);
1121: }
1122:
1123: void addMethodElement(Element element) {
1124: String srcloc = CompilerUtils.sourceLocationDirective(element,
1125: true);
1126: String name = element.getAttributeValue("name");
1127: String event = element.getAttributeValue("event");
1128: String args = XMLUtils.getAttributeValue(element, "args", "");
1129: String override = element.getAttributeValue("override");
1130:
1131: if ((name == null || !ScriptCompiler.isIdentifier(name))
1132: && (event == null || !ScriptCompiler
1133: .isIdentifier(event))) {
1134: env.warn(
1135: /* (non-Javadoc)
1136: * @i18n.test
1137: * @org-mes="method needs a non-null name or event attribute"
1138: */
1139: org.openlaszlo.i18n.LaszloMessages.getMessage(
1140: NodeModel.class.getName(), "051018-832"));
1141: return;
1142: }
1143: if (name != null && sDeprecatedMethods.containsKey(name)) {
1144: String oldName = name;
1145: String newName = (String) sDeprecatedMethods.get(name);
1146: name = newName;
1147: env.warn(
1148: /* (non-Javadoc)
1149: * @i18n.test
1150: * @org-mes=p[0] + " is deprecated. " + "This method will be compiled as <method name='" + p[1] + "' instead. " + "Please update your sources."
1151: */
1152: org.openlaszlo.i18n.LaszloMessages.getMessage(
1153: NodeModel.class.getName(), "051018-846",
1154: new Object[] { oldName, newName }), element);
1155: }
1156:
1157: String parent_name = element.getParentElement()
1158: .getAttributeValue("id");
1159: String name_loc = (name == null ? CompilerUtils
1160: .attributeLocationDirective(element, "event")
1161: : CompilerUtils.attributeLocationDirective(element,
1162: "name"));
1163: if (parent_name == null) {
1164: parent_name = (name == null ? CompilerUtils
1165: .attributeUniqueName(element, "event")
1166: : CompilerUtils
1167: .attributeUniqueName(element, "name"));
1168: }
1169: if (event != null) {
1170: if (name == null) {
1171: // Names have to be unique across binary libraries, so
1172: // append parent name
1173: name = env.methodNameGenerator.next() + "_"
1174: + parent_name + "_reference";
1175: }
1176: String reference = element.getAttributeValue("reference");
1177: Object referencefn = "null";
1178: if (reference != null) {
1179: String ref_loc = CompilerUtils
1180: .attributeLocationDirective(element,
1181: "reference");
1182: referencefn = new Function(name, CompilerUtils
1183: .attributeLocationDirective(element, "args")
1184: + args, "\n#pragma 'withThis'\n" + "return ("
1185: + "#beginAttribute\n" + ref_loc + reference
1186: + "\n#endAttribute\n)", ref_loc);
1187: }
1188: // delegates is only used to determine whether to
1189: // default clickable to true. Clickable should only
1190: // default to true if the event handler is attached to
1191: // this view.
1192: if (reference == null)
1193: delegates.put(event, Boolean.TRUE);
1194: delegateList.add(ScriptCompiler.quote(event));
1195: delegateList.add(ScriptCompiler.quote(name));
1196: delegateList.add(referencefn);
1197: }
1198: String body = element.getText();
1199:
1200: String childcontentloc = CompilerUtils.sourceLocationDirective(
1201: element, true);
1202: Function fndef = new Function(name,
1203: //"#beginAttribute\n" +
1204: CompilerUtils.attributeLocationDirective(element,
1205: "args")
1206: + args, "\n#beginContent\n"
1207: + "\n#pragma 'methodName=" + name + "'\n"
1208: + "\n#pragma 'withThis'\n" + childcontentloc
1209: + body + "\n#endContent", name_loc);
1210:
1211: if (attrs.containsKey(name, caseSensitive)) {
1212: env.warn(
1213: /* (non-Javadoc)
1214: * @i18n.test
1215: * @org-mes="an attribute or method named '" + p[0] + "' already is defined on " + p[1]
1216: */
1217: org.openlaszlo.i18n.LaszloMessages.getMessage(
1218: NodeModel.class.getName(), "051018-922",
1219: new Object[] { name, getMessageName() }), element);
1220: }
1221:
1222: if (!("true".equals(override))) {
1223: String classname = element.getParentElement().getName();
1224: // Just check method declarations on regular node.
1225: // Method declarations inside of class definitions will be already checked elsewhere,
1226: // in the call from ClassCompiler.updateSchema to schema.addElement
1227: if (!"class".equals(classname)) {
1228: schema.checkInstanceMethodDeclaration(element,
1229: classname, name, env);
1230: }
1231: }
1232:
1233: attrs.put(name, fndef);
1234: }
1235:
1236: // Pattern matcher for '$once{...}' style constraints
1237: Pattern constraintPat = Pattern
1238: .compile("^\\s*\\$(\\w*)\\s*\\{(.*)\\}\\s*");
1239:
1240: CompiledAttribute compileAttribute(Element source, String name,
1241: String value, Schema.Type type, String when) {
1242:
1243: String srcloc = CompilerUtils.sourceLocationDirective(source,
1244: true);
1245: String parent_name = source.getAttributeValue("id");
1246: if (parent_name == null) {
1247: parent_name = CompilerUtils.attributeUniqueName(source,
1248: name);
1249: }
1250: // Some values are not canonicalized to String
1251: Object canonicalValue = null;
1252: boolean warnOnDeprecatedConstraints = true;
1253:
1254: if (value == null) {
1255: throw new RuntimeException(
1256: /* (non-Javadoc)
1257: * @i18n.test
1258: * @org-mes="value is null in " + p[0]
1259: */
1260: org.openlaszlo.i18n.LaszloMessages.getMessage(
1261: NodeModel.class.getName(), "051018-956",
1262: new Object[] { source }));
1263: }
1264:
1265: Matcher m = constraintPat.matcher(value);
1266: // value.matches("\\s*['\"]\\S*['\"]\\s*")
1267: if (value.matches("\\s*\\$\\s*\\(")) {
1268: env
1269: .warn(
1270: "The syntax '$(...)' is not valid, "
1271: + "you probably meant to use curly-braces instead '${...}'",
1272: source);
1273: } else if (m.matches()) {
1274: // extract out $when{value}
1275: when = m.group(1);
1276: value = m.group(2);
1277: // Expressions override any when value, default is
1278: // constraint
1279: if (when.equals("")) {
1280: when = WHEN_ALWAYS;
1281: }
1282: } else if (type == ViewSchema.XML_LITERAL) {
1283: value = "LzDataNode.stringToLzData(" + value + ")";
1284: } else if (type == ViewSchema.COLOR_TYPE) {
1285: if (when.equals(WHEN_IMMEDIATELY)) {
1286: try {
1287: value = "0x"
1288: + Integer.toHexString(ViewSchema
1289: .parseColor(value));
1290: } catch (ColorFormatException e) {
1291: // Or just set when to WHEN_ONCE and fall
1292: // through to TODO?
1293: throw new CompilationError(source, name, e);
1294: }
1295: }
1296: // TODO: [2003-05-02 ptw] Wrap non-constant colors in
1297: // runtime parser
1298: } else if (type == ViewSchema.CSS_TYPE) {
1299: if (when.equals(WHEN_IMMEDIATELY)) {
1300: try {
1301: Map cssProperties = new CSSParser(
1302: new AttributeStream(source, name, value))
1303: .Parse();
1304: for (Iterator i2 = cssProperties.entrySet()
1305: .iterator(); i2.hasNext();) {
1306: Map.Entry entry = (Map.Entry) i2.next();
1307: Object mv = entry.getValue();
1308: if (mv instanceof String) {
1309: entry.setValue(ScriptCompiler
1310: .quote((String) mv));
1311: }
1312: }
1313: canonicalValue = cssProperties;
1314: } catch (org.openlaszlo.css.ParseException e) {
1315: // Or just set when to WHEN_ONCE and fall
1316: // through to TODO?
1317: throw new CompilationError(e);
1318: } catch (org.openlaszlo.css.TokenMgrError e) {
1319: // Or just set when to WHEN_ONCE and fall
1320: // through to TODO?
1321: throw new CompilationError(e);
1322: }
1323: }
1324: // TODO: [2003-05-02 ptw] Wrap non-constant styles in
1325: // runtime parser
1326: } else if (type == ViewSchema.STRING_TYPE
1327: || type == ViewSchema.TOKEN_TYPE
1328: || type == ViewSchema.ID_TYPE) {
1329: // Immediate string attributes are auto-quoted
1330: if (when.equals(WHEN_IMMEDIATELY)) {
1331: value = ScriptCompiler.quote(value);
1332: }
1333: } else if ((type == ViewSchema.EXPRESSION_TYPE)
1334: || (type == ViewSchema.BOOLEAN_TYPE)) {
1335: // No change currently, possibly analyze expressions
1336: // and default non-constant to when="once" in the
1337: // future
1338: } else if (type == ViewSchema.INHERITABLE_BOOLEAN_TYPE) {
1339: // change "inherit" to null and pass true/false through as expression
1340: if ("inherit".equals(value)) {
1341: value = "null";
1342: } else if ("true".equals(value)) {
1343: value = "true";
1344: } else if ("false".equals(value)) {
1345: value = "false";
1346: } else {
1347: // TODO [hqm 2007-0] i8nalize this message
1348: env
1349: .warn(
1350: "attribute '"
1351: + name
1352: + "' must have the value 'true', 'false', or 'inherit'",
1353: element);
1354: }
1355: } else if (type == ViewSchema.NUMBER_TYPE) {
1356: // No change currently, possibly analyze expressions
1357: // and default non-constant to when="once" in the
1358: // future
1359: } else if (type == ViewSchema.NUMBER_EXPRESSION_TYPE
1360: || type == ViewSchema.SIZE_EXPRESSION_TYPE) {
1361: // if it's a number that ends in percent:
1362: if (value.trim().endsWith("%")) {
1363: String numstr = value.trim();
1364: numstr = numstr.substring(0, numstr.length() - 1);
1365: try {
1366: double scale = new Float(numstr).floatValue() / 100.0;
1367: warnOnDeprecatedConstraints = false;
1368: String referenceAttribute = name;
1369: if (name.equals("x")) {
1370: referenceAttribute = "width";
1371: } else if (name.equals("y")) {
1372: referenceAttribute = "height";
1373: }
1374: value = "immediateparent." + referenceAttribute;
1375: if (scale != 1.0) {
1376: // This special case doesn't change the
1377: // semantics, but it generates shorter (since
1378: // the sc doesn't fold constants) and more
1379: // debuggable code
1380: value += "\n * " + scale;
1381: }
1382: // fall through to the reference case
1383: } catch (NumberFormatException e) {
1384: // fall through
1385: }
1386: }
1387: // if it's a literal, treat it the same as a number
1388: try {
1389: new Float(value); // for effect, to generate the exception
1390: when = WHEN_IMMEDIATELY;
1391: } catch (NumberFormatException e) {
1392: // It's not a constant, unless when has been
1393: // specified, default to a constraint
1394: if (when.equals(WHEN_IMMEDIATELY)) {
1395: if (warnOnDeprecatedConstraints) {
1396: env.warn("Use " + name + "=\"${" + value
1397: + "}\" instead.", element);
1398: }
1399: when = WHEN_ALWAYS;
1400: }
1401: }
1402: } else if (type == ViewSchema.EVENT_HANDLER_TYPE) {
1403: // Someone said <attribute name="..." ... /> instead of
1404: // <event name="..." />
1405: throw new CompilationError(
1406: element,
1407: name,
1408: new Throwable(
1409: "'"
1410: + name
1411: + "' is an event and may not be redeclared as an attribute"));
1412: } else if (type == ViewSchema.REFERENCE_TYPE) {
1413: // type="reference" is defined to imply when="once"
1414: // since reference expressions are unlikely to
1415: // evaluate correctly at when="immediate" time
1416: if (when.equals(WHEN_IMMEDIATELY)) {
1417: when = WHEN_ONCE;
1418: }
1419: } else if (type == ViewSchema.METHOD_TYPE) {
1420: // methods are emitted elsewhere
1421: } else {
1422: throw new RuntimeException("unknown schema datatype "
1423: + type);
1424: }
1425:
1426: if (canonicalValue == null)
1427: canonicalValue = value;
1428:
1429: // Handle when cases
1430: // N.B., $path and $style are not really when values, but
1431: // there you go...
1432: if (when.equals(WHEN_PATH)) {
1433: return new CompiledAttribute(CompiledAttribute.PATH, srcloc
1434: + ScriptCompiler.quote(value) + "\n");
1435: } else if (when.equals(WHEN_STYLE)) {
1436: return new CompiledAttribute(CompiledAttribute.STYLE,
1437: srcloc + value + "\n");
1438: } else if (when.equals(WHEN_ONCE)) {
1439: return new CompiledAttribute(CompiledAttribute.REFERENCE,
1440: "function "
1441: + parent_name
1442: + "_"
1443: + name
1444: + "_once"
1445: + " () {"
1446: + "\n#pragma 'withThis'\n"
1447: +
1448: // Use this.setAttribute so that the compiler
1449: // will recognize it for inlining.
1450: "this.setAttribute("
1451: + ScriptCompiler.quote(name) + " , "
1452: + "\n#beginAttribute\n" + srcloc
1453: + canonicalValue + "\n#endAttribute\n)}");
1454: } else if (when.equals(WHEN_ALWAYS)) {
1455: return new CompiledAttribute(CompiledAttribute.REFERENCE,
1456: "function "
1457: + parent_name
1458: + "_"
1459: + name
1460: + "_always"
1461: + " () {"
1462: + "\n#pragma 'constraintFunction'\n"
1463: + "\n#pragma 'withThis'\n"
1464: +
1465: // Use this.setAttribute so that the compiler
1466: // will recognize it for inlining.
1467: "this.setAttribute("
1468: + ScriptCompiler.quote(name) + ", "
1469: + "\n#beginAttribute\n" + srcloc
1470: + canonicalValue + "\n#endAttribute\n)}");
1471: } else if (when.equals(WHEN_IMMEDIATELY)) {
1472: if ((CanvasCompiler.isElement(source) && ("width"
1473: .equals(name) || "height".equals(name)))
1474: || canonicalValue instanceof Map) {
1475: // The Canvas compiler depends on seeing width/height
1476: // unadulterated <sigh />. Or, if it's already an
1477: // object, e.g., compiled from a CSS list, we
1478: // don't want to mess it up.
1479: //
1480: // TODO: [2007-05-05 ptw] (LPP-3949) The
1481: // #beginAttribute directives for the parser should
1482: // be added when the attribute is written, not
1483: // here...
1484: return new CompiledAttribute(canonicalValue);
1485: } else {
1486: return new CompiledAttribute("\n#beginAttribute\n"
1487: + srcloc + canonicalValue + "\n#endAttribute\n");
1488: }
1489: } else {
1490: throw new CompilationError("invalid when value '" + when
1491: + "'", source);
1492: }
1493: }
1494:
1495: /* Handle the <event> tag
1496: * example: <event name="onfoobar"/>
1497: */
1498: void addEventElement(Element element) {
1499: String name;
1500: try {
1501: name = ElementCompiler.requireIdentifierAttributeValue(
1502: element, "name");
1503: } catch (MissingAttributeException e) {
1504: throw new CompilationError(
1505: /* (non-Javadoc)
1506: * @i18n.test
1507: * @org-mes="'name' is a required attribute of <" + p[0] + "> and must be a valid identifier"
1508: */
1509: org.openlaszlo.i18n.LaszloMessages.getMessage(
1510: NodeModel.class.getName(), "051018-1157",
1511: new Object[] { element.getName() }), element);
1512: }
1513:
1514: if (events.containsKey(name, caseSensitive)) {
1515: env.warn(
1516: /* (non-Javadoc)
1517: * @i18n.test
1518: * @org-mes="redefining event '" + p[0] + "' which has already been defined on " + p[1]
1519: */
1520: org.openlaszlo.i18n.LaszloMessages.getMessage(
1521: NodeModel.class.getName(), "051018-694",
1522: new Object[] { name, getMessageName() }), element);
1523: }
1524:
1525: // An event is really just an attribute with an implicit
1526: // default (sentinal) value
1527: CompiledAttribute cattr = new CompiledAttribute(
1528: CompiledAttribute.EVENT, "LzDeclaredEvent");
1529: addAttribute(cattr, name, attrs, events, references, paths,
1530: styles);
1531: }
1532:
1533: void addAttributeElement(Element element) {
1534: String name;
1535: try {
1536: name = ElementCompiler.requireIdentifierAttributeValue(
1537: element, "name");
1538: } catch (MissingAttributeException e) {
1539: throw new CompilationError(
1540: /* (non-Javadoc)
1541: * @i18n.test
1542: * @org-mes="'name' is a required attribute of <" + p[0] + "> and must be a valid identifier"
1543: */
1544: org.openlaszlo.i18n.LaszloMessages.getMessage(
1545: NodeModel.class.getName(), "051018-1157",
1546: new Object[] { element.getName() }), element);
1547: }
1548:
1549: String value = element.getAttributeValue("value");
1550: String when = element.getAttributeValue("when");
1551: String typestr = element.getAttributeValue("type");
1552: Element parent = element.getParentElement();
1553: String parent_name = parent.getAttributeValue("id");
1554:
1555: if (parent_name == null) {
1556: parent_name = CompilerUtils.attributeUniqueName(element,
1557: name);
1558: }
1559:
1560: // Default when according to parent
1561: if (when == null) {
1562: when = this .getAttributeValueDefault(name, "when",
1563: WHEN_IMMEDIATELY);
1564: }
1565:
1566: Schema.Type type = null;
1567: Schema.Type parenttype = null;
1568:
1569: AttributeSpec parentAttrSpec = schema.getAttributeSpec(parent
1570: .getName(), name);
1571: boolean forceOverride = parentAttrSpec != null
1572: && "true".equals(parentAttrSpec.override);
1573:
1574: try {
1575: if (parent.getName().equals("class")) {
1576: parenttype = getAttributeTypeInfoFromSuperclass(parent,
1577: name);
1578: } else {
1579: parenttype = schema.getAttributeType(parent, name);
1580:
1581: }
1582: } catch (UnknownAttributeException e) {
1583: // If attribute type is not defined on parent, leave
1584: // parenttype null. The user can define it however they
1585: // like.
1586: }
1587:
1588: if (typestr == null) {
1589: // Did user supply an explicit attribute type?
1590: // No. Default to parent type if there is one, else
1591: // EXPRESSION type.
1592: if (parenttype == null) {
1593: type = ViewSchema.EXPRESSION_TYPE;
1594: } else {
1595: type = parenttype;
1596: }
1597: } else {
1598: // parse attribute type and compare to parent type
1599: type = schema.getTypeForName(typestr);
1600: if (type == null) {
1601: throw new CompilationError(
1602: /* (non-Javadoc)
1603: * @i18n.test
1604: * @org-mes="unknown attribute type: " + p[0]
1605: */
1606: org.openlaszlo.i18n.LaszloMessages.getMessage(
1607: NodeModel.class.getName(), "051018-1211",
1608: new Object[] { typestr }), element);
1609: }
1610:
1611: // If we are trying to declare the attribute with a
1612: // conflicting type to the parent, throw an error
1613: if (!forceOverride && parenttype != null
1614: && type != parenttype) {
1615: env.warn(new CompilationError(element, name,
1616: new Throwable(
1617: /* (non-Javadoc)
1618: * @i18n.test
1619: * @org-mes="In element '" + p[0] + "' attribute '" + p[1] + "' with type '" + p[2] + "' is overriding parent class attribute with same name but different type: " + p[3]
1620: */
1621: org.openlaszlo.i18n.LaszloMessages.getMessage(
1622: NodeModel.class.getName(),
1623: "051018-1227", new Object[] {
1624: parent.getName(), name,
1625: type.toString(),
1626: parenttype.toString() }))));
1627: }
1628: }
1629:
1630: // Warn if we are overidding a method, handler, or other function
1631: if (!forceOverride
1632: && (parenttype == schema.METHOD_TYPE
1633: || parenttype == schema.EVENT_HANDLER_TYPE
1634: || parenttype == schema.SETTER_TYPE || parenttype == schema.REFERENCE_TYPE)) {
1635: env
1636: .warn(
1637: "In element '"
1638: + parent.getName()
1639: + "' attribute '"
1640: + name
1641: + "' is overriding parent class attribute which has the same name but type: "
1642: + parenttype.toString(), element);
1643: }
1644:
1645: // Don't initialize an attribute that is only declared.
1646: if (value != null) {
1647: CompiledAttribute cattr = compileAttribute(element, name,
1648: value, type, when);
1649: addAttribute(cattr, name, attrs, events, references, paths,
1650: styles);
1651: }
1652:
1653: // Add entry for attribute setter function
1654: String setter = element.getAttributeValue("setter");
1655:
1656: if (setter != null) {
1657: String srcloc = CompilerUtils.sourceLocationDirective(
1658: element, true);
1659: // By convention 'anonymous' setters are put in the 'lzc'
1660: // namespace with the name set_<property name>
1661: String settername = "$lzc$" + "set_" + name;
1662: // Maybe we need a new type for "function"?
1663: Function setterfn = new Function(settername,
1664: // the lone argument to a setter is named after
1665: // the attribute
1666: name, "\n#beginContent\n"
1667: + "\n#pragma 'withThis'\n" + srcloc
1668: + setter + "\n#endContent", srcloc);
1669:
1670: if (setters.get(name) != null) {
1671: env.warn(
1672: "a setter for attribute named '" + name
1673: + "' is already defined on "
1674: + getMessageName(), element);
1675: }
1676:
1677: // TODO: [2008-01-21 ptw] some day this will be coalesed
1678: // into just creating a method named `"set" + name`
1679: attrs.put(settername, setterfn);
1680: setters.put(name, ScriptCompiler.quote(settername));
1681: }
1682: }
1683:
1684: /* Handle a <data> tag.
1685: * If there is more than one immediate child data node at the top level, signal a warning.
1686: */
1687:
1688: void addLiteralDataElement(Element element) {
1689: String name = element.getAttributeValue("name");
1690:
1691: if (name == null) {
1692: name = "initialdata";
1693: }
1694:
1695: boolean trimWhitespace = "true".equals(element
1696: .getAttributeValue("trimwhitespace"));
1697:
1698: String xmlcontent = getDatasetContent(element, env,
1699: trimWhitespace);
1700:
1701: Element parent = element.getParentElement();
1702:
1703: CompiledAttribute cattr = compileAttribute(element, name,
1704: xmlcontent, ViewSchema.XML_LITERAL, WHEN_IMMEDIATELY);
1705:
1706: addAttribute(cattr, name, attrs, events, references, paths,
1707: styles);
1708: }
1709:
1710: boolean hasAttribute(String name) {
1711: return attrs.containsKey(name);
1712: }
1713:
1714: void removeAttribute(String name) {
1715: attrs.remove(name);
1716: }
1717:
1718: void setAttribute(String name, Object value) {
1719: attrs.put(name, value);
1720: }
1721:
1722: void addText() {
1723: if (schema.hasHTMLContent(element)) {
1724: String text = TextCompiler.getHTMLContent(element);
1725: if (text.length() != 0) {
1726: if (!attrs.containsKey("text")) {
1727: attrs.put("text", ScriptCompiler.quote(text));
1728: }
1729: }
1730: } else if (schema.hasTextContent(element)) {
1731: String text;
1732: // The current inputtext component doesn't understand
1733: // HTML, but we'd like to have some way to enter
1734: // linebreaks in the source.
1735: text = TextCompiler.getInputText(element);
1736: if (text.length() != 0) {
1737: if (!attrs.containsKey("text")) {
1738: attrs.put("text", ScriptCompiler.quote(text));
1739: }
1740: }
1741: }
1742: }
1743:
1744: void updateAttrs() {
1745: if (!setters.isEmpty()) {
1746: attrs.put("$setters", setters);
1747: }
1748: if (!delegateList.isEmpty()) {
1749: attrs.put("$delegates", delegateList);
1750: }
1751: if (!references.isEmpty()) {
1752: attrs.put("$refs", references);
1753: }
1754: if (!paths.isEmpty()) {
1755: attrs.put("$paths", paths);
1756: }
1757: if (datapath != null) {
1758: attrs.put("$datapath", datapath.asMap());
1759: // If we've got an explicit datapath value, we have to
1760: // null out the "datapath" attribute with the magic
1761: // LzNode._ignoreAttribute value, so it doesn't get
1762: // overridden by an inherited value from the class.
1763: attrs.put("datapath", "LzNode._ignoreAttribute");
1764: }
1765:
1766: if (!styles.isEmpty()) {
1767: String styleMap;
1768: try {
1769: java.io.Writer writer = new java.io.StringWriter();
1770: ScriptCompiler.writeObject(styles, writer);
1771: styleMap = writer.toString();
1772: } catch (java.io.IOException e) {
1773: throw new ChainedException(e);
1774: }
1775: // NOTE: [2006-09-04 ptw] The $styles method _must_ create
1776: // a new map each time, because the sub-class or instance
1777: // may modify it.
1778: attrs.put("$styles", new Function("", "", "\n#beginContent"
1779: + "\n#pragma 'withThis'"
1780: + "\n#pragma 'methodName=$styles'"
1781: + "\nvar map = super.$styles() || (new Object);"
1782: + "\nvar s = " + styleMap + ";"
1783: + "\nfor (var k in s) { map[k] = s[k]; };"
1784: + "\nreturn map;" + "\n#endContent" + "\n"));
1785: }
1786: }
1787:
1788: Map asMap() {
1789: Map map = new LinkedHashMap();
1790: updateAttrs();
1791: map.put("name", ScriptCompiler.quote(className));
1792: map.put("attrs", attrs);
1793: if (id != null) {
1794: map.put("id", ScriptCompiler.quote(id));
1795: attrs.put("id", ScriptCompiler.quote(id));
1796: }
1797: if (!children.isEmpty()) {
1798: List childMaps = new Vector(children.size());
1799: for (Iterator iter = children.iterator(); iter.hasNext();)
1800: childMaps.add(((NodeModel) iter.next()).asMap());
1801: map.put("children", childMaps);
1802: }
1803: return map;
1804: }
1805:
1806: /** Expand eligible instances by replacing the instance by the
1807: * merge of its class definition with the instance content
1808: * (attributes and children). An eligible instance is an instance
1809: * of a compile-time class, that doesn't contain any merge
1810: * stoppers. If the class and the instance contain a member with
1811: * the same name, this is a merge stopper. In the future, this
1812: * restriction may be relaxed, but will probably always include
1813: * the case where a class and instance have a member with the same
1814: * name and the instance name calls a superclass method. */
1815: NodeModel expandClassDefinitions() {
1816: NodeModel model = this ;
1817: while (true) {
1818: ClassModel classModel = schema
1819: .getClassModel(model.className);
1820: if (classModel == null)
1821: break;
1822: if (classModel.getSuperclassName() == null)
1823: break;
1824: if (!classModel.getInline())
1825: break;
1826: model = classModel.applyClass(model);
1827: // Iterate to allow for the original classes superclass to
1828: // be expanded as well.
1829: }
1830: // Recurse. Make a copy so we can replace the child list.
1831: // TODO [2004-0604]: As an optimization, only do this if one
1832: // of the children changed.
1833: model = (NodeModel) model.clone();
1834: for (ListIterator iter = model.children.listIterator(); iter
1835: .hasNext();) {
1836: NodeModel child = (NodeModel) iter.next();
1837: iter.set(child.expandClassDefinitions());
1838: }
1839: return model;
1840: }
1841:
1842: /** Replace members of this with like-named members of source. */
1843: void updateMembers(NodeModel source) {
1844: final String OPTIONS_ATTR_NAME = "options";
1845:
1846: // FIXME [2004-06-04]: only compare events with the same reference
1847: if (CollectionUtils.containsAny(events.normalizedKeySet(),
1848: source.events.normalizedKeySet())) {
1849: Collection sharedEvents = CollectionUtils.intersection(
1850: events.normalizedKeySet(), source.events
1851: .normalizedKeySet());
1852: throw new CompilationError(
1853: /* (non-Javadoc)
1854: * @i18n.test
1855: * @org-mes="Both the class and the instance or subclass define the " + p[0] + p[1]
1856: */
1857: org.openlaszlo.i18n.LaszloMessages
1858: .getMessage(
1859: NodeModel.class.getName(),
1860: "051018-1388",
1861: new Object[] {
1862: new ChoiceFormat(
1863: "1#event |1<events ")
1864: .format(sharedEvents
1865: .size()),
1866: new ListFormat("and")
1867: .format(sharedEvents) }));
1868:
1869: }
1870:
1871: // Check for duplicate methods. Collect all the keys that name
1872: // a Function in both the source and target.
1873: List sharedMethods = new Vector();
1874: for (Iterator iter = attrs.normalizedKeySet().iterator(); iter
1875: .hasNext();) {
1876: String key = (String) iter.next();
1877: if (attrs.get(key) instanceof Function
1878: && source.attrs.get(key) instanceof Function)
1879: sharedMethods.add(key);
1880: }
1881: if (!sharedMethods.isEmpty())
1882: throw new CompilationError(
1883: /* (non-Javadoc)
1884: * @i18n.test
1885: * @org-mes="Both the class and the instance or subclass define the method" + p[0] + p[1]
1886: */
1887: org.openlaszlo.i18n.LaszloMessages
1888: .getMessage(NodeModel.class.getName(),
1889: "051018-1409", new Object[] {
1890: new ChoiceFormat("1# |1<s ")
1891: .format(sharedMethods
1892: .size()),
1893: new ListFormat("and")
1894: .format(sharedMethods) }));
1895:
1896: // Check for attributes that have a value in this and
1897: // a setter in the source. These can't be merged.
1898: Collection overriddenAttributes = CollectionUtils.intersection(
1899: attrs.normalizedKeySet(), source.setters
1900: .normalizedKeySet());
1901: if (!overriddenAttributes.isEmpty())
1902: throw new CompilationError(
1903: /* (non-Javadoc)
1904: * @i18n.test
1905: * @org-mes="A class that defines a value can't be inlined against a " + "subclass or instance that defines a setter. The following " + p[0] + " this condition: " + p[1]
1906: */
1907: org.openlaszlo.i18n.LaszloMessages
1908: .getMessage(
1909: NodeModel.class.getName(),
1910: "051018-1422",
1911: new Object[] {
1912: new ChoiceFormat(
1913: "1#attribute violates|1<attributes violate")
1914: .format(overriddenAttributes
1915: .size()),
1916: new ListFormat("and")
1917: .format(overriddenAttributes) }));
1918:
1919: // Do the actual merge.
1920: id = source.id;
1921: if (source.initstage != null)
1922: initstage = source.initstage;
1923: Object options = attrs.get(OPTIONS_ATTR_NAME);
1924: Object sourceOptions = source.attrs.get(OPTIONS_ATTR_NAME);
1925: attrs.putAll(source.attrs);
1926: if (options instanceof Map && sourceOptions instanceof Map) {
1927: // System.err.println(options);
1928: // System.err.println(sourceOptions);
1929: Map newOptions = new HashMap((Map) options);
1930: newOptions.putAll((Map) sourceOptions);
1931: attrs.put(OPTIONS_ATTR_NAME, newOptions);
1932: }
1933: delegates.putAll(source.delegates);
1934: events.putAll(source.events);
1935: references.putAll(source.references);
1936: paths.putAll(source.paths);
1937: setters.putAll(source.setters);
1938: styles.putAll(source.styles);
1939: delegateList.addAll(source.delegateList);
1940: // TBD: warn on children that share a name?
1941: // TBD: update the node count
1942: children.addAll(source.children);
1943: }
1944: }
|