/*
* DocCommentUtils.java
*
* Copyright (c) 2006 David Holroyd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.co.badgersinfoil.metaas.impl;
import java.io.IOException;
import java.io.StringReader;
import java.util.Iterator;
import java.util.regex.Pattern;
import org.antlr.runtime.ANTLRReaderStream;
import org.antlr.runtime.MismatchedTokenException;
import org.antlr.runtime.RecognitionException;
import org.asdt.core.internal.antlr.AS3Parser;
import uk.co.badgersinfoil.metaas.ActionScriptFactory;
import uk.co.badgersinfoil.metaas.SyntaxException;
import uk.co.badgersinfoil.metaas.dom.DocComment;
import uk.co.badgersinfoil.metaas.dom.DocTag;
import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListToken;
import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTokenSource;
import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTokenStream;
import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTree;
import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTreeAdaptor;
import uk.co.badgersinfoil.metaas.impl.parser.javadoc.JavadocLexer;
import uk.co.badgersinfoil.metaas.impl.parser.javadoc.JavadocParser;
/**
* Helpers for dealing with documentation-comments.
*/
class DocCommentUtils {
private static final LinkedListTreeAdaptor TREE_ADAPTOR = new LinkedListTreeAdaptor();
private DocCommentUtils() { /* hide ctor */ }
public static String getDocComment(LinkedListTree node) {
LinkedListToken tok = findDocCommentToken(node);
if (tok == null) {
return null;
}
return commentToString(tok);
}
public static LinkedListToken findDocCommentToken(LinkedListTree node) {
LinkedListToken tok=node.getStartToken();
if (tok == null) {
return null;
}
// find the first proper token,
outer: while (true) {
switch (tok.getType()) {
case AS3Parser.WS:
case AS3Parser.NL:
case AS3Parser.ML_COMMENT:
case AS3Parser.SL_COMMENT:
tok = tok.getNext();
break;
default:
break outer;
}
}
// search backwards from the first proper token until we reach the first ML_COMMENT
for (tok=tok.getPrev(); tok!=null; tok=tok.getPrev()) {
switch (tok.getType()) {
case AS3Parser.WS:
case AS3Parser.NL:
continue;
case AS3Parser.ML_COMMENT:
if (tok.getText().startsWith("/**")) {
return tok;
}
// fall though,
default:
return null;
}
}
return null;
}
// TODO: need to tie comment indentation etc. to the actual current
// indentation level.
private static String commentToString(LinkedListToken tok) {
return tok.getText().replaceFirst("\\A/\\*+", "")
.replaceFirst("(?:(?<=[\n\r])[ \t]+)?\\*+/\\Z", "")
.replaceAll("([\n\r])\\s*\\*", "$1");
}
public static void setDocComment(LinkedListTree node, String text) {
LinkedListToken tok = findDocCommentToken(node);
if (text == null) {
if (tok != null) {
LinkedListToken next = tok.getNext();
LinkedListToken prev = tok.getPrev();
tok.delete();
// TODO: looks like I didn't finish here,
if (next.getType() == AS3Parser.NL) {
next.getNext();
}
if (prev.getType() == AS3Parser.WS) {
prev.getPrev();
}
}
return;
}
assertValidContent(text);
String indent = ASTUtils.findIndent(node);
text = "/**" + text.replaceAll("(\n|\r\n|\r)", "$1"+indent+" *");
if (!text.endsWith("*")) {
text += "*";
}
text += "/";
if (tok == null) {
LinkedListToken comment = TokenBuilder.newMLComment(text);
insertDocComment(node, indent, comment);
} else {
tok.setText(text);
}
}
private static void insertDocComment(LinkedListTree node, String indent, LinkedListToken comment) {
LinkedListToken nl = TokenBuilder.newNewline();
// TODO: try harder to find the right place/line to
// insert the comment
findTokenToInsertCommentBefore(node).beforeInsert(comment);
comment.afterInsert(nl);
if (indent.length() > 0) {
LinkedListToken indentTok = TokenBuilder.newWhiteSpace(indent);
nl.afterInsert(indentTok);
}
}
private static LinkedListToken findTokenToInsertCommentBefore(LinkedListTree node) {
LinkedListToken tok = node.getStartToken();
outer: while (true) {
switch (tok.getType()) {
case AS3Parser.WS:
case AS3Parser.NL:
tok = tok.getNext();
break;
default:
break outer;
}
}
return tok;
}
public static void assertValidContent(String text) {
int pos = text.indexOf("*/");
if (pos != -1) {
throw new SyntaxException("End-on-comment marker found at position "+pos);
}
}
public static void increaseCommentIndent(LinkedListToken tok, String indent) {
tok.setText(tok.getText().replaceAll("(\n|\r\n|\r)", "$1"+indent));
}
public static String getDescriptionString(LinkedListTree ast) {
LinkedListTree javadoc = buildJavadoc(ast);
if (javadoc == null) {
return null;
}
LinkedListTree desc = javadoc.getFirstChild();
return stringify(desc);
}
public static LinkedListTree buildJavadoc(LinkedListTree ast) {
LinkedListToken doc = findDocCommentToken(ast);
if (doc == null) {
return null;
}
String body = getCommentBody(doc);
LinkedListTree javadoc = parse(body);
return javadoc;
}
private static String getCommentBody(LinkedListToken doc) {
String text = doc.getText();
return text.substring(3, text.length()-2);
}
private static String stringify(LinkedListTree ast) {
StringBuffer result = new StringBuffer();
for (LinkedListToken tok=ast.getStartToken(); tok!=null&&tok.getType()!=-1; tok=tok.getNext()) {
result.append(stringify(tok));
if (tok == ast.getStopToken()) {
break;
}
}
return result.toString();
}
private static String stringify(LinkedListToken tok) {
switch (tok.getType()) {
case JavadocParser.NL:
// TODO: use the original line-ending format
return "\n";
default:
return tok.getText();
}
}
public static void setDescriptionString(LinkedListTree ast, String description) {
LinkedListToken doc = findDocCommentToken(ast);
LinkedListTree javadoc = null;
LinkedListTree desc = null;
if (doc != null) {
javadoc = parse(getCommentBody(doc));
trimEOF(javadoc);
desc = javadoc.getFirstChild();
}
if (description == null) {
if (desc != null) {
ASTUtils.deleteAllChildren(desc);
doc.setText("/**"+ASTUtils.stringifyNode(javadoc)+"*/");
}
return;
}
assertValidContent(description);
String newline = getNewlineText(ast, javadoc);
if (!description.startsWith("\n")) {
description = "\n" + description;
}
description = description.replaceAll("\n", newline);
LinkedListTree newDesc = parseDescription(description);
if (doc == null) {
String indent = ASTUtils.findIndent(ast);
doc = TokenBuilder.newMLComment("/**"+ASTUtils.stringifyNode(newDesc)+"\n"+indent+" */");
insertDocComment(ast, indent, doc);
} else {
newDesc.appendToken(new LinkedListToken(JavadocParser.NL, newline));
javadoc.setChildWithTokens(0, newDesc);
doc.setText("/**"+ASTUtils.stringifyNode(javadoc)+"*/");
}
}
public static String getNewlineText(LinkedListTree ast, LinkedListTree javadoc) {
String newline = null;
if (javadoc != null) {
newline = findNewline(javadoc);
}
if (newline == null) {
newline = "\n"+ASTUtils.findIndent(ast)+" * "; // TODO: use document existing end-of-line format
}
return newline;
}
public static String findNewline(LinkedListTree javadoc) {
LinkedListToken tok=javadoc.getStopToken();
if (tok.getType() == JavadocParser.NL) {
// Skip the very-last NL, since this will precede the
// closing-comment marker, and therefore will lack the
// '*' that should be present at the start of every
// other line,
tok=tok.getPrev();
}
for (; tok!=null; tok=tok.getPrev()) {
if (tok.getType() == JavadocParser.NL) {
return tok.getText();
}
}
return null;
}
private static LinkedListTree parseDescription(String description) {
try {
JavadocParser parser = parserOn(description);
LinkedListTree desc = (LinkedListTree)parser.description().getTree();
LinkedListToken after = (LinkedListToken)parser.getTokenStream().LT(1);
if (!isEOF(after)) {
throw new SyntaxException("trailing content after description: "+ActionScriptFactory.str(after.getText()));
}
trimEOF(desc);
return desc;
} catch (IOException e) {
throw new SyntaxException(e);
} catch (RecognitionException e) {
throw new SyntaxException(e);
}
}
private static boolean isEOF(LinkedListToken after) {
return after.getType() == JavadocParser.EOF;
}
/**
* removes trailing enf-of-file tokens from the AST
*/
private static void trimEOF(LinkedListTree desc) {
LinkedListTree lastChild = desc.getLastChild();
if (lastChild != null) {
trimEOF(lastChild);
}
while (isEOF(desc.getStopToken())) {
LinkedListToken stop = desc.getStopToken();
LinkedListToken prev = stop.getPrev();
desc.setStopToken(prev);
stop.delete();
}
}
private static LinkedListTree parse(String body) {
try {
JavadocParser parser = parserOn(body);
LinkedListTree result = (LinkedListTree)parser.comment_body().getTree();
trimEOF(result);
return result;
} catch (IOException e) {
throw new SyntaxException(e);
} catch (RecognitionException e) {
throw new SyntaxException(e);
}
}
public static LinkedListTree parseParaTag(String text) {
try {
JavadocParser parser = parserOn(text);
LinkedListTree result = (LinkedListTree)parser.paragraph_tag().getTree();
trimEOF(result);
return result;
} catch (IOException e) {
throw new SyntaxException(e);
} catch (MismatchedTokenException e) {
throw new SyntaxException("Expexted "+JavadocParser.tokenNames[e.expecting]+" but found "+JavadocParser.tokenNames[e.getUnexpectedType()], e);
} catch (RecognitionException e) {
throw new SyntaxException(e);
}
}
private static JavadocParser parserOn(String text) throws IOException {
StringReader in = new StringReader(text);
ANTLRReaderStream cs = new ANTLRReaderStream(in);
JavadocLexer lexer = new JavadocLexer(cs);
LinkedListTokenSource source = new LinkedListTokenSource(lexer);
LinkedListTokenStream stream = new LinkedListTokenStream(source);
JavadocParser parser = new JavadocParser(stream);
parser.setTreeAdaptor(TREE_ADAPTOR);
return parser;
}
public static ASTDocComment createDocComment(LinkedListTree ast) {
return new ASTDocComment(ast);
}
public static DocTag findParam(DocComment doc, String name) {
Iterator params = doc.findTags("param");
Pattern p = Pattern.compile("\\s*\\Q"+name+"\\E\\b");
while (params.hasNext()) {
DocTag param = (DocTag)params.next();
if (p.matcher(param.getBodyString()).lookingAt()) {
return param;
}
}
return null;
}
}