/*******************************************************************************
* Copyright (c) 2005, 2007 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*******************************************************************************/
package org.eclipse.dltk.javascript.scriptdoc;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.ISourceRange;
import org.eclipse.dltk.corext.SourceRange;
import org.eclipse.dltk.internal.ui.text.SubstitutionTextReader;
import org.eclipse.dltk.javascript.core.JSKeywordCategory;
import org.eclipse.dltk.javascript.core.JSKeywordManager;
import org.eclipse.dltk.javascript.parser.jsdoc.JSDocTag;
import org.eclipse.dltk.utils.TextUtils;
public class JavaDoc2HTMLTextReader extends SubstitutionTextReader {
/*
* Standard doc tag name (value {@value}).
*/
public static final String TAG_AUTHOR = "@author"; //$NON-NLS-1$
/**
* Standard inline doc tag name (value {@value} ).
* <p>
* Note that this tag first appeared in J2SE 5.
* </p>
*
* @since 3.1
*/
public static final String TAG_CODE = "@code"; //$NON-NLS-1$
/**
* Standard doc tag name (value {@value} ).
*/
public static final String TAG_DEPRECATED = "@deprecated"; //$NON-NLS-1$
/**
* Standard inline doc tag name (value {@value} ).
*/
public static final String TAG_DOCROOT = "@docRoot"; //$NON-NLS-1$
/**
* Standard doc tag name (value {@value} ).
*/
public static final String TAG_EXCEPTION = "@exception"; //$NON-NLS-1$
/**
* Standard inline doc tag name (value {@value} ).
*/
public static final String TAG_INHERITDOC = "@inheritDoc"; //$NON-NLS-1$
/**
* Standard inline doc tag name (value {@value} ).
*/
public static final String TAG_LINK = "@link"; //$NON-NLS-1$
/**
* Standard inline doc tag name (value {@value} ).
*/
public static final String TAG_LINKPLAIN = "@linkplain"; //$NON-NLS-1$
/**
* Standard inline doc tag name (value {@value} ).
* <p>
* Note that this tag first appeared in J2SE 5.
* </p>
*
* @since 3.1
*/
public static final String TAG_LITERAL = "@literal"; //$NON-NLS-1$
/**
* Standard doc tag name (value {@value} ).
*/
public static final String TAG_PARAM = "@param"; //$NON-NLS-1$
/**
* Standard doc tag name (value {@value} ).
*/
public static final String TAG_RETURN = "@return"; //$NON-NLS-1$
/**
* Standard doc tag name (value {@value} ).
*/
public static final String TAG_RETURNS = "@returns"; //$NON-NLS-1$
/**
* Standard doc tag name (value {@value} ).
*/
public static final String TAG_SEE = "@see"; //$NON-NLS-1$
/**
* Standard doc tag name (value {@value} ).
*/
public static final String TAG_SERIAL = "@serial"; //$NON-NLS-1$
/**
* Standard doc tag name (value {@value} ).
*/
public static final String TAG_SERIALDATA = "@serialData"; //$NON-NLS-1$
/**
* Standard doc tag name (value {@value} ).
*/
public static final String TAG_SERIALFIELD = "@serialField"; //$NON-NLS-1$
/**
* Standard doc tag name (value {@value} ).
*/
public static final String TAG_SINCE = "@since"; //$NON-NLS-1$
/**
* Standard doc tag name (value {@value} ).
*/
public static final String TAG_THROWS = "@throws"; //$NON-NLS-1$
/**
* Standard inline doc tag name (value {@value} ).
*/
public static final String TAG_VALUE = "@value"; //$NON-NLS-1$
/**
* Standard doc tag name (value {@value} ).
*/
public static final String TAG_VERSION = "@version"; //$NON-NLS-1$
static private class Pair {
final String fTag;
final String fContent;
Pair(String tag, String content) {
fTag = tag;
fContent = content;
}
}
private List<String> fParameters;
private String fReturn;
private List<String> fExceptions;
private List<String> fAuthors;
private List<String> fSees;
private List<String> fSince;
private List<Pair> fRest; // list of Pair objects
public JavaDoc2HTMLTextReader(Reader reader) {
super(reader);
setSkipWhitespace(false);
}
private int getTag(StringBuffer buffer) throws IOException {
int c = nextChar();
while (c == '.' || c == '-' || c != -1
&& Character.isLetterOrDigit((char) c)) {
buffer.append((char) c);
c = nextChar();
}
return c;
}
private int getContent(StringBuffer buffer, char stopChar)
throws IOException {
int c = nextChar();
while (c != -1 && c != stopChar) {
buffer.append((char) c);
c = nextChar();
}
return c;
}
private int getContentUntilNextTag(StringBuffer buffer) throws IOException {
int c = nextChar();
boolean blockStartRead = false;
while (c != -1) {
if (c == '@') {
int index = buffer.length();
while (--index >= 0
&& Character.isWhitespace(buffer.charAt(index))) {
switch (buffer.charAt(index)) {
case '\n':
case '\r':
return c;
}
if (index <= 0) {
return c;
}
}
}
if (blockStartRead) {
buffer.append(processBlockTag());
blockStartRead = false;
} else {
buffer.append((char) c);
}
c = nextChar();
blockStartRead = c == '{';
}
return c;
}
private String substituteQualification(String qualification) {
String result = qualification.replace('#', '.');
if (result.startsWith(".")) { //$NON-NLS-1$
result = result.substring(1);
}
return result;
}
static enum TypedDefinition {
NONE, AUTO, PARAM
}
private void printDefinitions(StringBuffer buffer, List<String> list,
TypedDefinition typed) {
Iterator<String> e = list.iterator();
while (e.hasNext()) {
String s = e.next();
printDefinition(buffer, s, typed);
}
}
private void printDefinition(StringBuffer buffer, String s,
TypedDefinition typed) {
buffer.append("<dd>"); //$NON-NLS-1$
if (s != null && s.length() != 0) {
if (typed == TypedDefinition.NONE) {
buffer.append(s);
} else if (typed == TypedDefinition.PARAM) {
final ISourceRange param = getParamRange(s);
if (param != null) {
if (param.getOffset() > 0) {
buffer.append("<i>");
buffer.append(TextUtils.escapeHTML(s.substring(0,
param.getOffset())));
buffer.append("</i>");
}
buffer.append("<b>"); //$NON-NLS-1$
buffer.append(TextUtils.escapeHTML(s.substring(
param.getOffset(),
param.getOffset() + param.getLength())));
buffer.append("</b>"); //$NON-NLS-1$
buffer.append(s.substring(param.getOffset()
+ param.getLength()));
} else {
buffer.append(s);
}
} else {
assert (typed == TypedDefinition.AUTO);
int endOfType = skipTypeDefinition(s);
if (endOfType > 0) {
buffer.append("<i>");
buffer.append(TextUtils.escapeHTML(s
.substring(0, endOfType)));
buffer.append("</i>");
}
buffer.append(s.substring(endOfType));
}
}
buffer.append("</dd>"); //$NON-NLS-1$
}
private int skipTypeDefinition(String s) {
final int length = s.length();
int i = 0;
int braces = 0;
// \s*
while (i < length && Character.isWhitespace(s.charAt(i)))
++i;
if (i < length && s.charAt(i) == '{') {
braces++;
++i;
while (i < length) {
if (s.charAt(i) == '}') {
if (--braces == 0)
break;
}
if (s.charAt(i) == '{')
braces++;
++i;
}
if (i < length) {
++i; // skip closing '}'
}
}
return i;
}
private ISourceRange getParamRange(String s) {
int i = skipTypeDefinition(s);
final int length = s.length();
while (i < length && Character.isWhitespace(s.charAt(i)))
++i;
final int paramStart = i;
if (i < length && s.charAt(i) == '<') {
++i;
// generic type parameter
// read <\s*\w*\s*>
while (i < length && Character.isWhitespace(s.charAt(i)))
++i;
while (i < length && Character.isJavaIdentifierPart(s.charAt(i)))
++i;
while (i < length && s.charAt(i) != '>')
++i;
} else {
if (i < length && s.charAt(i) == '[')
i++; // optional
// simply read an identifier
while (i < length
&& (Character.isJavaIdentifierPart(s.charAt(i)) || s
.charAt(i) == '.'))
++i;
if (i < length && s.charAt(i) == ']')
i++; // optional
}
if (i > paramStart) {
return new SourceRange(paramStart, i - paramStart);
}
return null;
}
private void print(StringBuffer buffer, String tag, List<String> elements,
TypedDefinition typed) {
if (!elements.isEmpty()) {
buffer.append("<dt>"); //$NON-NLS-1$
buffer.append(tag);
buffer.append("</dt>"); //$NON-NLS-1$
printDefinitions(buffer, elements, typed);
}
}
private void print(StringBuffer buffer, String tag, String content,
TypedDefinition typed) {
if (content != null) {
buffer.append("<dt>"); //$NON-NLS-1$
buffer.append(tag);
buffer.append("</dt>"); //$NON-NLS-1$
printDefinition(buffer, content, typed);
}
}
private void printRest(StringBuffer buffer) {
if (!fRest.isEmpty()) {
final Set<String> definedTags = new HashSet<String>();
Collections.addAll(definedTags, JSDocTag.getTags());
ISourceModule module = null; /* TODO identify module? */
Collections.addAll(definedTags, JSKeywordManager.getInstance()
.getKeywords(JSKeywordCategory.JS_DOC_TAG, module));
final List<Pair> unknowTags = new ArrayList<Pair>();
for (Pair p : fRest) {
buffer.append("<dt>"); //$NON-NLS-1$
if (definedTags.contains(p.fTag)) {
buffer.append(Character.toUpperCase(p.fTag.charAt(1)))
.append(p.fTag.substring(2));
if (p.fContent.length() != 0) {
buffer.append(":");
}
buffer.append("</dt>"); //$NON-NLS-1$
printDefinition(buffer, p.fContent, TypedDefinition.AUTO);
} else {
unknowTags.add(p);
}
}
for (Pair p : unknowTags) {
buffer.append("<dt>"); //$NON-NLS-1$
buffer.append(p.fTag);
if (p.fContent.length() != 0) {
buffer.append(":");
}
buffer.append("</dt>"); //$NON-NLS-1$
printDefinition(buffer, p.fContent, TypedDefinition.AUTO);
}
}
}
private String printSimpleTag() {
StringBuffer buffer = new StringBuffer();
buffer.append("<dl>"); //$NON-NLS-1$
print(buffer,
JavaDocMessages.JavaDoc2HTMLTextReader_parameters_section,
fParameters, TypedDefinition.PARAM);
print(buffer, JavaDocMessages.JavaDoc2HTMLTextReader_returns_section,
fReturn, TypedDefinition.AUTO);
print(buffer, JavaDocMessages.JavaDoc2HTMLTextReader_throws_section,
fExceptions, TypedDefinition.AUTO);
print(buffer, JavaDocMessages.JavaDoc2HTMLTextReader_author_section,
fAuthors, TypedDefinition.NONE);
print(buffer, JavaDocMessages.JavaDoc2HTMLTextReader_since_section,
fSince, TypedDefinition.NONE);
print(buffer, JavaDocMessages.JavaDoc2HTMLTextReader_see_section,
fSees, TypedDefinition.NONE);
printRest(buffer);
buffer.append("</dl>"); //$NON-NLS-1$
return buffer.toString();
}
private void handleTag(String tag, String tagContent) {
tagContent = tagContent.trim();
if (TAG_PARAM.equals(tag))
fParameters.add(tagContent);
else if (TAG_RETURN.equals(tag) || TAG_RETURNS.equals(tag))
fReturn = tagContent;
else if (TAG_EXCEPTION.equals(tag))
fExceptions.add(tagContent);
else if (TAG_THROWS.equals(tag))
fExceptions.add(tagContent);
else if (TAG_AUTHOR.equals(tag))
fAuthors.add(substituteQualification(tagContent));
else if (TAG_SEE.equals(tag))
fSees.add(substituteQualification(tagContent));
else if (TAG_SINCE.equals(tag))
fSince.add(substituteQualification(tagContent));
else {
fRest.add(new Pair(tag, tagContent));
}
}
/*
* A '@' has been read. Process a javadoc tag
*/
private String processSimpleTag() throws IOException {
fParameters = new ArrayList<String>();
fExceptions = new ArrayList<String>();
fAuthors = new ArrayList<String>();
fSees = new ArrayList<String>();
fSince = new ArrayList<String>();
fRest = new ArrayList<Pair>();
StringBuffer buffer = new StringBuffer();
int c = '@';
while (c != -1) {
buffer.setLength(0);
buffer.append((char) c);
c = getTag(buffer);
String tag = buffer.toString();
buffer.setLength(0);
if (c != -1) {
// e.g. @SuppressWarnings(...) case
if (!Character.isWhitespace(c)) {
buffer.append((char) c);
}
c = getContentUntilNextTag(buffer);
}
handleTag(tag, buffer.toString());
}
return printSimpleTag();
}
private String printBlockTag(String tag, String tagContent) {
if (TAG_LINK.equals(tag) || TAG_LINKPLAIN.equals(tag)) {
char[] contentChars = tagContent.toCharArray();
boolean inParentheses = false;
int labelStart = 0;
for (int i = 0; i < contentChars.length; i++) {
char nextChar = contentChars[i];
// tagContent always has a leading space
if (i == 0 && Character.isWhitespace(nextChar)) {
labelStart = 1;
continue;
}
if (nextChar == '(') {
inParentheses = true;
continue;
}
if (nextChar == ')') {
inParentheses = false;
continue;
}
// Stop at first whitespace that is not in parentheses
if (!inParentheses && Character.isWhitespace(nextChar)) {
labelStart = i + 1;
break;
}
}
if (TAG_LINK.equals(tag))
return "<code>" + substituteQualification(tagContent.substring(labelStart)) + "</code>"; //$NON-NLS-1$//$NON-NLS-2$
else
return substituteQualification(tagContent.substring(labelStart));
} else if (TAG_LITERAL.equals(tag)) {
return printLiteral(tagContent);
} else if (TAG_CODE.equals(tag)) {
return "<code>" + printLiteral(tagContent) + "</code>"; //$NON-NLS-1$//$NON-NLS-2$
}
// If something went wrong at least replace the {} with the content
return substituteQualification(tagContent);
}
private String printLiteral(String tagContent) {
int contentStart = 0;
for (int i = 0; i < tagContent.length(); i++) {
if (!Character.isWhitespace(tagContent.charAt(i))) {
contentStart = i;
break;
}
}
return TextUtils.escapeHTML(tagContent.substring(contentStart));
}
/*
* A '{' has been read. Process a block tag
*/
private String processBlockTag() throws IOException {
int c = nextChar();
if (c != '@') {
StringBuffer buffer = new StringBuffer();
buffer.append('{');
buffer.append((char) c);
return buffer.toString();
}
StringBuffer buffer = new StringBuffer();
if (c != -1) {
buffer.setLength(0);
buffer.append((char) c);
c = getTag(buffer);
String tag = buffer.toString();
buffer.setLength(0);
if (c != -1 && c != '}') {
buffer.append((char) c);
c = getContent(buffer, '}');
}
return printBlockTag(tag, buffer.toString());
}
return null;
}
/*
* @see SubstitutionTextReaderr#computeSubstitution(int)
*/
protected String computeSubstitution(int c) throws IOException {
if (c == '@' && fWasWhiteSpace)
return processSimpleTag();
if (c == '{')
return processBlockTag();
return null;
}
}