001: /* *****************************************************************************
002: * PropertyUtils.java
003: * ****************************************************************************/
004:
005: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
006: * Copyright 2007 Laszlo Systems, Inc. All Rights Reserved. *
007: * Use is subject to license terms. *
008: * J_LZ_COPYRIGHT_END *********************************************************/
009:
010: package org.openlaszlo.js2doc;
011:
012: import java.util.*;
013: import java.util.logging.*;
014: import org.openlaszlo.sc.parser.*;
015: import org.openlaszlo.js2doc.JS2DocUtils.InternalError;
016: import org.w3c.dom.*;
017:
018: public class PropertyReference {
019:
020: static private Logger logger = Logger
021: .getLogger("org.openlaszlo.js2doc");
022:
023: org.w3c.dom.Element propertyOwner;
024: String propertyName;
025: ConditionalState state;
026:
027: private org.w3c.dom.Element cachedProperty;
028: private org.w3c.dom.Element cachedValue;
029:
030: public PropertyReference() {
031: this .propertyOwner = null;
032: this .propertyName = null;
033: this .state = null;
034: this .cachedProperty = null;
035: this .cachedValue = null;
036: }
037:
038: public PropertyReference(PropertyReference orig) {
039: this .propertyOwner = orig.propertyOwner;
040: this .propertyName = orig.propertyName;
041: this .state = orig.state;
042: this .cachedProperty = orig.cachedProperty;
043: this .cachedValue = orig.cachedValue;
044: }
045:
046: public PropertyReference(org.w3c.dom.Element owner,
047: String propertyName, ConditionalState state) {
048: this .propertyOwner = owner;
049: this .propertyName = propertyName;
050: if (this .propertyName == null) {
051: logger.warning("explicit propertyName has null name");
052: }
053: this .state = state;
054: this .cachedProperty = null;
055: this .cachedValue = null;
056: }
057:
058: public PropertyReference(org.w3c.dom.Element owner,
059: SimpleNode lvalDesc, ConditionalState state) {
060: this ();
061: resolvePropertyReference(owner, lvalDesc, state);
062: }
063:
064: public boolean isValid() {
065: return (this .propertyOwner != null && this .propertyName != null);
066: }
067:
068: public boolean hasOwner() {
069: return (this .propertyOwner != null);
070: }
071:
072: public boolean hasProperty() {
073: resolveProperty();
074: return (this .cachedProperty != null);
075: }
076:
077: public org.w3c.dom.Element getProperty() {
078: resolveProperty();
079: return this .cachedProperty;
080: }
081:
082: public org.w3c.dom.Element ensureProperty() {
083: resolveProperty();
084: if (this .isValid() == false)
085: return null;
086: if (this .cachedProperty == null) {
087: this .createProperty();
088: }
089: return this .cachedProperty;
090: }
091:
092: /** Create new property node, deleting any previous property node if necessary */
093: public org.w3c.dom.Element redefineProperty(String comment) {
094: resolveProperty();
095: if (this .isValid() == false)
096: return null;
097: if (this .cachedProperty != null) {
098: this .propertyOwner.removeChild(this .cachedProperty);
099: // clean member in case createElement throws (unlikely, I know...)
100: this .cachedProperty = null;
101: }
102:
103: this .cachedProperty = propertyOwner.getOwnerDocument()
104: .createElement("property");
105: this .propertyOwner.appendChild(this .cachedProperty);
106:
107: this .cachedProperty.setAttribute("name", this .propertyName);
108:
109: this .setPropertyMetadata(comment);
110:
111: return this .cachedProperty;
112: }
113:
114: public boolean hasValue() {
115: resolveValue();
116: return (this .cachedValue != null);
117: }
118:
119: public org.w3c.dom.Element getValue() {
120: resolveValue();
121: return this .cachedValue;
122: }
123:
124: public org.w3c.dom.Element redefineValue(SimpleNode valueDesc) {
125: this .resolveProperty();
126: if (this .cachedProperty == null) {
127: logger
128: .warning("cannot set value -- no property to bind to");
129: return null;
130: } else if (this .cachedValue != null) {
131: this .cachedProperty.removeChild(this .cachedValue);
132: this .cachedValue = null;
133: }
134:
135: this .setPropertyValue(valueDesc);
136:
137: return this .cachedValue;
138: }
139:
140: public String derivePropertyID() {
141: if (this .isValid() == false) {
142: logger.warning("propertyName: " + this .propertyName);
143: throw new InternalError(
144: "Can't derive ID of invalid property", null);
145: }
146: return JS2DocUtils.derivePropertyID(this .propertyOwner,
147: this .propertyName, this .state);
148: }
149:
150: private static void addFunctionParameter(SimpleNode parseNode,
151: org.w3c.dom.Element docNode) {
152: org.w3c.dom.Element paramNode = docNode.getOwnerDocument()
153: .createElement("parameter");
154: docNode.appendChild(paramNode);
155:
156: if (!(parseNode instanceof ASTIdentifier))
157: throw new InternalError(
158: "FunctionParameter is not an ASTIdentifier",
159: parseNode);
160:
161: ASTIdentifier param = (ASTIdentifier) parseNode;
162: paramNode.setAttribute("name", param.getName());
163: }
164:
165: protected org.w3c.dom.Element setClassValue(SimpleNode parseNode) {
166: resolveProperty();
167: org.w3c.dom.Element classNode = this .cachedProperty
168: .getOwnerDocument().createElement("class");
169: this .cachedProperty.appendChild(classNode);
170:
171: SimpleNode[] children = parseNode.getChildren();
172: Iterator iter = Arrays.asList(children).iterator();
173:
174: SimpleNode temp = (SimpleNode) iter.next();
175: // assert temp instanceof ASTIdentifier && ((ASIdentifier) temp).getName() == "class" or "trait"
176:
177: ASTIdentifier nameNode = (ASTIdentifier) iter.next();
178: String name = nameNode.getName();
179:
180: SimpleNode extendsNode = (SimpleNode) iter.next();
181: if (extendsNode instanceof ASTIdentifier) {
182: ASTIdentifier extendsNameNode = (ASTIdentifier) extendsNode;
183: String extendsName = extendsNameNode.getName();
184: PropertyReference extendsRef = new PropertyReference(
185: this .propertyOwner, extendsName, this .state);
186: if (extendsRef.hasProperty() == false) {
187: extendsRef = new PropertyReference(this .propertyOwner,
188: extendsName, null);
189: }
190: String extendsID = extendsRef.derivePropertyID();
191: classNode.setAttribute("extends", extendsID);
192: } else {
193: if (!(extendsNode instanceof ASTEmptyExpression))
194: throw new InternalError(
195: "unexpected node type parsing extends",
196: extendsNode);
197: }
198:
199: SimpleNode inheritsNode = (SimpleNode) iter.next();
200: if (inheritsNode instanceof ASTTraitsList) {
201: logger.fine("Processing traits list is NYI");
202: } else {
203: if (!(inheritsNode instanceof ASTEmptyExpression))
204: throw new InternalError(
205: "unexpected node type parsing inherits",
206: inheritsNode);
207: }
208:
209: this .cachedValue = classNode;
210: return classNode;
211: }
212:
213: private void setPropertyValueFromNewExpression(
214: ASTIdentifier classNameNode) {
215: String className = classNameNode.getName();
216: logger.fine("setting property to new " + className);
217: if (className.equals("Object")) {
218: org.w3c.dom.Element objectNode = this .cachedProperty
219: .getOwnerDocument().createElement("object");
220: this .cachedProperty.appendChild(objectNode);
221: } else if (className.equals("Function")) {
222: org.w3c.dom.Element objectNode = this .cachedProperty
223: .getOwnerDocument().createElement("function");
224: this .cachedProperty.appendChild(objectNode);
225: } else {
226: org.w3c.dom.Element objectNode = this .cachedProperty
227: .getOwnerDocument().createElement("object");
228: this .cachedProperty.appendChild(objectNode);
229: objectNode.setAttribute("type", className);
230: }
231: }
232:
233: private void setPropertyValue(SimpleNode valueNode) {
234: resolveProperty();
235: if (valueNode instanceof ASTNewExpression) {
236: JS2DocUtils.checkChildrenLowerBounds(valueNode, 1, 1,
237: "visitVariableDeclaration");
238: SimpleNode classExprNode = valueNode.getChildren()[0];
239: if (classExprNode instanceof ASTIdentifier) {
240: setPropertyValueFromNewExpression((ASTIdentifier) classExprNode);
241:
242: } else if (classExprNode instanceof ASTCallExpression) {
243: setPropertyValueFromNewExpression((ASTIdentifier) classExprNode
244: .getChildren()[0]);
245: }
246: } else if (valueNode instanceof ASTFunctionExpression
247: || valueNode instanceof ASTFunctionDeclaration) {
248: JS2DocUtils.checkChildrenLowerBounds(valueNode, 3, 3,
249: "visitVariableDeclaration");
250:
251: org.w3c.dom.Element fnNode = this .cachedProperty
252: .getOwnerDocument().createElement("function");
253: this .cachedProperty.appendChild(fnNode);
254:
255: SimpleNode[] children = valueNode.getChildren();
256: boolean functionHasName = (children[0] instanceof ASTIdentifier);
257: ASTFormalParameterList paramList = (ASTFormalParameterList) children[functionHasName ? 1
258: : 0];
259:
260: SimpleNode[] params = paramList.getChildren();
261: for (int i = 0; i < params.length; i++) {
262: addFunctionParameter(params[i], fnNode);
263: }
264: } else if (valueNode instanceof ASTClassDefinition) {
265: this .setClassValue(valueNode);
266: } else if (valueNode instanceof ASTObjectLiteral) {
267: resolveProperty();
268: org.w3c.dom.Element objectNode = this .cachedProperty
269: .getOwnerDocument().createElement("object");
270: this .cachedProperty.appendChild(objectNode);
271: this .cachedValue = objectNode;
272: } else if (valueNode instanceof ASTLiteral) {
273: Object value = ((ASTLiteral) valueNode).getValue();
274: String valueString = (value == null) ? "null" : value
275: .toString();
276: this .cachedProperty.setAttribute("value", valueString);
277: } else if (valueNode instanceof ASTIdentifier
278: || valueNode instanceof ASTThisReference
279: || valueNode instanceof ASTPropertyIdentifierReference
280: || valueNode instanceof ASTArrayLiteral
281: || valueNode instanceof ASTConditionalExpression
282: || valueNode instanceof ASTUnaryExpression
283: || valueNode instanceof ASTBinaryExpressionSequence
284: || valueNode instanceof ASTOrExpressionSequence
285: || valueNode instanceof ASTCallExpression
286: || valueNode instanceof ASTAndExpressionSequence) {
287: logger.fine("NYI " + valueNode.getClass().getName()
288: + " initializer");
289: } else {
290: logger.warning("ignoring unknown initializer type: "
291: + valueNode.getClass().getName());
292: JS2DocUtils.debugPrintNode(valueNode);
293: }
294: }
295:
296: private void setPropertyMetadata(String comment) {
297: this .resolveProperty();
298: if (this .cachedProperty == null)
299: return;
300:
301: if (comment != null) {
302: Comment parsedComment = Comment
303: .extractLastJS2DocFromCommentSequence(comment);
304: parsedComment.appendAsXML(this .cachedProperty);
305: }
306:
307: if (this .state != null)
308: JS2DocUtils.describeConditionalState(this .state,
309: this .cachedProperty);
310:
311: String propertyID = this .derivePropertyID();
312: this .cachedProperty.setAttribute("id", propertyID);
313:
314: }
315:
316: private void resolvePropertyReference(org.w3c.dom.Element owner,
317: SimpleNode lvalDesc, ConditionalState state) {
318:
319: if (lvalDesc instanceof ASTIdentifier) {
320:
321: this .propertyOwner = owner;
322: this .propertyName = ((ASTIdentifier) lvalDesc).getName();
323: this .state = state;
324:
325: if (this .propertyName == null) {
326: logger
327: .warning("lval identifier propertyName has null name");
328: }
329:
330: } else if (lvalDesc instanceof ASTPropertyIdentifierReference) {
331:
332: JS2DocUtils.checkChildrenLowerBounds(lvalDesc, 2, 2,
333: "findLVal");
334: SimpleNode[] children = lvalDesc.getChildren();
335:
336: PropertyReference ownerLVal = new PropertyReference(owner,
337: children[0], state);
338:
339: if (ownerLVal.hasProperty() == true) {
340:
341: this .propertyOwner = ownerLVal.getValue();
342:
343: if (this .propertyOwner == null) {
344: logger
345: .warning("lvalue has no binding; can't attach property "
346: + propertyName);
347: }
348:
349: SimpleNode propertyNameNode = children[1];
350: if (!(propertyNameNode instanceof ASTIdentifier))
351: throw new InternalError(
352: "propertyNameNode is not an ASTIdentifier",
353: lvalDesc);
354:
355: this .propertyName = ((ASTIdentifier) propertyNameNode)
356: .getName();
357:
358: if (this .propertyName == null) {
359: logger
360: .warning("lval identifier propertyName has null name");
361: }
362:
363: this .state = null;
364:
365: } else {
366:
367: org.w3c.dom.Node parent = owner.getParentNode();
368: if (parent != null
369: && parent instanceof org.w3c.dom.Element)
370: resolvePropertyReference(
371: (org.w3c.dom.Element) parent, lvalDesc,
372: state);
373: }
374:
375: } else if (lvalDesc instanceof ASTPropertyValueReference) {
376: logger.fine("NYI property value reference");
377: } else {
378: JS2DocUtils.debugPrintNode(lvalDesc);
379: throw new InternalError("Unhandled lval parser type",
380: lvalDesc);
381: }
382: }
383:
384: private void resolveProperty() {
385: if (this .cachedProperty == null) {
386: if (this .isValid()) {
387: String id = derivePropertyID();
388: this .cachedProperty = JS2DocUtils
389: .findFirstChildElementWithAttribute(
390: this .propertyOwner, "property", "id",
391: id);
392:
393: if (this .cachedProperty == null) {
394: String name = this .propertyName;
395: if (name.equals("prototype")
396: || name.equals("setters")) {
397: this .createProperty();
398: }
399: if (this .propertyOwner.getNodeName().equals(
400: "js2doc")) {
401: if (name.equals("Object")
402: || name.equals("Function")
403: || name.equals("Array")
404: || name.equals("String")
405: || name.equals("Boolean")
406: || name.equals("Number")
407: || name.equals("Date")) {
408: this .createProperty();
409: this .cachedProperty.setAttribute("topic",
410: "JavaScript");
411: this .cachedProperty.setAttribute(
412: "subtopic", "Intrinsic Classes");
413: }
414:
415: if (name.equals("window")
416: || name.equals("document")
417: || name.equals("_root")) {
418: this .createProperty();
419: this .cachedProperty.setAttribute("topic",
420: "JavaScript");
421: this .cachedProperty.setAttribute(
422: "subtopic", "DOM");
423: }
424: }
425: }
426: }
427: }
428: }
429:
430: private void createProperty() {
431: this .cachedProperty = propertyOwner.getOwnerDocument()
432: .createElement("property");
433: this .propertyOwner.appendChild(this .cachedProperty);
434: this .cachedProperty.setAttribute("name", this .propertyName);
435: this .setPropertyMetadata(null);
436: }
437:
438: private void resolveValue() {
439: resolveProperty();
440: if (this .cachedValue == null) {
441: if (this .cachedProperty != null) {
442: org.w3c.dom.Element foundElt = null;
443: org.w3c.dom.NodeList childNodes = this .cachedProperty
444: .getChildNodes();
445: final int n = childNodes.getLength();
446: for (int i = 0; i < n; i++) {
447: org.w3c.dom.Node childNode = childNodes.item(i);
448: if (childNode instanceof org.w3c.dom.Element) {
449: String nodeName = childNode.getNodeName();
450: if (nodeName.equals("object")
451: || nodeName.equals("function")
452: || nodeName.equals("class")
453: || nodeName.equals("event")) {
454: foundElt = (org.w3c.dom.Element) childNode;
455: break;
456: }
457: }
458: }
459: if (foundElt == null) {
460: foundElt = this .cachedProperty.getOwnerDocument()
461: .createElement("object");
462: this.cachedProperty.appendChild(foundElt);
463: }
464: this.cachedValue = foundElt;
465: }
466: }
467: }
468:
469: }
|