/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* Converts the javadoc comment in the {@code package-info.java} file into Oracle Wiki format.
*/
package com.oracle.max.tools.javadoc.wiki;
import java.io.*;
import java.util.*;
import com.sun.max.ide.JavaProject;
import com.sun.max.program.Classpath;
import com.sun.max.program.ClasspathTraversal;
import com.sun.javadoc.*;
/**
* {@link Doclet} to convert package-info javadoc to Oracle Wiki format.
*
* The conversion includes inline tags and embedded HTML.
* There are many heuristics used to do a reasonable, given the ad hoc nature of HTML
* and the Wiki notation rules. Writing proper HTML, e.g., always including end tags, helps.
*/
public class WikiDoclet extends Doclet {
private static final String TEXT = "Text";
private static final String KENAI_MAXINE_TIP = "http://kenai.com/hg/maxine~maxine/file/tip";
private static final String HIDDEN_EXCERPT_START = "{excerpt:hidden=true}";
private static final String EXCERPT_END = "{excerpt}\n";
private static final String AUTO_GEN_BEGIN = HIDDEN_EXCERPT_START + "DO NOT EDIT: Automatically generated from ";
private static final String AUTO_GEN_END = EXCERPT_END;
private static final String HRULE = "\n----\n";
private static String outputDir;
private static String docletPath;
private static String projectList;
private static boolean includeToc = true;
private static StringBuilder sb;
private static Map<String, Integer> headerDepth = new HashMap<String, Integer>();
private static Map<String, String> validHtmlTags = new HashMap<String, String>();
private static Map<String, String> classToProject = new HashMap<String, String>();
private static char[] commentTextArray;
private static Stack<HtmlTag> lists = new Stack<HtmlTag>();
static class WikiException extends RuntimeException {
private static final long serialVersionUID = 1L;
WikiException(String message) {
super(message);
}
}
/**
* Records information on an HTML tag, specifically the tag name and any embedded attributes.
*/
private static class HtmlTag {
String tag;
String attributes;
HtmlTag(String tag, String attributes) {
this.tag = tag;
this.attributes = attributes;
}
static HtmlTag createTag(StringRange range) {
int spaceIndex = range.string.indexOf(' ', range.startIndex);
if (spaceIndex >= range.endIndex) {
spaceIndex = -1;
}
String tag = range.string.substring(range.startIndex, spaceIndex < 0 ? range.endIndex : spaceIndex).toUpperCase();
String attributes = null;
if (spaceIndex >= 0) {
attributes = range.string.substring(spaceIndex + 1, range.endIndex);
}
return new HtmlTag(tag, attributes);
}
@Override
public String toString() {
return "<" + tag + (attributes != null ? " " + attributes : "") + ">";
}
}
/**
* Denotes a subrange of a string and provides search operations that are constrained to the range.
*/
private static class StringRange {
final String string;
String substring; // lazily created
final int startIndex;
final int endIndex;
StringRange(String string, int startIndex, int endIndex) {
this.string = string;
this.startIndex = startIndex;
this.endIndex = endIndex;
assert endIndex >= startIndex;
}
StringRange(StringRange range, int startIndex, int endIndex) {
this(range.string, startIndex, endIndex);
}
private String makeSubstring() {
if (substring == null) {
substring = string.substring(startIndex, endIndex);
}
return substring;
}
String content() {
return makeSubstring();
}
/*
* Searches that use String.indexOf but handles the range constraints.
* The fromIndex argument indexes "string".
*/
int indexOf(char ch, int fromIndex) {
if (fromIndex >= endIndex) {
return -1;
}
int result = makeSubstring().indexOf(ch, fromIndex - startIndex);
return result < 0 ? result : result + startIndex;
}
int indexOf(String s, int fromIndex) {
if (fromIndex >= endIndex) {
return -1;
}
int result = makeSubstring().indexOf(s, fromIndex - startIndex);
return result < 0 ? result : result + startIndex;
}
int indexOf(char ch) {
return indexOf(ch, startIndex);
}
@Override
public String toString() {
return "\"" + makeSubstring() + "\"\nstart: " + startIndex + ", end: " + endIndex;
}
}
/**
* Denotes the the content between an HTML tag and its matching end tag, with
* the index of the character after the '>' of the end tag for convenience.
*/
private static class HtmlTagData extends StringRange {
int tagEndIndex;
HtmlTagData(StringRange range, int startIndex, int endIndex, int tagEndIndex) {
super(range, startIndex, endIndex);
this.tagEndIndex = tagEndIndex;
}
@Override
public String toString() {
return super.toString() + ", tagEndIndex: " + tagEndIndex;
}
}
private static class InlineTagInfo {
Tag inlineTag;
StringRange range;
InlineTagInfo(Tag inlineTag, StringRange range) {
this.inlineTag = inlineTag;
this.range = range;
}
@Override
public String toString() {
return Tag.class.getName() + ", index: " + range;
}
}
/**
* An entity is either an HTML tag or an inline javadoc tag.
* The {@code index} value is the start of the entity.
* In the case of HTML, it is the index of the {@code '<'} character.
*/
private static abstract class EntityIndex {
final int index;
EntityIndex(int index) {
this.index = index;
}
}
private static class InlineTagEntityIndex extends EntityIndex {
/**
* The {@link InlineTagInfo} associated with this javadoc inline tag.
*/
InlineTagInfo inlineTagInfo;
InlineTagEntityIndex(int index, InlineTagInfo inlineTagInfo) {
super(index);
assert inlineTagInfo != null;
this.inlineTagInfo = inlineTagInfo;
}
}
private static class HtmlTagEntityIndex extends EntityIndex {
HtmlTagEntityIndex(int index) {
super(index);
}
}
public static boolean start(RootDoc root) {
readOptions(root.options());
addHtmlTags();
buildClassToProjectsMap();
new File(outputDir).mkdir();
PackageDoc[] packageDocs = root.specifiedPackages();
for (PackageDoc packageDoc : packageDocs) {
try {
Tag[] inlineTags = packageDoc.inlineTags();
String commentText = packageDoc.commentText();
processPackageInfoDoc(packageDoc, commentText, inlineTags);
} catch (Exception ex) {
System.err.println("Exception processing package " + packageDoc.name() + ": " + ex);
System.exit(1);
}
}
return true;
}
private static void buildClassToProjectsMap() {
// We can't use system classpath to find workspace root as it doesn't include any Maxine projects
// javadoc -classpath is NOT the same as java -classpath!
final File wsRoot = JavaProject.findWorkspace(new Classpath(docletPath));
final int wsRootLength = wsRoot.getAbsolutePath().length();
ArrayList<Classpath.Entry> projectEntries = new ArrayList<Classpath.Entry>();
String[] projects = projectList.split(",");
for (String project : projects) {
projectEntries.add(new Classpath.Directory(new File(new File(wsRoot, project), "bin")));
}
Classpath projectClasspath = new Classpath(projectEntries);
new ClasspathTraversal() {
@Override
protected boolean visitFile(File parent, String resource) {
if (resource.endsWith(".class")) {
int x = resource.lastIndexOf(".class");
classToProject.put(resource.substring(0, x).replace('/', '.').replace('$', '.'), getProject(wsRootLength, parent.getAbsolutePath()));
}
return true;
}
}.run(projectClasspath);
}
private static String getProject(int wsRootEndIndex, String binPath) {
int binIndex = binPath.lastIndexOf("/bin");
return binPath.substring(wsRootEndIndex + 1, binIndex);
}
private static void addHtmlTags() {
for (int i = 1; i <= 4; i++) {
String hd = "H" + i;
headerDepth.put(hd, i);
addHtmlTag(hd);
}
addHtmlTag("P");
addHtmlTag("I");
addHtmlTag("UL");
addHtmlTag("OL");
addHtmlTag("I");
addHtmlTag("B");
addHtmlTag("LI");
addHtmlTag("PRE");
addHtmlTag("A");
addHtmlTag("HR");
addHtmlTag("BR");
}
private static void addHtmlTag(String h) {
validHtmlTags.put(h, h);
}
private static void processPackageInfoDoc(PackageDoc packageDoc, String commentText, Tag[] inlineTags) {
commentTextArray = new char[commentText.length()];
for (int i = 0; i < commentText.length(); i++) {
commentTextArray[i] = commentText.charAt(i);
}
InlineTagInfo[] inlineTagInfo = computeTagIndices(commentText, inlineTags);
String packageInfo = "{{" + packageDoc.name() + ".package-info}}";
sb = new StringBuilder();
sb.append(AUTO_GEN_BEGIN);
sb.append(packageInfo);
sb.append(AUTO_GEN_END);
if (includeToc) {
sb.append("{toc}\n");
}
processText(new StringRange(commentText, 0, commentText.length()), inlineTagInfo, null);
sb.append(HRULE);
sb.append("Automatically generated from ");
sb.append(packageInfo);
File wikiFile = new File(outputDir, packageDoc.name() + ".wiki");
BufferedWriter wr = null;
try {
wr = new BufferedWriter(new FileWriter(wikiFile));
wr.write(sb.toString());
} catch (IOException ex) {
System.err.println(ex);
} finally {
if (wr != null) {
try {
wr.close();
} catch (IOException ex) {
}
}
}
}
/**
* Compute the actual indices of the non-text tags in the comment text.
* Experimentally, every tag in, say, a package-info comment, has the same "position",
* that of the package statement - not very helpful.
* @param inlineTags
* @return
*/
private static InlineTagInfo[] computeTagIndices(String commentText, Tag[] inlineTags) {
ArrayList<InlineTagInfo> indicesList = new ArrayList<InlineTagInfo>();
int index = 0;
for (int i = 0; i < inlineTags.length; i++) {
Tag tag = inlineTags[i];
String tagText = tag.text();
String tagName = tag.name();
if (tagName.equals(TEXT)) {
index += tagText.length();
} else {
int startIndex = index;
assert commentText.charAt(index) == '{';
int x = commentText.indexOf(tagText, index);
assert x >= 0;
x = commentText.indexOf('}', x + tagText.length());
assert x >= 0;
index = x + 1;
indicesList.add(new InlineTagInfo(tag, new StringRange(commentText, startIndex, index)));
}
}
InlineTagInfo[] indices = new InlineTagInfo[indicesList.size()];
indicesList.toArray(indices);
return indices;
}
/**
* Convert any embedded HTML tags and inline javadoc tags in {@code range} into Wiki equivalents.
* HTML tag content can contain inline tags, but not vice versa.
* Transformed text is appended to {@link #sb}.
* @param range {@code StringRange} to process
* @param inlineTagInfo info on where the inline tags are
* @param lastEntityIndex of the last entity processed
*
*/
private static void processText(StringRange range, InlineTagInfo[] inlineTagInfo, EntityIndex lastEntityIndex) {
int lastIndex = range.startIndex;
while (lastIndex < range.endIndex) {
// Create a new subrange starting at lastIndex
StringRange subRange = new StringRange(range, lastIndex, range.endIndex);
// Find the next entity in newRange
EntityIndex entityIndex = nextEntity(subRange, inlineTagInfo);
if (entityIndex == null) {
// no more, copy remaining content and terminate loop
sb.append(fixLineBreaks(subRange, lastEntityIndex));
break;
}
// copy content from lastIndex to start of new entity
sb.append(fixLineBreaks(new StringRange(range, lastIndex, entityIndex.index), lastEntityIndex));
// save entityIndex for next iteration
lastEntityIndex = entityIndex;
if (entityIndex instanceof InlineTagEntityIndex) {
// handle inline javadoc tag
InlineTagEntityIndex inlineTagEntityIndex = (InlineTagEntityIndex) entityIndex;
Tag inlineTag = inlineTagEntityIndex.inlineTagInfo.inlineTag;
if (inlineTag.name().equals("@code")) {
sb.append("{{");
sb.append(fixWikiEscapes(fixEntityReferences(inlineTag.text())));
sb.append("}}");
} else if (inlineTag.name().startsWith("@link")) {
SeeTag seeTag = (SeeTag) inlineTag;
boolean plain = inlineTag.name().equals("@linkplain");
String label = seeTag.label();
if (label.isEmpty()) {
label = null;
}
String className = seeTag.referencedClassName();
String simpleClassName = stripPackage(className);
String projectName = classToProject.get(className);
String memberName = seeTag.referencedMemberName();
String classAndMemberName = memberName == null ? simpleClassName : simpleClassName + "." + memberName;
if (projectName != null) {
sb.append('[');
}
if (!plain) {
sb.append("{{");
}
sb.append(label == null ? classAndMemberName : label);
if (!plain) {
sb.append("}}");
}
if (projectName != null) {
// link to source on kenai
sb.append(createKenaiPath(projectName, className));
}
} else {
assert false;
}
// step lastIndex beyond inline tag
lastIndex = inlineTagEntityIndex.inlineTagInfo.range.endIndex;
} else if (entityIndex instanceof HtmlTagEntityIndex) {
// process an HTML tag
int tagEndIndex = subRange.indexOf('>', entityIndex.index);
// check tag is well formed
if (tagEndIndex < 0) {
throw new WikiException("malformed HTML tag");
}
// create a new HTML tag from the tag content minus the <>
HtmlTag htmlTagInfo = HtmlTag.createTag(new StringRange(subRange.string, entityIndex.index + 1, tagEndIndex));
String htmlTag = htmlTagInfo.tag;
// a tag we don't handle (yet)
if (validHtmlTags.get(htmlTag) == null) {
throw new WikiException("unimplemented HTML tag: " + htmlTag);
}
// HTML is horribly irregular but in "good" HTML, many tags have matching end tags
// and we want to process the internal context recursively.
HtmlTagData data = findmatchingTag(subRange, htmlTag, tagEndIndex + 1);
int hd = isHeader(htmlTag);
if (hd > 0) {
sb.append('\n');
sb.append('h');
sb.append(hd);
sb.append(". ");
processText(data, inlineTagInfo, lastEntityIndex);
sb.append('\n');
} else if (htmlTag.equals("I")) {
sb.append('_');
sb.append(data.content());
sb.append('_');
} else if (htmlTag.equals("B")) {
sb.append('*');
sb.append(data.content());
sb.append('*');
} else if (htmlTag.equals("P")) {
// matching tag usually omitted
sb.append('\n');
if (data != null) {
processText(data, inlineTagInfo, lastEntityIndex);
} else {
lastIndex = tagEndIndex + 1;
}
sb.append('\n');
} else if (htmlTag.equals("UL") | htmlTag.equals("OL")) {
lists.push(htmlTagInfo);
processText(data, inlineTagInfo, lastEntityIndex);
lists.pop();
sb.append('\n');
} else if (htmlTag.equals("PRE")) {
// no interpretation of body
sb.append("{code}\n");
sb.append(replacePreLeadingSpaces(fixEntityReferences(data.content())));
sb.append("{code}\n");
} else if (htmlTag.equals("LI")) {
sb.append('\n');
processLists();
processText(data, inlineTagInfo, lastEntityIndex);
} else if (htmlTag.equals("A")) {
sb.append('[');
sb.append(data.content());
sb.append('|');
sb.append(getHRef(htmlTagInfo.attributes));
sb.append(']');
} else if (htmlTag.equals("HR")) {
sb.append(HRULE);
} else if (htmlTag.equals("BR")) {
sb.append("\n");
}
if (data != null) {
lastIndex = data.tagEndIndex;
} else {
lastIndex = tagEndIndex + 1;
}
}
}
}
private static void processLists() {
for (HtmlTag htmlTag : lists) {
if (htmlTag.tag.equals("UL")) {
sb.append('*');
} else if (htmlTag.tag.equals("OL")) {
sb.append('#');
} else {
assert false;
}
}
sb.append(' ');
}
private static String getHRef(String s) {
int index = s.indexOf('"');
int lastIndex = s.lastIndexOf('"');
return s.substring(index + 1, lastIndex);
}
private static String createKenaiPath(String projectName, String className) {
return createKenaiPath(projectName, className, true);
}
private static String createKenaiPath(String projectName, String className, boolean inLink) {
StringBuilder ssb = new StringBuilder();
if (inLink) {
ssb.append('|');
}
ssb.append(KENAI_MAXINE_TIP);
ssb.append('/');
ssb.append(projectName);
ssb.append("/src/");
ssb.append(className.replace('.', '/'));
ssb.append(".java");
if (inLink) {
ssb.append(']');
}
return ssb.toString();
}
/**
* In package-info files class names must be fully qualified (pain), but javadoc
* strips the package in the HTML, and we do the same.
* @param qualName
*/
private static String stripPackage(String qualName) {
for (int i = 0; i < qualName.length(); i++) {
if (Character.isUpperCase(qualName.charAt(i))) {
return i == 0 ? qualName : qualName.substring(i);
}
}
return qualName;
}
/**
* Remove internal line breaks and leading space from the javadoc comment as Wiki will treat them literally.
* Very heuristic unfortunately.
* @param range string range to be analyzed
* @return
*/
private static String fixLineBreaks(StringRange range, EntityIndex lastEntityIndex) {
String result = "";
int index = range.startIndex;
while (index < range.endIndex) {
int breakIndex = range.indexOf("\n ", index);
if (breakIndex < 0 || breakIndex >= range.endIndex - 2) {
if (breakIndex == range.endIndex - 2) {
// if it ends in "\n " we drop the newline but keep the space
result += range.string.substring(index, range.endIndex - 2) + " ";
} else {
result += range.string.substring(index, range.endIndex);
}
break;
}
result += range.string.substring(index, breakIndex);
// if at start, after HTML, skip newline and space, else just the newline (space becomes separator)
if (breakIndex == range.startIndex && (lastEntityIndex != null && lastEntityIndex instanceof HtmlTagEntityIndex)) {
index = breakIndex + 2;
} else {
index = breakIndex + 1;
}
}
return result;
}
private static String fixEntityReferences(String s) {
StringBuilder ssb = new StringBuilder();
int index = 0;
while (index < s.length()) {
int eIndex = s.indexOf('&', index);
if (eIndex < 0) {
break;
}
// append up to the '&'
ssb.append(s.substring(index, eIndex));
eIndex++;
char c = s.charAt(eIndex);
if (c == '#') {
int nIndex = eIndex + 1;
char dig = s.charAt(nIndex);
int code = 0;
while (dig >= '0' && dig <= '9') {
code = code * 10 + (dig - '0');
nIndex++;
dig = s.charAt(nIndex);
}
ssb.append((char) code);
index = nIndex;
} else if (entityMatch(s, eIndex, "amp")) {
ssb.append('&');
index = eIndex + 4;
} else if (entityMatch(s, eIndex, "gt")) {
ssb.append('>');
index = eIndex + 3;
} else if (entityMatch(s, eIndex, "ge")) {
ssb.append(">=");
index = eIndex + 3;
} else if (entityMatch(s, eIndex, "lt")) {
ssb.append('<');
index = eIndex + 3;
} else if (entityMatch(s, eIndex, "le")) {
ssb.append("<=");
index = eIndex + 3;
} else {
throw new WikiException("undecoded character entity reference");
}
}
ssb.append(s, index, s.length());
return ssb.toString();
}
private static boolean entityMatch(String s, int index, String m) {
try {
for (int i = 0; i < m.length(); i++) {
if (s.charAt(index + i) != m.charAt(i)) {
return false;
}
}
return s.charAt(index + m.length()) == ';';
} catch (StringIndexOutOfBoundsException ex) {
return false;
}
}
private static String fixWikiEscapes(String s) {
StringBuilder ssb = new StringBuilder();
int index = 0;
while (index < s.length()) {
char ch = s.charAt(index);
if (ch == '[' || ch == ']') {
ssb.append('\\');
}
ssb.append(ch);
index++;
}
return ssb.toString();
}
/**
* Remove the first leading space after a newline (arises due to the way javadoc comments are written).
* @param s
* @return
*/
private static String replacePreLeadingSpaces(String s) {
StringBuilder ssb = new StringBuilder();
int index = 0;
while (index < s.length()) {
int nlIndex = s.indexOf("\n ", index);
if (nlIndex < 0) {
break;
}
// There is always a "\n " at the start that we just want to ignore
if (index > 0) {
// append up to and including the newline
nlIndex++;
ssb.append(s.substring(index, nlIndex));
// check for leading space
try {
if (s.charAt(nlIndex) == ' ') {
nlIndex++;
}
} catch (StringIndexOutOfBoundsException ex) {
break;
}
} else {
nlIndex = 2;
}
index = nlIndex;
}
// append everything after the last position
ssb.append(s.substring(index));
return ssb.toString();
}
/**
* Finds the index of the next inline tag or HTML tag in {@code s[range]}.
* @param range string range to search
* @param inlineTagInfo
* @return an {@link EntityIndex} or null if not found
*/
private static EntityIndex nextEntity(StringRange range, InlineTagInfo[] inlineTagInfo) {
int htmlTagIndex = range.indexOf('<');
if (htmlTagIndex < 0 || (range.string.charAt(htmlTagIndex + 1) == '/')) {
htmlTagIndex = -1;
}
int inlineTagIndex = -1;
InlineTagInfo inlineTagInfoResult = null;
for (int i = 0; i < inlineTagInfo.length; i++) {
int thisIndex = inlineTagInfo[i].range.startIndex;
if (thisIndex >= range.startIndex && thisIndex < range.endIndex) {
// possibility
inlineTagIndex = thisIndex;
inlineTagInfoResult = inlineTagInfo[i];
break;
}
}
if (htmlTagIndex < 0 && inlineTagIndex < 0) {
return null;
} else {
if (htmlTagIndex < 0) {
return new InlineTagEntityIndex(inlineTagIndex, inlineTagInfoResult);
} else if (inlineTagIndex < 0) {
return new HtmlTagEntityIndex(htmlTagIndex);
} else {
// both possible
if (htmlTagIndex < inlineTagIndex) {
return new HtmlTagEntityIndex(htmlTagIndex);
} else {
return new InlineTagEntityIndex(inlineTagIndex, inlineTagInfoResult);
}
}
}
}
/**
* Find the tag that matches {@code tag} in string {@code range} starting at {@code index}.
* @param range string range to be searched
* @param tag
* @param index index of first char after '>', i.e. the start tag
* @return {@link HtmlTagData} or null if not found
*/
private static HtmlTagData findmatchingTag(StringRange range, String tag, final int index) {
int curIndex = index;
int nestCount = 0;
while (true) {
int tagIndex = range.indexOf('<', curIndex);
if (tagIndex < 0) {
// no more tags, therefore no matching tag found in the given string range
return null;
}
// find the end of this tag, which may be the starts of a nested tag or the matching end tag
int tagEndIndex = range.indexOf('>', tagIndex);
if (tagEndIndex < 0) {
throw new WikiException("malformed HTML tag");
}
// check if this is a closing tag
if (range.string.charAt(tagIndex + 1) == '/') {
String endTag = range.string.substring(tagIndex + 2, tagEndIndex).toUpperCase(); // skip "</"
if (endTag.equals(tag)) {
if (nestCount == 0) {
return new HtmlTagData(range, index, tagIndex, tagEndIndex + 1);
} else {
nestCount--;
}
} else {
// we found the end of an unmatching nested tag, just keep going
}
curIndex = tagEndIndex + 1;
} else {
// start of nested tag, stack it if it matches "tag" else skip it.
// <P> is special, any nested tag matches (<P> as separator style)
String endTag = range.string.substring(tagIndex + 1, tagEndIndex).toUpperCase();
if (tag.equals("P") && endTag.equals("P")) {
return new HtmlTagData(range, index, tagIndex, tagIndex);
} else if (endTag.equals(tag)) {
nestCount++;
}
curIndex = tagEndIndex + 1;
}
}
}
private static int isHeader(String tagName) {
Integer depth = headerDepth.get(tagName.toUpperCase());
if (depth == null) {
return -1;
} else {
return depth;
}
}
private static String readOptions(String[][] options) {
String tagName = null;
for (int i = 0; i < options.length; i++) {
String[] opt = options[i];
if (opt[0].equals("-d")) {
outputDir = opt[1];
} else if (opt[0].equals("-notoc")) {
includeToc = false;
} else if (opt[0].equals("-docletpath")) {
docletPath = opt[1];
} else if (opt[0].equals("-projects")) {
projectList = opt[1];
}
}
return tagName;
}
public static int optionLength(String option) {
if (option.equals("-d") || option.equals("-link")) {
return 2;
} else if (option.equals("-notoc")) {
return 1;
} else if (option.equals("-projects")) {
return 2;
}
return 0;
}
public static boolean validOptions(String[][] options, DocErrorReporter reporter) {
boolean foundDirOption = false;
for (int i = 0; i < options.length; i++) {
String[] opt = options[i];
if (opt[0].equals("-d")) {
if (foundDirOption) {
reporter.printError("Only one -d option allowed.");
return false;
} else {
foundDirOption = true;
}
} else if (opt[0].equals("-notoc")) {
// ignore
} else if (opt[0].equals("-link")) {
// ignore
} else if (opt[0].equals("-projects")) {
// ignore
}
}
if (!foundDirOption) {
reporter.printError("Usage: javadoc -doclet WikiDoclet -d outputDir -link url ...");
}
return foundDirOption;
}
}