/* Copyright (c) 2013-2017, Jesper Öqvist <jesper.oqvist@cs.lth.se> * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.extendj.ragdoc; import java.util.Collection; import java.util.LinkedList; public class JavaDocParser extends Object { static final InlineTagExpander expander = new InlineTagExpander() { @Override public String expand(String tag, String text) { if (tag.equals("code")) { return "<code>" + text + "</code>"; } else if (tag.equals("link")) { return "<a href=\"" + classDocURL(text) + "\">" + text + "</a>"; } else { return text; } } private String classDocURL(String klass) { String result = klass.replaceAll("\\.", "/"); return "#" + result; } }; private int i; private char[] doc; private Collection<DocTag> tags = new LinkedList<DocTag>(); /** * @return normalized documentation comment. All line endings replaced by \\n. */ public String parse(String comments) { if (comments == null) { return ""; } int start = 0; int end = -2; while (true) { int newStart = comments.indexOf("/**", end + 2); if (newStart == -1) { break; } int newEnd = comments.indexOf("*/", newStart + 3); if (newEnd == -1) { break; } start = newStart; end = newEnd; } if (end <= start + 3) { return ""; } doc = comments.substring(start + 3, end).toCharArray(); StringBuilder text = new StringBuilder(doc.length); i = 0; boolean first = true; while (i < doc.length) { if (skipNewline()) { skipWhitespace(); if (skipAsterisk()) { if (!first) { text.append('\n'); } first = false; skipWhitespace(); } } else { text.append(doc[i++]); } } String expanded = expandInlineTags(text.toString()); String content = extractContent(expanded); return content; } public Collection<DocTag> getTags() { return tags; } private static boolean isKnownTag(String tag) { return /* JastAdd tags: */ tag.equals("declaredat") || tag.equals("production") || tag.equals("astdecl") || tag.equals("ast") || tag.equals("aspect") || tag.equals("apilevel") || tag.equals("attribute") || tag.equals("relation") || /* JavaDoc tags: * (http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#javadoctags) */ tag.equals("author") || tag.equals("deprecated") || tag.equals("exception") || tag.equals("param") || tag.equals("return") || tag.equals("see") || tag.equals("serial") || tag.equals("serialData") || tag.equals("serialField") || tag.equals("since") || tag.equals("throws") || tag.equals("version"); } private static boolean ignoreTag(String tag) { return tag.equals("production"); } /** * Separate content and doc tags. */ private String extractContent(String javadoc) { this.doc = javadoc.toCharArray(); StringBuilder content = new StringBuilder(doc.length); i = 0; while (i < doc.length) { int rewind = i; DocTag tag = parseTag(); if (tag != null && isKnownTag(tag.tag)) { if (!ignoreTag(tag.tag)) { tags.add(tag); } } else { if (tag != null) { i = rewind; } String html = parseHtmlTag(); if (!html.isEmpty()) { content.append(html); } else { content.append(doc[i++]); } } } return content.toString(); } private String parseHtmlTag() { // TODO handle string literals! if (doc[i] != '<') { return ""; } int j = i + 1; StringBuilder text = new StringBuilder(); text.append('<'); while (j < doc.length) { char c = doc[j++]; text.append(c); if (c == '>') { i = j; return text.toString(); } } return ""; } private DocTag parseTag() { if (doc[i] != '@') { return null; } i += 1; StringBuilder text = new StringBuilder(); while (i < doc.length) { char c = doc[i]; if (c == '@') { return new DocTag(text.toString().trim()); } else { String html = parseHtmlTag(); if (!html.isEmpty()) { text.append(html); } else { text.append(c); i += 1; } } } return new DocTag(text.toString().trim()); } private String expandInlineTags(String javadoc) { this.doc = javadoc.toCharArray(); StringBuilder text = new StringBuilder(doc.length); i = 0; while (i < doc.length) { String inlineTag = parseInlineTag(); if (!inlineTag.isEmpty()) { text.append(expandInlineTag(inlineTag, expander)); } else { text.append(doc[i++]); } } return text.toString(); } private String expandInlineTag(String inlineTag, InlineTagExpander expander) { String tag, text; int end = inlineTag.indexOf(' '); if (end == -1) { tag = inlineTag.substring(0); text = ""; } else { tag = inlineTag.substring(0, end); text = inlineTag.substring(end + 1); } return expander.expand(tag, text); } private String parseInlineTag() { if (doc[i] != '{' || doc[i + 1] != '@') { // No opening bracket. return ""; } int j = i + 2; StringBuilder text = new StringBuilder(); while (j < doc.length) { char c = doc[j++]; if (c == '}') { i = j; return text.toString(); } else { text.append(c); } } // EOF before closing bracket return ""; } private boolean skipNewline() { if (doc[i] == '\r') { i += 1; if (i < doc.length && doc[i] == '\n') { i += 1; } return true; } else if (doc[i] == '\n') { i += 1; return true; } return false; } private void skipWhitespace() { while (i < doc.length && isWhitespace(doc[i])) { i += 1; } } private boolean isWhitespace(char c) { return Character.isWhitespace(c) && c != '\n' && c != '\n'; } private boolean skipAsterisk() { if (i < doc.length && doc[i] == '*') { i += 1; return true; } return false; } }