001: /* *****************************************************************************
002: * ClassNode.java
003: * ****************************************************************************/
004:
005: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
006: * Copyright 2001-2007 Laszlo Systems, Inc. All Rights Reserved. *
007: * Use is subject to license terms. *
008: * J_LZ_COPYRIGHT_END *********************************************************/
009:
010: package org.openlaszlo.compiler;
011:
012: import java.io.*;
013: import java.util.*;
014: import org.apache.log4j.*;
015: import org.jdom.Element;
016: import org.jdom.output.Format.TextMode;
017: import org.openlaszlo.xml.internal.XMLUtils;
018: import org.openlaszlo.xml.internal.MissingAttributeException;
019: import org.openlaszlo.xml.internal.Schema;
020: import org.openlaszlo.xml.internal.Schema.Type;
021: import org.openlaszlo.sc.ScriptCompiler;
022: import org.openlaszlo.utils.ChainedException;
023: import org.openlaszlo.css.CSSParser;
024:
025: /** Compiler for <code>class</code> class elements.
026: */
027: class ClassCompiler extends ViewCompiler {
028: /**
029: For a declaration of a class named "foobar"
030:
031: <pre><class name="foobar" extends="baz" with="mixin1,mixin2"></pre>
032:
033: We are going to call
034:
035: <pre>
036: LzInstantiateView(
037: {
038: name: 'class',
039: attrs: {
040: parent: "baz",
041: initobj: {
042: name: "foobar",
043: attrs: { extends: "baz",
044: with: "mixin1,mixin2"" }
045: }
046: }
047: }
048: </pre>
049: */
050: static final String DEFAULT_SUPERCLASS_NAME = "view";
051:
052: ClassCompiler(CompilationEnvironment env) {
053: super (env);
054: }
055:
056: /** Returns true iff this class applies to this element.
057: * @param element an element
058: * @return see doc
059: */
060: static boolean isElement(Element element) {
061: return element.getName().equals("class");
062: }
063:
064: /** Parse out an XML class definition, add the superclass and
065: * attribute types to the schema.
066: *
067: * <p>
068: * For each CLASS element, find child ATTRIBUTE tags, and add them
069: * to the schema.
070: *
071: * Also, any EVENT tags will be added to the schema as
072: * attributes of type "script".
073: */
074: void updateSchema(Element element, ViewSchema schema, Set visited) {
075: Element elt = element;
076:
077: String classname = elt.getAttributeValue("name");
078: String super class = elt.getAttributeValue("extends");
079:
080: if (classname == null
081: || (schema.enforceValidIdentifier && !ScriptCompiler
082: .isIdentifier(classname))) {
083: CompilationError cerr = new CompilationError(
084: /* (non-Javadoc)
085: * @i18n.test
086: * @org-mes="The classname attribute, \"name\" must be a valid identifier for a class definition"
087: */
088: org.openlaszlo.i18n.LaszloMessages.getMessage(
089: ClassCompiler.class.getName(), "051018-77"), elt);
090: throw (cerr);
091: }
092:
093: if (super class != null
094: && !ScriptCompiler.isIdentifier(super class)) {
095: mEnv.warn(
096: /* (non-Javadoc)
097: * @i18n.test
098: * @org-mes="The value for the 'extends' attribute on a class definition must be a valid identifier"
099: */
100: org.openlaszlo.i18n.LaszloMessages.getMessage(
101: ClassCompiler.class.getName(), "051018-89"), elt);
102: super class = null;
103: }
104: if (super class == null) {
105: // Default to LzView
106: super class = ClassCompiler.DEFAULT_SUPERCLASS_NAME;
107: }
108:
109: ClassModel super classinfo = schema.getClassModel(super class);
110:
111: if (super classinfo == null) {
112:
113: throw new CompilationError(
114: /* (non-Javadoc)
115: * @i18n.test
116: * @org-mes="undefined superclass " + p[0] + " for class " + p[1]
117: */
118: org.openlaszlo.i18n.LaszloMessages.getMessage(
119: ClassCompiler.class.getName(), "051018-106",
120: new Object[] { super class, classname }), elt);
121: }
122:
123: // Collect up the attribute defs, if any, of this class
124: List attributeDefs = new ArrayList();
125: Iterator iterator = element.getContent().iterator();
126:
127: while (iterator.hasNext()) {
128: Object o = iterator.next();
129: if (o instanceof Element) {
130: Element child = (Element) o;
131: if (child.getName().equals("method")) {
132: String attrName = child.getAttributeValue("name");
133: String attrEvent = child.getAttributeValue("event");
134: if (attrEvent == null) {
135: if (schema.enforceValidIdentifier) {
136: try {
137: attrName = requireIdentifierAttributeValue(
138: child, "name");
139: } catch (MissingAttributeException e) {
140: throw new CompilationError(
141: "'name' is a required attribute of <"
142: + child.getName()
143: + "> and must be a valid identifier",
144: child);
145: }
146: }
147: ViewSchema.Type attrType = ViewSchema.METHOD_TYPE;
148: AttributeSpec attrSpec = new AttributeSpec(
149: attrName, attrType, null, null, child);
150: attrSpec.override = child
151: .getAttributeValue("override");
152: attributeDefs.add(attrSpec);
153: }
154:
155: } else if (child.getName().equals("attribute")) {
156: // Is this an element named ATTRIBUTE which is a
157: // direct child of this CLASS or INTERFACE tag?
158:
159: String attrName = child.getAttributeValue("name");
160: if (schema.enforceValidIdentifier) {
161: try {
162: attrName = requireIdentifierAttributeValue(
163: child, "name");
164: } catch (MissingAttributeException e) {
165: throw new CompilationError(
166: /* (non-Javadoc)
167: * @i18n.test
168: * @org-mes="'name' is a required attribute of <" + p[0] + "> and must be a valid identifier"
169: */
170: org.openlaszlo.i18n.LaszloMessages
171: .getMessage(ClassCompiler.class
172: .getName(), "051018-131",
173: new Object[] { child
174: .getName() }),
175: child);
176: }
177: }
178:
179: String attrTypeName = child
180: .getAttributeValue("type");
181: String attrDefault = child
182: .getAttributeValue("default");
183: String attrSetter = child
184: .getAttributeValue("setter");
185: String attrRequired = child
186: .getAttributeValue("required");
187:
188: ViewSchema.Type attrType;
189: if (attrTypeName == null) {
190: // Check if this attribute exists in ancestor classes,
191: // and if so, default to that type.
192: attrType = super classinfo
193: .getAttributeType(attrName);
194: if (attrType == null) {
195: // The default attribute type
196: attrType = ViewSchema.EXPRESSION_TYPE;
197: }
198: } else {
199: attrType = schema.getTypeForName(attrTypeName);
200: }
201:
202: if (attrType == null) {
203: throw new CompilationError(
204: /* (non-Javadoc)
205: * @i18n.test
206: * @org-mes="In class " + p[0] + " type '" + p[1] + "', declared for attribute '" + p[2] + "' is not a known data type."
207: */
208: org.openlaszlo.i18n.LaszloMessages.getMessage(
209: ClassCompiler.class.getName(),
210: "051018-160", new Object[] { classname,
211: attrTypeName, attrName }),
212: element);
213: }
214:
215: AttributeSpec attrSpec = new AttributeSpec(
216: attrName, attrType, attrDefault,
217: attrSetter, child);
218: attrSpec.override = child
219: .getAttributeValue("override");
220: if (attrName.equals("text") && attrTypeName != null) {
221: if ("text".equals(attrTypeName))
222: attrSpec.contentType = attrSpec.TEXT_CONTENT;
223: else if ("html".equals(attrTypeName))
224: attrSpec.contentType = attrSpec.HTML_CONTENT;
225: }
226: attributeDefs.add(attrSpec);
227: } else if (child.getName().equals("event")) {
228: String attrName = child.getAttributeValue("name");
229: if (schema.enforceValidIdentifier) {
230: try {
231: attrName = requireIdentifierAttributeValue(
232: child, "name");
233: } catch (MissingAttributeException e) {
234: throw new CompilationError(
235: "'name' is a required attribute of <"
236: + child.getName()
237: + "> and must be a valid identifier",
238: child);
239: }
240: }
241:
242: ViewSchema.Type attrType = ViewSchema.EVENT_HANDLER_TYPE;
243: AttributeSpec attrSpec = new AttributeSpec(
244: attrName, attrType, null, null, child);
245: attributeDefs.add(attrSpec);
246: } else if (child.getName().equals("doc")) {
247: // Ignore documentation nodes
248: }
249: }
250: }
251:
252: // Add this class to the schema
253: schema.addElement(element, classname, super class,
254: attributeDefs, mEnv);
255: }
256:
257: public void compile(Element elt) {
258: String className = elt.getAttributeValue("name");
259: ViewSchema schema = mEnv.getSchema();
260: if (schema.enforceValidIdentifier) {
261: try {
262: className = requireIdentifierAttributeValue(elt, "name");
263: } catch (MissingAttributeException e) {
264: throw new CompilationError(
265: /* (non-Javadoc)
266: * @i18n.test
267: * @org-mes="'name' is a required attribute of <" + p[0] + "> and must be a valid identifier"
268: */
269: org.openlaszlo.i18n.LaszloMessages.getMessage(
270: ClassCompiler.class.getName(), "051018-193",
271: new Object[] { elt.getName() }), elt);
272: }
273: }
274:
275: ClassModel classModel = schema.getClassModel(className);
276:
277: String linedir = CompilerUtils.sourceLocationDirective(elt,
278: true);
279: ViewCompiler.preprocess(elt, mEnv);
280:
281: // We compile a class declaration just like a view, and then
282: // add attribute declarations and perhaps some other stuff that
283: // the runtime wants.
284: NodeModel model = NodeModel.elementAsModel(elt, schema, mEnv);
285: model = model.expandClassDefinitions();
286: model.removeAttribute("name");
287: classModel.setNodeModel(model);
288: Map viewMap = model.asMap();
289:
290: // Put in the class name, not "class"
291: viewMap.put("name", ScriptCompiler.quote(className));
292:
293: // TODO: [2007-01-29 ptw] Someday write out real Javascript classes
294: // String superclass = XMLUtils.getAttributeValue(elt, "extends", DEFAULT_SUPERCLASS_NAME);
295: // org.openlaszlo.sc.ScriptClass scriptClass =
296: // new org.openlaszlo.sc.ScriptClass(className, superclass, (Map)viewMap.get("attrs"));
297:
298: // Construct a Javascript statement from the initobj map
299: String initobj;
300: try {
301: java.io.Writer writer = new java.io.StringWriter();
302: ScriptCompiler.writeObject(viewMap, writer);
303: initobj = writer.toString();
304: } catch (java.io.IOException e) {
305: throw new ChainedException(e);
306: }
307: compileClass(elt, classModel, initobj);
308: }
309:
310: protected void compileClass(Element elt, ClassModel classModel,
311: String initobj) {
312: // Generate a call to queue instantiation
313: String extendsName = XMLUtils.getAttributeValue(elt, "extends",
314: DEFAULT_SUPERCLASS_NAME);
315: StringBuffer buffer = new StringBuffer();
316: buffer.append(VIEW_INSTANTIATION_FNAME
317: + "({name: 'class', attrs: " + "{parent: "
318: + ScriptCompiler.quote(extendsName) + ", initobj: "
319: + initobj + "}}" + ", "
320: + ((ElementWithLocationInfo) elt).model.totalSubnodes()
321: + ");\n");
322: if (!classModel.getInline()) {
323: ClassModel super classModel = classModel
324: .getSuperclassModel();
325: mEnv.compileScript(buffer.toString(), elt);
326: }
327: // TODO: [12-27-2002 ows] use the log4j API instead of this property
328: boolean tracexml = mEnv.getProperties().getProperty(
329: "trace.xml", "false") == "true";
330: if (tracexml) {
331: Logger mXMLLogger = Logger.getLogger("trace.xml");
332: mXMLLogger.info("compiling class definition:");
333: org.jdom.output.XMLOutputter outputter = new org.jdom.output.XMLOutputter();
334: outputter.getFormat().setTextMode(TextMode.NORMALIZE);
335: mXMLLogger.info(outputter.outputString(elt));
336: mXMLLogger
337: .info("compiled to:\n" + buffer.toString() + "\n");
338: }
339: }
340: }
|