001: /* *****************************************************************************
002: * ReprocessComments.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.io.*;
013: import java.util.regex.*;
014: import java.util.*;
015: import java.util.logging.*;
016: import javax.xml.parsers.DocumentBuilderFactory;
017: import org.w3c.dom.*;
018:
019: public class ReprocessComments {
020:
021: static private Logger logger = Logger
022: .getLogger("org.openlaszlo.js2doc");
023:
024: static void appendToAttribute(org.w3c.dom.Element node,
025: String attr, String value) {
026: String oldvalue = node.getAttribute(attr);
027: if (oldvalue == null || oldvalue.length() == 0)
028: node.setAttribute(attr, value.trim());
029: else
030: node.setAttribute(attr, oldvalue + " " + value.trim());
031: }
032:
033: static private abstract class CommentFieldFilter {
034: CommentFieldFilter nextFilter;
035:
036: public CommentFieldFilter(CommentFieldFilter nextFilter) {
037: this .nextFilter = nextFilter;
038: }
039:
040: public CommentFieldFilter() {
041: this (null);
042: }
043:
044: public boolean handle(org.w3c.dom.Element node,
045: String fieldName, String fieldValue) {
046: CommentFieldFilter f = this ;
047: while (f != null) {
048: if (f.handleField(node, fieldName, fieldValue) == true)
049: return true;
050: f = f.nextFilter;
051: }
052: return false;
053: }
054:
055: abstract protected boolean handleField(
056: org.w3c.dom.Element node, String fieldName,
057: String fieldValue);
058: };
059:
060: static private abstract class CommentKeywordFilter {
061: CommentKeywordFilter nextFilter;
062:
063: public CommentKeywordFilter(CommentKeywordFilter nextFilter) {
064: this .nextFilter = nextFilter;
065: }
066:
067: public CommentKeywordFilter() {
068: this (null);
069: }
070:
071: public boolean handle(org.w3c.dom.Element node,
072: String keywordName) {
073: CommentKeywordFilter f = this ;
074: while (f != null) {
075: if (f.handleKeyword(node, keywordName) == true)
076: return true;
077: f = f.nextFilter;
078: }
079: return false;
080: }
081:
082: abstract protected boolean handleKeyword(
083: org.w3c.dom.Element node, String keywordName);
084: };
085:
086: static private class TopicFieldFilter extends CommentFieldFilter {
087:
088: public boolean handleField(org.w3c.dom.Element node,
089: String fieldName, String fieldValue) {
090: if (fieldName.equals("topic")) {
091: node.setAttribute("topic", fieldValue.trim());
092: return true;
093: } else if (fieldName.equals("subtopic")) {
094: node.setAttribute("subtopic", fieldValue.trim());
095: return true;
096: }
097: return false;
098: }
099: };
100:
101: static private class AccessFieldFilter extends CommentFieldFilter {
102:
103: public AccessFieldFilter(CommentFieldFilter nextFilter) {
104: super (nextFilter);
105: }
106:
107: public AccessFieldFilter() {
108: super ();
109: }
110:
111: public boolean handleField(org.w3c.dom.Element node,
112: String fieldName, String fieldValue) {
113: fieldValue = fieldValue.trim();
114: if (fieldName.equals("access")) {
115: StringTokenizer keywordTokens = new StringTokenizer(
116: fieldValue);
117: String keyword = keywordTokens.nextToken();
118: if (keyword.equals("private")
119: || keyword.equals("public")
120: || keyword.equals("protected")) {
121: node.setAttribute("access", keyword);
122: return true;
123: } else {
124: logger.warning("Invalid @access keyword: '"
125: + keyword + "'");
126: }
127: } else if (fieldName.equals("runtimes")) {
128: node.setAttribute("runtimes", fieldValue);
129: return true;
130: }
131: return false;
132: }
133: };
134:
135: static private class TypeFieldFilter extends CommentFieldFilter {
136: public TypeFieldFilter(CommentFieldFilter nextFilter) {
137: super (nextFilter);
138: }
139:
140: public TypeFieldFilter() {
141: super ();
142: }
143:
144: public boolean handleField(org.w3c.dom.Element node,
145: String fieldName, String fieldValue) {
146: fieldValue = fieldValue.trim();
147: if (fieldName.equals("type")) {
148: StringTokenizer keywordTokens = new StringTokenizer(
149: fieldValue);
150: String type = keywordTokens.nextToken();
151: node.setAttribute("type", type);
152: return true;
153: }
154: return false;
155: }
156: };
157:
158: static private class ModifiersCommentKeywordFilter extends
159: CommentKeywordFilter {
160:
161: public ModifiersCommentKeywordFilter(
162: CommentKeywordFilter nextFilter) {
163: super (nextFilter);
164: }
165:
166: public ModifiersCommentKeywordFilter() {
167: super ();
168: }
169:
170: public boolean handleKeyword(org.w3c.dom.Element node,
171: String keywordName) {
172: // TODO [jgrandy 11/23/06] eventually this use of @keywords should be deprecated
173: if (keywordName.equals("private")
174: || keywordName.equals("public")
175: || keywordName.equals("protected")) {
176:
177: node.setAttribute("access", keywordName);
178: return true;
179:
180: } else if (keywordName.equals("final")
181: || keywordName.equals("virtual")
182: || keywordName.equals("deprecated")
183: || keywordName.equals("override")
184: || keywordName.equals("abstract")
185: || keywordName.equals("const")) {
186:
187: appendToAttribute(node, "modifiers", keywordName);
188: return true;
189:
190: } else if (keywordName.equals("read-only")
191: || keywordName.equals("readonly")) {
192: appendToAttribute(node, "modifiers", "read-only");
193: return true;
194: }
195: return false;
196: }
197: };
198:
199: static private class ParamFieldFilter extends CommentFieldFilter {
200:
201: // TODO [jgrandy 12/14/2006] use Java5's Scanner instead of regexp here
202:
203: static private final Pattern paramPattern = Pattern
204: .compile("^\\s*(?:(\\w+)\\s+)?(\\w*)(?::(.*))?$",
205: Pattern.DOTALL);
206: static private final Pattern returnPattern = Pattern.compile(
207: "^\\s*(\\w+)(?::(.*))?$", Pattern.DOTALL);
208:
209: public ParamFieldFilter(CommentFieldFilter nextFilter) {
210: super (nextFilter);
211: }
212:
213: public boolean handleField(org.w3c.dom.Element node,
214: String fieldName, String fieldValue) {
215: boolean handled = false;
216:
217: if (fieldName.equals("param")) {
218: Matcher valueMatcher = paramPattern.matcher(fieldValue);
219: boolean found = valueMatcher.find();
220: if (found) {
221: String paramType = valueMatcher.group(1), paramName = valueMatcher
222: .group(2), paramDesc = valueMatcher
223: .group(3);
224:
225: // now find the appropriate parameter node in the DOM node
226: String tagName = node.getTagName();
227: org.w3c.dom.Element functionNode = (tagName
228: .equals("function")) ? node
229: : (org.w3c.dom.Element) firstChildNodeWithName(
230: node, "function");
231:
232: if (functionNode == null) {
233: logger
234: .warning("Couldn't find function node when placing parameter info "
235: + node.getTagName());
236: } else {
237: org.w3c.dom.Element childElt = JS2DocUtils
238: .findFirstChildElementWithAttribute(
239: functionNode, "parameter",
240: "name", paramName);
241: if (childElt == null) {
242: logger
243: .warning("Couldn't find parameter named "
244: + paramName);
245: } else {
246: if (paramType != null) {
247: // TODO [jgrandy 12/1/2006] check if @type already set
248: childElt
249: .setAttribute("type", paramType);
250: }
251:
252: paramDesc = (paramDesc != null) ? paramDesc
253: .trim() : null;
254: if (paramDesc != null
255: && paramDesc.length() > 0) {
256: org.w3c.dom.Element docNode = (org.w3c.dom.Element) JS2DocUtils
257: .firstChildNodeWithName(
258: childElt, "doc");
259: if (docNode == null) {
260: docNode = childElt
261: .getOwnerDocument()
262: .createElement("doc");
263: childElt.appendChild(docNode);
264: }
265: org.w3c.dom.Element textNode = childElt
266: .getOwnerDocument()
267: .createElement("text");
268: docNode.appendChild(textNode);
269: JS2DocUtils.setXMLContent(textNode,
270: paramDesc);
271: }
272:
273: handled = true;
274: }
275: }
276: }
277: } else if (fieldName.equals("return")
278: || fieldName.equals("returns")) {
279: Matcher valueMatcher = returnPattern
280: .matcher(fieldValue);
281: boolean found = valueMatcher.find();
282: if (found) {
283: String paramType = valueMatcher.group(1), paramDesc = valueMatcher
284: .group(2);
285:
286: org.w3c.dom.Element functionNode = (node
287: .getTagName().equals("function")) ? node
288: : (org.w3c.dom.Element) firstChildNodeWithName(
289: node, "function");
290:
291: if (functionNode == null) {
292: logger
293: .warning("Couldn't find function node when placing returns info");
294: } else {
295: // now find/create the appropriate returns node in the DOM node
296: org.w3c.dom.Element returnsNode = (org.w3c.dom.Element) JS2DocUtils
297: .firstChildNodeWithName(functionNode,
298: "returns");
299:
300: if (returnsNode == null) {
301: returnsNode = functionNode
302: .getOwnerDocument().createElement(
303: "returns");
304: functionNode.appendChild(returnsNode);
305: }
306:
307: if (paramType != null) {
308: // TODO [jgrandy 12/1/2006] check if @type already set
309: returnsNode.setAttribute("type", paramType);
310: }
311:
312: paramDesc = (paramDesc != null) ? paramDesc
313: .trim() : null;
314: if (paramDesc != null && paramDesc.length() > 0) {
315: org.w3c.dom.Element docNode = (org.w3c.dom.Element) JS2DocUtils
316: .firstChildNodeWithName(
317: returnsNode, "doc");
318: if (docNode == null) {
319: docNode = returnsNode
320: .getOwnerDocument()
321: .createElement("doc");
322: returnsNode.appendChild(docNode);
323: }
324: org.w3c.dom.Element textNode = returnsNode
325: .getOwnerDocument().createElement(
326: "text");
327: docNode.appendChild(textNode);
328: JS2DocUtils.setXMLContent(textNode,
329: paramDesc);
330: }
331: handled = true;
332: }
333: }
334: }
335: return handled;
336: }
337: };
338:
339: static private class InitializerFieldFilter extends
340: CommentFieldFilter {
341:
342: // TODO [jgrandy 12/14/2006] use Java5's Scanner instead of regexp here
343:
344: static private final Pattern initializerPattern = Pattern
345: .compile(
346: "^\\s*(?:(\\w+)\\s+)?(?:(\\w+)\\s+)?(\\w*)(?::(.*))?$",
347: Pattern.DOTALL);
348:
349: public InitializerFieldFilter(CommentFieldFilter nextFilter) {
350: super (nextFilter);
351: }
352:
353: public boolean handleField(org.w3c.dom.Element node,
354: String fieldName, String fieldValue) {
355: boolean handled = false;
356:
357: if (fieldName.equals("initarg")) {
358: Matcher valueMatcher = initializerPattern
359: .matcher(fieldValue);
360: boolean found = valueMatcher.find();
361: if (found) {
362:
363: String initializerMod1 = valueMatcher.group(1), initializerMod2 = valueMatcher
364: .group(2), initializerName = valueMatcher
365: .group(3), initializerDesc = valueMatcher
366: .group(4);
367:
368: org.w3c.dom.Element classNode = (node.getTagName()
369: .equals("class")) ? node
370: : (org.w3c.dom.Element) firstChildNodeWithName(
371: node, "class");
372:
373: if (classNode == null) {
374: logger
375: .warning("Couldn't find class node when placing initarg info");
376: } else {
377:
378: org.w3c.dom.Element initializerNode = classNode
379: .getOwnerDocument().createElement(
380: "initarg");
381: classNode.appendChild(initializerNode);
382:
383: initializerNode.setAttribute("name",
384: initializerName);
385:
386: initializerNode.setAttribute("id", JS2DocUtils
387: .derivePropertyID(classNode,
388: initializerName, null));
389:
390: if (initializerMod2 != null) {
391: String modifier = initializerMod1, type = initializerMod2;
392: if (modifier != null) {
393: if (modifier.equals("public")
394: || modifier.equals("private")
395: || modifier.equals("protected")) {
396: initializerNode.setAttribute(
397: "access", modifier);
398: } else
399: appendToAttribute(initializerNode,
400: "modifiers",
401: initializerMod1);
402: }
403: initializerNode.setAttribute("type",
404: initializerMod2);
405: } else if (initializerMod1 != null) {
406: String type = initializerMod1;
407: initializerNode.setAttribute("type",
408: initializerMod1);
409: }
410:
411: initializerDesc = (initializerDesc != null) ? initializerDesc
412: .trim()
413: : null;
414: if (initializerDesc != null
415: && initializerDesc.length() > 0) {
416: org.w3c.dom.Element docNode = (org.w3c.dom.Element) JS2DocUtils
417: .firstChildNodeWithName(
418: initializerNode, "doc");
419: if (docNode == null) {
420: docNode = initializerNode
421: .getOwnerDocument()
422: .createElement("doc");
423: initializerNode.appendChild(docNode);
424: }
425: org.w3c.dom.Element textNode = docNode
426: .getOwnerDocument().createElement(
427: "text");
428: docNode.appendChild(textNode);
429: JS2DocUtils.setXMLContent(textNode,
430: initializerDesc);
431: }
432:
433: handled = true;
434: }
435: }
436: }
437: return handled;
438: }
439: };
440:
441: static private CommentFieldFilter toplevelDeclFieldFilter = new TypeFieldFilter(
442: new AccessFieldFilter(new TopicFieldFilter()));
443: static private CommentKeywordFilter toplevelDeclKeywordFilter = new ModifiersCommentKeywordFilter();
444:
445: static private CommentFieldFilter functionCommentFieldFilter = new ParamFieldFilter(
446: new AccessFieldFilter());
447:
448: static private CommentFieldFilter classFieldFilter = new InitializerFieldFilter(
449: new AccessFieldFilter(new TopicFieldFilter()));
450:
451: static private CommentFieldFilter memberCommentFieldFilter = new TypeFieldFilter(
452: new AccessFieldFilter());
453: static private CommentKeywordFilter memberCommentKeywordFilter = new ModifiersCommentKeywordFilter();
454:
455: static private CommentFieldFilter classMethodCommentFieldFilter = new ParamFieldFilter(
456: new AccessFieldFilter());
457:
458: static private Node firstChildNodeWithName(Node node, String name) {
459: Node foundNode = null;
460: NodeList childNodes = node.getChildNodes();
461: final int n = childNodes.getLength();
462: for (int i = 0; i < n; i++) {
463: Node childNode = childNodes.item(i);
464: if (childNode.getNodeName().equals(name)) {
465: foundNode = childNode;
466: break;
467: }
468: }
469: return foundNode;
470: }
471:
472: static private final Pattern contentsPattern = Pattern
473: .compile(
474: "^<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?><text>(.*)</text>$",
475: Pattern.DOTALL);
476:
477: static private void hoistDocTags(Node node, Element docNode,
478: CommentFieldFilter fieldFilter,
479: CommentKeywordFilter keywordFilter) {
480: NodeList childNodes = docNode.getChildNodes();
481: int n = childNodes.getLength();
482: for (int i = 0; i < n; i++) {
483: org.w3c.dom.Node childNode = childNodes.item(i);
484: if (childNode.getNodeName().equals("tag")) {
485: Element childElt = (Element) childNode;
486: String fieldName = childElt.getAttribute("name");
487:
488: Element fieldText = (Element) firstChildNodeWithName(
489: childNode, "text");
490:
491: // We can't use getTextContents here because fieldText is a tree
492: // fragment with XML substructure. So reconstitute as an XML
493: // string and let the fieldFilter reparse it.
494: String fieldValue = JS2DocUtils.xmlToString(fieldText);
495:
496: // xmlToString returns as well-formed XML document string, so
497: // strip back to be just the fragment contents.
498: fieldValue = contentsPattern.matcher(fieldValue)
499: .replaceAll("$1");
500:
501: final boolean handled = fieldFilter.handle(
502: (Element) node, fieldName, fieldValue);
503:
504: if (handled == true) {
505: docNode.removeChild(childNode);
506: n--;
507: i--;
508: }
509: }
510: }
511:
512: String keywords = docNode.getAttribute("keywords");
513: if (keywords == null)
514: keywords = docNode.getAttribute("keyword");
515: if (keywords != null) {
516: docNode.removeAttribute("keywords");
517: StringTokenizer keywordTokens = new StringTokenizer(
518: keywords);
519: while (keywordTokens.hasMoreTokens()) {
520: String keyword = keywordTokens.nextToken();
521: final boolean handled = keywordFilter.handle(
522: (Element) node, keyword);
523: if (handled == false)
524: appendToAttribute(docNode, "keywords", keyword);
525: }
526: }
527:
528: if (docNode.hasChildNodes() == false
529: && docNode.hasAttributes() == false) {
530: node.removeChild(docNode);
531: }
532: }
533:
534: static private void reprocessElement(Node node,
535: CommentFieldFilter fieldFilter,
536: CommentKeywordFilter keywordFilter) {
537:
538: org.w3c.dom.Element doc = (org.w3c.dom.Element) JS2DocUtils
539: .firstChildNodeWithName(node, "doc");
540: if (doc != null) {
541: hoistDocTags(node, doc, fieldFilter, keywordFilter);
542: }
543:
544: org.w3c.dom.Element commentNode = (org.w3c.dom.Element) JS2DocUtils
545: .firstChildNodeWithName(node, "comment");
546: if (commentNode != null) {
547: String commentText = commentNode.getTextContent();
548: node.removeChild(commentNode);
549: Comment comment = Comment.extractJS2DocComment(commentText);
550: org.w3c.dom.Element docNode = comment.appendAsXML(node);
551: hoistDocTags(node, docNode, fieldFilter, keywordFilter);
552: }
553: }
554:
555: static public void reprocessChildElements(Node node,
556: boolean isTopLevel) {
557: NodeList childNodes = node.getChildNodes();
558: final int n = childNodes.getLength();
559: for (int i = 0; i < n; i++) {
560: Node childNode = childNodes.item(i);
561: reprocess(childNode, isTopLevel);
562: }
563: }
564:
565: static public void reprocess(Node node, boolean isTopLevel) {
566:
567: String nodeName = node.getNodeName();
568:
569: if (nodeName.equals("js2doc")) {
570: if (isTopLevel == false)
571: logger.warning("unexpected js2doc inside declaration");
572: reprocessChildElements(node, true);
573: } else if (nodeName.equals("unit")) {
574: if (isTopLevel == false)
575: logger.warning("unexpected unit inside declaration");
576: reprocessElement(node, toplevelDeclFieldFilter,
577: toplevelDeclKeywordFilter);
578: } else if (nodeName.equals("ivars")) {
579: if (isTopLevel == true)
580: logger.warning("unexpected ivars outside class decl");
581: reprocessChildElements(node, false);
582: } else if (nodeName.equals("property")) {
583: // todo: maybe slightly cleaner to iterate over all child nodes and
584: // switch on each one?
585: Node child = null;
586: if ((child = firstChildNodeWithName(node, "function")) != null) {
587: reprocessElement(node, functionCommentFieldFilter,
588: isTopLevel ? toplevelDeclKeywordFilter
589: : memberCommentKeywordFilter);
590: } else if ((child = firstChildNodeWithName(node, "class")) != null) {
591: reprocessElement(node, classFieldFilter,
592: isTopLevel ? toplevelDeclKeywordFilter
593: : memberCommentKeywordFilter);
594: } else if ((child = firstChildNodeWithName(node, "object")) != null) {
595: reprocessElement(node,
596: isTopLevel ? toplevelDeclFieldFilter
597: : memberCommentFieldFilter,
598: isTopLevel ? toplevelDeclKeywordFilter
599: : memberCommentKeywordFilter);
600: } else if ((child = firstChildNodeWithName(node, "initarg")) != null) {
601: reprocessElement(node,
602: isTopLevel ? toplevelDeclFieldFilter
603: : memberCommentFieldFilter,
604: isTopLevel ? toplevelDeclKeywordFilter
605: : memberCommentKeywordFilter);
606: } else {
607: // todo: what case is this handling?
608: reprocessElement(node,
609: isTopLevel ? toplevelDeclFieldFilter
610: : memberCommentFieldFilter,
611: isTopLevel ? toplevelDeclKeywordFilter
612: : memberCommentKeywordFilter);
613: }
614: if (child != null)
615: reprocessChildElements(child, false);
616: }
617: }
618:
619: static public Document reprocess(String contents) {
620:
621: org.w3c.dom.Document doc = null;
622:
623: try {
624:
625: DocumentBuilderFactory factory = DocumentBuilderFactory
626: .newInstance();
627: factory.setValidating(false);
628: doc = factory.newDocumentBuilder().parse(
629: new org.xml.sax.InputSource(new StringReader(
630: contents)));
631:
632: reprocessChildElements(doc, true);
633:
634: } catch (java.io.IOException e) {
635: logger.warning("Could not read string");
636: e.printStackTrace();
637: } catch (javax.xml.parsers.ParserConfigurationException e) {
638: logger.warning("Could not parse xml input");
639: e.printStackTrace();
640: } catch (org.xml.sax.SAXException e) {
641: logger.warning("Could not parse xml input");
642: e.printStackTrace();
643: }
644:
645: return doc;
646: }
647:
648: }
|