# HG changeset patch
# User aefimov
# Date 1468875192 -10800
# Node ID 7d5a50793b6df9ee5b69984888874d5268e95ac9
# Parent cfd609c461fa0287d22e927031bb9dab9599f0f5
8138725: Add options for Javadoc generation
Reviewed-by: jjg
diff -r cfd609c461fa -r 7d5a50793b6d make/build.properties
--- a/make/build.properties Wed Nov 23 00:35:47 2016 -0800
+++ b/make/build.properties Mon Jul 18 23:53:12 2016 +0300
@@ -106,7 +106,8 @@
javadoc.includes = \
com/sun/javadoc/ \
- com/sun/tools/javadoc/
+ com/sun/tools/javadoc/ \
+ com/sun/tools/doclets/internal/toolkit/util/FatalError.java
javadoc.tests = \
tools/javadoc/
diff -r cfd609c461fa -r 7d5a50793b6d src/share/classes/com/sun/tools/doclets/formats/html/ConfigurationImpl.java
--- a/src/share/classes/com/sun/tools/doclets/formats/html/ConfigurationImpl.java Wed Nov 23 00:35:47 2016 -0800
+++ b/src/share/classes/com/sun/tools/doclets/formats/html/ConfigurationImpl.java Mon Jul 18 23:53:12 2016 +0300
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2016, 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
@@ -25,6 +25,8 @@
package com.sun.tools.doclets.formats.html;
+import com.sun.tools.javadoc.JavaScriptScanner;
+import com.sun.tools.javadoc.RootDocImpl;
import com.sun.tools.doclets.internal.toolkit.*;
import com.sun.tools.doclets.internal.toolkit.util.*;
@@ -164,6 +166,11 @@
public boolean createoverview = false;
/**
+ * Whether or not to check for JavaScript in doc comments.
+ */
+ private boolean allowScriptInComments;
+
+ /**
* Unique Resource Handler for this package.
*/
public final MessageRetriever standardmessage;
@@ -259,8 +266,11 @@
nooverview = true;
} else if (opt.equals("-overview")) {
overview = true;
+ } else if (opt.equals("--allow-script-in-comments")) {
+ allowScriptInComments = true;
}
}
+
if (root.specifiedClasses().length > 0) {
Map map = new HashMap();
PackageDoc pd;
@@ -274,6 +284,30 @@
}
setCreateOverview();
setTopFile(root);
+
+ if (root instanceof RootDocImpl) {
+ JavaScriptScanner jss = ((RootDocImpl) root).initJavaScriptScanner(isAllowScriptInComments());
+ if (jss != null) {
+ // In a more object-oriented world, this would be done by methods on the Option objects.
+ // Note that -windowtitle silently removes any and all HTML elements, and so does not need
+ // to be handled here.
+ checkJavaScript(jss, "-header", header);
+ checkJavaScript(jss, "-footer", footer);
+ checkJavaScript(jss, "-top", top);
+ checkJavaScript(jss, "-bottom", bottom);
+ checkJavaScript(jss, "-doctitle", doctitle);
+ checkJavaScript(jss, "-packagesheader", packagesheader);
+ }
+ }
+ }
+
+ private void checkJavaScript(JavaScriptScanner jss, final String opt, String value) {
+ jss.parse(value, new JavaScriptScanner.Reporter() {
+ public void report() {
+ root.printError(getText("doclet.JavaScript_in_option", opt));
+ throw new FatalError();
+ }
+ });
}
/**
@@ -307,7 +341,9 @@
option.equals("-serialwarn") ||
option.equals("-use") ||
option.equals("-nonavbar") ||
- option.equals("-nooverview")) {
+ option.equals("-nooverview") ||
+ option.startsWith("-xdoclint:") ||
+ option.equals("--allow-script-in-comments")) {
return 1;
} else if (option.equals("-help")) {
System.out.println(getText("doclet.usage"));
@@ -503,4 +539,13 @@
else
return Locale.getDefault();
}
+
+ /**
+ * Returns whether or not to allow JavaScript in comments.
+ * Default is off; can be set true from a command line option.
+ * @return the allowScriptInComments
+ */
+ public boolean isAllowScriptInComments() {
+ return allowScriptInComments;
+ }
}
diff -r cfd609c461fa -r 7d5a50793b6d src/share/classes/com/sun/tools/doclets/formats/html/HtmlDoclet.java
--- a/src/share/classes/com/sun/tools/doclets/formats/html/HtmlDoclet.java Wed Nov 23 00:35:47 2016 -0800
+++ b/src/share/classes/com/sun/tools/doclets/formats/html/HtmlDoclet.java Mon Jul 18 23:53:12 2016 +0300
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2003, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2016, 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
@@ -185,6 +185,8 @@
} catch (Exception e) {
e.printStackTrace();
throw new DocletAbortException();
+ } catch (FatalError fe) {
+ throw fe;
}
}
}
diff -r cfd609c461fa -r 7d5a50793b6d src/share/classes/com/sun/tools/doclets/internal/toolkit/AbstractDoclet.java
--- a/src/share/classes/com/sun/tools/doclets/internal/toolkit/AbstractDoclet.java Wed Nov 23 00:35:47 2016 -0800
+++ b/src/share/classes/com/sun/tools/doclets/internal/toolkit/AbstractDoclet.java Mon Jul 18 23:53:12 2016 +0300
@@ -81,6 +81,8 @@
}
try {
doclet.startGeneration(root);
+ } catch (FatalError e) {
+ return false;
} catch (Exception exc) {
exc.printStackTrace();
return false;
diff -r cfd609c461fa -r 7d5a50793b6d src/share/classes/com/sun/tools/doclets/internal/toolkit/builders/AbstractBuilder.java
--- a/src/share/classes/com/sun/tools/doclets/internal/toolkit/builders/AbstractBuilder.java Wed Nov 23 00:35:47 2016 -0800
+++ b/src/share/classes/com/sun/tools/doclets/internal/toolkit/builders/AbstractBuilder.java Mon Jul 18 23:53:12 2016 +0300
@@ -108,7 +108,14 @@
configuration.root.printError("Unknown element: " + component);
throw new DocletAbortException();
} catch (InvocationTargetException e) {
- e.getCause().printStackTrace();
+ Throwable cause = e.getCause();
+ if (cause instanceof FatalError) {
+ throw (FatalError) cause;
+ } else if (cause instanceof DocletAbortException) {
+ throw (DocletAbortException) cause;
+ } else {
+ cause.printStackTrace();
+ }
} catch (Exception e) {
e.printStackTrace();
configuration.root.printError("Exception " +
diff -r cfd609c461fa -r 7d5a50793b6d src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclets.properties
--- a/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclets.properties Wed Nov 23 00:35:47 2016 -0800
+++ b/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclets.properties Mon Jul 18 23:53:12 2016 +0300
@@ -29,6 +29,8 @@
doclet.Building_Tree=Building tree for all the packages and classes...
doclet.Building_Index=Building index for all the packages and classes...
doclet.Building_Index_For_All_Classes=Building index for all classes...
+doclet.JavaScript_in_option=Argument for {0} contains JavaScript.\n\
+Use --allow-script-in-comments to allow use of JavaScript.
doclet.sourcetab_warning=The argument for -sourcetab must be an integer greater than 0.
doclet.Packages=Packages
doclet.Other_Packages=Other Packages
diff -r cfd609c461fa -r 7d5a50793b6d src/share/classes/com/sun/tools/doclets/internal/toolkit/util/FatalError.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/tools/doclets/internal/toolkit/util/FatalError.java Mon Jul 18 23:53:12 2016 +0300
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2016, 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.sun.tools.doclets.internal.toolkit.util;
+
+/**
+ *
This is NOT part of any supported API.
+ * If you write code that depends on this, you do so at your own risk.
+ * This code and its internal interfaces are subject to change or
+ * deletion without notice.
+ */
+@Deprecated
+public class FatalError extends Error {
+ private static final long serialVersionUID = -9131058909576418984L;
+
+ public FatalError() { }
+}
diff -r cfd609c461fa -r 7d5a50793b6d src/share/classes/com/sun/tools/javac/parser/Parser.java
--- a/src/share/classes/com/sun/tools/javac/parser/Parser.java Wed Nov 23 00:35:47 2016 -0800
+++ b/src/share/classes/com/sun/tools/javac/parser/Parser.java Mon Jul 18 23:53:12 2016 +0300
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2016, 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
@@ -435,7 +435,7 @@
/**
* Ident = IDENTIFIER
*/
- Name ident() {
+ public Name ident() {
if (S.token() == IDENTIFIER) {
Name name = S.name();
S.nextToken();
diff -r cfd609c461fa -r 7d5a50793b6d src/share/classes/com/sun/tools/javadoc/DocEnv.java
--- a/src/share/classes/com/sun/tools/javadoc/DocEnv.java Wed Nov 23 00:35:47 2016 -0800
+++ b/src/share/classes/com/sun/tools/javadoc/DocEnv.java Mon Jul 18 23:53:12 2016 +0300
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2016, 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
@@ -76,7 +76,7 @@
JavadocEnter enter;
/** The name table. */
- Name.Table names;
+ private final Name.Table names;
/** The encoding name. */
private String encoding;
@@ -97,6 +97,7 @@
Check chk;
Types types;
JavaFileManager fileManager;
+ JavaScriptScanner javaScriptScanner;
/** Allow documenting from class files? */
boolean docClasses = false;
@@ -765,4 +766,13 @@
result |= Modifier.VOLATILE;
return result;
}
+
+ JavaScriptScanner initJavaScriptScanner(boolean allowScriptInComments) {
+ if (allowScriptInComments) {
+ javaScriptScanner = null;
+ } else {
+ javaScriptScanner = new JavaScriptScanner();
+ }
+ return javaScriptScanner;
+ }
}
diff -r cfd609c461fa -r 7d5a50793b6d src/share/classes/com/sun/tools/javadoc/DocImpl.java
--- a/src/share/classes/com/sun/tools/javadoc/DocImpl.java Wed Nov 23 00:35:47 2016 -0800
+++ b/src/share/classes/com/sun/tools/javadoc/DocImpl.java Mon Jul 18 23:53:12 2016 +0300
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2016, 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
@@ -32,6 +32,7 @@
import com.sun.javadoc.*;
+import com.sun.tools.doclets.internal.toolkit.util.FatalError;
import com.sun.tools.javac.util.Position;
/**
@@ -99,7 +100,17 @@
*/
Comment comment() {
if (comment == null) {
- comment = new Comment(this, documentation());
+ String d = documentation();
+ if (env.javaScriptScanner != null) {
+ env.javaScriptScanner.parse(d, new JavaScriptScanner.Reporter() {
+ @Override
+ public void report() {
+ env.error(DocImpl.this, "javadoc.JavaScript_in_comment");
+ throw new FatalError();
+ }
+ });
+ }
+ comment = new Comment(this, d);
}
return comment;
}
diff -r cfd609c461fa -r 7d5a50793b6d src/share/classes/com/sun/tools/javadoc/JavaScriptScanner.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/tools/javadoc/JavaScriptScanner.java Mon Jul 18 23:53:12 2016 +0300
@@ -0,0 +1,1103 @@
+/*
+ * Copyright (c) 2016, 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.sun.tools.javadoc;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import com.sun.tools.javadoc.JavaScriptScanner.TagParser.Kind;
+
+import static com.sun.tools.javac.util.LayoutCharacters.EOI;
+
+/**
+ * Parser to detect use of JavaScript in documentation comments.
+ */
+@Deprecated
+public class JavaScriptScanner {
+ public static interface Reporter {
+ void report();
+ }
+
+ static class ParseException extends Exception {
+ private static final long serialVersionUID = 0;
+ ParseException(String key) {
+ super(key);
+ }
+ }
+
+ private Reporter reporter;
+
+ /** The input buffer, index of most recent character read,
+ * index of one past last character in buffer.
+ */
+ protected char[] buf;
+ protected int bp;
+ protected int buflen;
+
+ /** The current character.
+ */
+ protected char ch;
+
+ private boolean newline = true;
+
+ Map tagParsers;
+ Set eventAttrs;
+ Set uriAttrs;
+
+ public JavaScriptScanner() {
+ initTagParsers();
+ initEventAttrs();
+ initURIAttrs();
+ }
+
+ public void parse(String comment, Reporter r) {
+ reporter = r;
+ String c = comment;
+ buf = new char[c.length() + 1];
+ c.getChars(0, c.length(), buf, 0);
+ buf[buf.length - 1] = EOI;
+ buflen = buf.length - 1;
+ bp = -1;
+ newline = true;
+ nextChar();
+
+ blockContent();
+ blockTags();
+ }
+
+ private void checkHtmlTag(String tag) {
+ if (tag.equalsIgnoreCase("script")) {
+ reporter.report();
+ }
+ }
+
+ private void checkHtmlAttr(String name, String value) {
+ String n = name.toLowerCase(Locale.ENGLISH);
+ if (eventAttrs.contains(n)
+ || uriAttrs.contains(n)
+ && value != null && value.toLowerCase(Locale.ENGLISH).trim().startsWith("javascript:")) {
+ reporter.report();
+ }
+ }
+
+ void nextChar() {
+ ch = buf[bp < buflen ? ++bp : buflen];
+ switch (ch) {
+ case '\f': case '\n': case '\r':
+ newline = true;
+ }
+ }
+
+ /**
+ * Read block content, consisting of text, html and inline tags.
+ * Terminated by the end of input, or the beginning of the next block tag:
+ * i.e. @ as the first non-whitespace character on a line.
+ */
+ @SuppressWarnings("fallthrough")
+ protected void blockContent() {
+
+ loop:
+ while (bp < buflen) {
+ switch (ch) {
+ case '\n': case '\r': case '\f':
+ newline = true;
+ // fallthrough
+
+ case ' ': case '\t':
+ nextChar();
+ break;
+
+ case '&':
+ entity(null);
+ break;
+
+ case '<':
+ html();
+ break;
+
+ case '>':
+ newline = false;
+ nextChar();
+ break;
+
+ case '{':
+ inlineTag(null);
+ break;
+
+ case '@':
+ if (newline) {
+ break loop;
+ }
+ // fallthrough
+
+ default:
+ newline = false;
+ nextChar();
+ }
+ }
+ }
+
+ /**
+ * Read a series of block tags, including their content.
+ * Standard tags parse their content appropriately.
+ * Non-standard tags are represented by {@link UnknownBlockTag}.
+ */
+ protected void blockTags() {
+ while (ch == '@')
+ blockTag();
+ }
+
+ /**
+ * Read a single block tag, including its content.
+ * Standard tags parse their content appropriately.
+ * Non-standard tags are represented by {@link UnknownBlockTag}.
+ */
+ protected void blockTag() {
+ int p = bp;
+ try {
+ nextChar();
+ if (isIdentifierStart(ch)) {
+ String name = readTagName();
+ TagParser tp = tagParsers.get(name);
+ if (tp == null) {
+ blockContent();
+ } else {
+ switch (tp.getKind()) {
+ case BLOCK:
+ tp.parse(p);
+ return;
+ case INLINE:
+ return;
+ }
+ }
+ }
+ blockContent();
+ } catch (ParseException e) {
+ blockContent();
+ }
+ }
+
+ protected void inlineTag(Void list) {
+ newline = false;
+ nextChar();
+ if (ch == '@') {
+ inlineTag();
+ }
+ }
+
+ /**
+ * Read a single inline tag, including its content.
+ * Standard tags parse their content appropriately.
+ * Non-standard tags are represented by {@link UnknownBlockTag}.
+ * Malformed tags may be returned as {@link Erroneous}.
+ */
+ protected void inlineTag() {
+ int p = bp - 1;
+ try {
+ nextChar();
+ if (isIdentifierStart(ch)) {
+ String name = readTagName();
+ TagParser tp = tagParsers.get(name);
+
+ if (tp == null) {
+ skipWhitespace();
+ inlineText(WhitespaceRetentionPolicy.REMOVE_ALL);
+ nextChar();
+ } else {
+ skipWhitespace();
+ if (tp.getKind() == TagParser.Kind.INLINE) {
+ tp.parse(p);
+ } else { // handle block tags (ex: @see) in inline content
+ inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip content
+ nextChar();
+ }
+ }
+ }
+ } catch (ParseException e) {
+ }
+ }
+
+ private static enum WhitespaceRetentionPolicy {
+ RETAIN_ALL,
+ REMOVE_FIRST_SPACE,
+ REMOVE_ALL
+ }
+
+ /**
+ * Read plain text content of an inline tag.
+ * Matching pairs of { } are skipped; the text is terminated by the first
+ * unmatched }. It is an error if the beginning of the next tag is detected.
+ */
+ private void inlineText(WhitespaceRetentionPolicy whitespacePolicy) throws ParseException {
+ switch (whitespacePolicy) {
+ case REMOVE_ALL:
+ skipWhitespace();
+ break;
+ case REMOVE_FIRST_SPACE:
+ if (ch == ' ')
+ nextChar();
+ break;
+ case RETAIN_ALL:
+ default:
+ // do nothing
+ break;
+
+ }
+ int pos = bp;
+ int depth = 1;
+
+ loop:
+ while (bp < buflen) {
+ switch (ch) {
+ case '\n': case '\r': case '\f':
+ newline = true;
+ break;
+
+ case ' ': case '\t':
+ break;
+
+ case '{':
+ newline = false;
+ depth++;
+ break;
+
+ case '}':
+ if (--depth == 0) {
+ return;
+ }
+ newline = false;
+ break;
+
+ case '@':
+ if (newline)
+ break loop;
+ newline = false;
+ break;
+
+ default:
+ newline = false;
+ break;
+ }
+ nextChar();
+ }
+ throw new ParseException("dc.unterminated.inline.tag");
+ }
+
+ /**
+ * Read Java class name, possibly followed by member
+ * Matching pairs of {@literal < >} are skipped. The text is terminated by the first
+ * unmatched }. It is an error if the beginning of the next tag is detected.
+ */
+ // TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE
+ // TODO: improve quality of parse to forbid bad constructions.
+ // TODO: update to use ReferenceParser
+ @SuppressWarnings("fallthrough")
+ protected void reference(boolean allowMember) throws ParseException {
+ int pos = bp;
+ int depth = 0;
+
+ // scan to find the end of the signature, by looking for the first
+ // whitespace not enclosed in () or <>, or the end of the tag
+ loop:
+ while (bp < buflen) {
+ switch (ch) {
+ case '\n': case '\r': case '\f':
+ newline = true;
+ // fallthrough
+
+ case ' ': case '\t':
+ if (depth == 0)
+ break loop;
+ break;
+
+ case '(':
+ case '<':
+ newline = false;
+ depth++;
+ break;
+
+ case ')':
+ case '>':
+ newline = false;
+ --depth;
+ break;
+
+ case '}':
+ if (bp == pos)
+ return;
+ newline = false;
+ break loop;
+
+ case '@':
+ if (newline)
+ break loop;
+ // fallthrough
+
+ default:
+ newline = false;
+
+ }
+ nextChar();
+ }
+
+ if (depth != 0)
+ throw new ParseException("dc.unterminated.signature");
+ }
+
+ /**
+ * Read Java identifier
+ * Matching pairs of { } are skipped; the text is terminated by the first
+ * unmatched }. It is an error if the beginning of the next tag is detected.
+ */
+ @SuppressWarnings("fallthrough")
+ protected void identifier() throws ParseException {
+ skipWhitespace();
+ int pos = bp;
+
+ if (isJavaIdentifierStart(ch)) {
+ readJavaIdentifier();
+ return;
+ }
+
+ throw new ParseException("dc.identifier.expected");
+ }
+
+ /**
+ * Read a quoted string.
+ * It is an error if the beginning of the next tag is detected.
+ */
+ @SuppressWarnings("fallthrough")
+ protected void quotedString() {
+ int pos = bp;
+ nextChar();
+
+ loop:
+ while (bp < buflen) {
+ switch (ch) {
+ case '\n': case '\r': case '\f':
+ newline = true;
+ break;
+
+ case ' ': case '\t':
+ break;
+
+ case '"':
+ nextChar();
+ // trim trailing white-space?
+ return;
+
+ case '@':
+ if (newline)
+ break loop;
+
+ }
+ nextChar();
+ }
+ }
+
+ /**
+ * Read a term ie. one word.
+ * It is an error if the beginning of the next tag is detected.
+ */
+ @SuppressWarnings("fallthrough")
+ protected void inlineWord() {
+ int pos = bp;
+ int depth = 0;
+ loop:
+ while (bp < buflen) {
+ switch (ch) {
+ case '\n':
+ newline = true;
+ // fallthrough
+
+ case '\r': case '\f': case ' ': case '\t':
+ return;
+
+ case '@':
+ if (newline)
+ break loop;
+
+ case '{':
+ depth++;
+ break;
+
+ case '}':
+ if (depth == 0 || --depth == 0)
+ return;
+ break;
+ }
+ newline = false;
+ nextChar();
+ }
+ }
+
+ /**
+ * Read general text content of an inline tag, including HTML entities and elements.
+ * Matching pairs of { } are skipped; the text is terminated by the first
+ * unmatched }. It is an error if the beginning of the next tag is detected.
+ */
+ @SuppressWarnings("fallthrough")
+ private void inlineContent() {
+
+ skipWhitespace();
+ int pos = bp;
+ int depth = 1;
+
+ loop:
+ while (bp < buflen) {
+
+ switch (ch) {
+ case '\n': case '\r': case '\f':
+ newline = true;
+ // fall through
+
+ case ' ': case '\t':
+ nextChar();
+ break;
+
+ case '&':
+ entity(null);
+ break;
+
+ case '<':
+ newline = false;
+ html();
+ break;
+
+ case '{':
+ newline = false;
+ depth++;
+ nextChar();
+ break;
+
+ case '}':
+ newline = false;
+ if (--depth == 0) {
+ nextChar();
+ return;
+ }
+ nextChar();
+ break;
+
+ case '@':
+ if (newline)
+ break loop;
+ // fallthrough
+
+ default:
+ nextChar();
+ break;
+ }
+ }
+
+ }
+
+ protected void entity(Void list) {
+ newline = false;
+ entity();
+ }
+
+ /**
+ * Read an HTML entity.
+ * {@literal &identifier; } or {@literal digits; } or {@literal hex-digits; }
+ */
+ protected void entity() {
+ nextChar();
+ String name = null;
+ if (ch == '#') {
+ int namep = bp;
+ nextChar();
+ if (isDecimalDigit(ch)) {
+ nextChar();
+ while (isDecimalDigit(ch))
+ nextChar();
+ name = new String(buf, namep, bp - namep);
+ } else if (ch == 'x' || ch == 'X') {
+ nextChar();
+ if (isHexDigit(ch)) {
+ nextChar();
+ while (isHexDigit(ch))
+ nextChar();
+ name = new String(buf, namep, bp - namep);
+ }
+ }
+ } else if (isIdentifierStart(ch)) {
+ name = readIdentifier();
+ }
+
+ if (name != null) {
+ if (ch != ';')
+ return;
+ nextChar();
+ }
+ }
+
+ /**
+ * Read the start or end of an HTML tag, or an HTML comment
+ * {@literal } or {@literal }
+ */
+ protected void html() {
+ int p = bp;
+ nextChar();
+ if (isIdentifierStart(ch)) {
+ String name = readIdentifier();
+ checkHtmlTag(name);
+ htmlAttrs();
+ if (ch == '/') {
+ nextChar();
+ }
+ if (ch == '>') {
+ nextChar();
+ return;
+ }
+ } else if (ch == '/') {
+ nextChar();
+ if (isIdentifierStart(ch)) {
+ readIdentifier();
+ skipWhitespace();
+ if (ch == '>') {
+ nextChar();
+ return;
+ }
+ }
+ } else if (ch == '!') {
+ nextChar();
+ if (ch == '-') {
+ nextChar();
+ if (ch == '-') {
+ nextChar();
+ while (bp < buflen) {
+ int dash = 0;
+ while (ch == '-') {
+ dash++;
+ nextChar();
+ }
+ // Strictly speaking, a comment should not contain "--"
+ // so dash > 2 is an error, dash == 2 implies ch == '>'
+ // See http://www.w3.org/TR/html-markup/syntax.html#syntax-comments
+ // for more details.
+ if (dash >= 2 && ch == '>') {
+ nextChar();
+ return;
+ }
+
+ nextChar();
+ }
+ }
+ }
+ }
+
+ bp = p + 1;
+ ch = buf[bp];
+ }
+
+ /**
+ * Read a series of HTML attributes, terminated by {@literal > }.
+ * Each attribute is of the form {@literal identifier[=value] }.
+ * "value" may be unquoted, single-quoted, or double-quoted.
+ */
+ protected void htmlAttrs() {
+ skipWhitespace();
+
+ loop:
+ while (isIdentifierStart(ch)) {
+ int namePos = bp;
+ String name = readAttributeName();
+ skipWhitespace();
+ StringBuilder value = new StringBuilder();
+ if (ch == '=') {
+ nextChar();
+ skipWhitespace();
+ if (ch == '\'' || ch == '"') {
+ char quote = ch;
+ nextChar();
+ while (bp < buflen && ch != quote) {
+ if (newline && ch == '@') {
+ // No point trying to read more.
+ // In fact, all attrs get discarded by the caller
+ // and superseded by a malformed.html node because
+ // the html tag itself is not terminated correctly.
+ break loop;
+ }
+ value.append(ch);
+ nextChar();
+ }
+ nextChar();
+ } else {
+ while (bp < buflen && !isUnquotedAttrValueTerminator(ch)) {
+ value.append(ch);
+ nextChar();
+ }
+ }
+ skipWhitespace();
+ }
+ checkHtmlAttr(name, value.toString());
+ }
+ }
+
+ protected void attrValueChar(Void list) {
+ switch (ch) {
+ case '&':
+ entity(list);
+ break;
+
+ case '{':
+ inlineTag(list);
+ break;
+
+ default:
+ nextChar();
+ }
+ }
+
+ protected boolean isIdentifierStart(char ch) {
+ return Character.isUnicodeIdentifierStart(ch);
+ }
+
+ protected String readIdentifier() {
+ int start = bp;
+ nextChar();
+ while (bp < buflen && Character.isUnicodeIdentifierPart(ch))
+ nextChar();
+ return new String(buf, start, bp - start);
+ }
+
+ protected String readAttributeName() {
+ int start = bp;
+ nextChar();
+ while (bp < buflen && (Character.isUnicodeIdentifierPart(ch) || ch == '-'))
+ nextChar();
+ return new String(buf, start, bp - start);
+ }
+
+ protected String readTagName() {
+ int start = bp;
+ nextChar();
+ while (bp < buflen
+ && (Character.isUnicodeIdentifierPart(ch) || ch == '.'
+ || ch == '-' || ch == ':')) {
+ nextChar();
+ }
+ return new String(buf, start, bp - start);
+ }
+
+ protected boolean isJavaIdentifierStart(char ch) {
+ return Character.isJavaIdentifierStart(ch);
+ }
+
+ protected String readJavaIdentifier() {
+ int start = bp;
+ nextChar();
+ while (bp < buflen && Character.isJavaIdentifierPart(ch))
+ nextChar();
+ return new String(buf, start, bp - start);
+ }
+
+ protected boolean isDecimalDigit(char ch) {
+ return ('0' <= ch && ch <= '9');
+ }
+
+ protected boolean isHexDigit(char ch) {
+ return ('0' <= ch && ch <= '9')
+ || ('a' <= ch && ch <= 'f')
+ || ('A' <= ch && ch <= 'F');
+ }
+
+ protected boolean isUnquotedAttrValueTerminator(char ch) {
+ switch (ch) {
+ case '\f': case '\n': case '\r': case '\t':
+ case ' ':
+ case '"': case '\'': case '`':
+ case '=': case '<': case '>':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ protected boolean isWhitespace(char ch) {
+ return Character.isWhitespace(ch);
+ }
+
+ protected void skipWhitespace() {
+ while (isWhitespace(ch)) {
+ nextChar();
+ }
+ }
+
+ /**
+ * @param start position of first character of string
+ * @param end position of character beyond last character to be included
+ */
+ String newString(int start, int end) {
+ return new String(buf, start, end - start);
+ }
+
+ static abstract class TagParser {
+ enum Kind { INLINE, BLOCK }
+
+ final Kind kind;
+ final String name;
+
+
+ TagParser(Kind k, String tk) {
+ kind = k;
+ name = tk;
+ }
+
+ TagParser(Kind k, String tk, boolean retainWhiteSpace) {
+ this(k, tk);
+ }
+
+ Kind getKind() {
+ return kind;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ abstract void parse(int pos) throws ParseException;
+ }
+
+ /**
+ * @see Javadoc Tags
+ */
+ @SuppressWarnings("deprecation")
+ private void initTagParsers() {
+ TagParser[] parsers = {
+ // @author name-text
+ new TagParser(Kind.BLOCK, "author") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // {@code text}
+ new TagParser(Kind.INLINE, "code", true) {
+ @Override
+ public void parse(int pos) throws ParseException {
+ inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
+ nextChar();
+ }
+ },
+
+ // @deprecated deprecated-text
+ new TagParser(Kind.BLOCK, "deprecated") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // {@docRoot}
+ new TagParser(Kind.INLINE, "docRoot") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ if (ch == '}') {
+ nextChar();
+ return;
+ }
+ inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
+ nextChar();
+ throw new ParseException("dc.unexpected.content");
+ }
+ },
+
+ // @exception class-name description
+ new TagParser(Kind.BLOCK, "exception") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ skipWhitespace();
+ reference(false);
+ blockContent();
+ }
+ },
+
+ // @hidden hidden-text
+ new TagParser(Kind.BLOCK, "hidden") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // @index search-term options-description
+ new TagParser(Kind.INLINE, "index") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ skipWhitespace();
+ if (ch == '}') {
+ throw new ParseException("dc.no.content");
+ }
+ if (ch == '"') quotedString(); else inlineWord();
+ skipWhitespace();
+ if (ch != '}') {
+ inlineContent();
+ } else {
+ nextChar();
+ }
+ }
+ },
+
+ // {@inheritDoc}
+ new TagParser(Kind.INLINE, "inheritDoc") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ if (ch == '}') {
+ nextChar();
+ return;
+ }
+ inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
+ nextChar();
+ throw new ParseException("dc.unexpected.content");
+ }
+ },
+
+ // {@link package.class#member label}
+ new TagParser(Kind.INLINE, "link") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ reference(true);
+ inlineContent();
+ }
+ },
+
+ // {@linkplain package.class#member label}
+ new TagParser(Kind.INLINE, "linkplain") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ reference(true);
+ inlineContent();
+ }
+ },
+
+ // {@literal text}
+ new TagParser(Kind.INLINE, "literal", true) {
+ @Override
+ public void parse(int pos) throws ParseException {
+ inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
+ nextChar();
+ }
+ },
+
+ // @param parameter-name description
+ new TagParser(Kind.BLOCK, "param") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ skipWhitespace();
+
+ boolean typaram = false;
+ if (ch == '<') {
+ typaram = true;
+ nextChar();
+ }
+
+ identifier();
+
+ if (typaram) {
+ if (ch != '>')
+ throw new ParseException("dc.gt.expected");
+ nextChar();
+ }
+
+ skipWhitespace();
+ blockContent();
+ }
+ },
+
+ // @return description
+ new TagParser(Kind.BLOCK, "return") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // @see reference | quoted-string | HTML
+ new TagParser(Kind.BLOCK, "see") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ skipWhitespace();
+ switch (ch) {
+ case '"':
+ quotedString();
+ skipWhitespace();
+ if (ch == '@'
+ || ch == EOI && bp == buf.length - 1) {
+ return;
+ }
+ break;
+
+ case '<':
+ blockContent();
+ return;
+
+ case '@':
+ if (newline)
+ throw new ParseException("dc.no.content");
+ break;
+
+ case EOI:
+ if (bp == buf.length - 1)
+ throw new ParseException("dc.no.content");
+ break;
+
+ default:
+ if (isJavaIdentifierStart(ch) || ch == '#') {
+ reference(true);
+ blockContent();
+ }
+ }
+ throw new ParseException("dc.unexpected.content");
+ }
+ },
+
+ // @serialData data-description
+ new TagParser(Kind.BLOCK, "@serialData") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // @serialField field-name field-type description
+ new TagParser(Kind.BLOCK, "serialField") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ skipWhitespace();
+ identifier();
+ skipWhitespace();
+ reference(false);
+ if (isWhitespace(ch)) {
+ skipWhitespace();
+ blockContent();
+ }
+ }
+ },
+
+ // @serial field-description | include | exclude
+ new TagParser(Kind.BLOCK, "serial") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // @since since-text
+ new TagParser(Kind.BLOCK, "since") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // @throws class-name description
+ new TagParser(Kind.BLOCK, "throws") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ skipWhitespace();
+ reference(false);
+ blockContent();
+ }
+ },
+
+ // {@value package.class#field}
+ new TagParser(Kind.INLINE, "value") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ reference(true);
+ skipWhitespace();
+ if (ch == '}') {
+ nextChar();
+ return;
+ }
+ nextChar();
+ throw new ParseException("dc.unexpected.content");
+ }
+ },
+
+ // @version version-text
+ new TagParser(Kind.BLOCK, "version") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+ };
+
+ tagParsers = new HashMap();
+ for (TagParser p: parsers)
+ tagParsers.put(p.getName(), p);
+
+ }
+
+ private void initEventAttrs() {
+ eventAttrs = new HashSet(Arrays.asList(
+ // See https://www.w3.org/TR/html-markup/global-attributes.html#common.attrs.event-handler
+ "onabort", "onblur", "oncanplay", "oncanplaythrough",
+ "onchange", "onclick", "oncontextmenu", "ondblclick",
+ "ondrag", "ondragend", "ondragenter", "ondragleave",
+ "ondragover", "ondragstart", "ondrop", "ondurationchange",
+ "onemptied", "onended", "onerror", "onfocus", "oninput",
+ "oninvalid", "onkeydown", "onkeypress", "onkeyup",
+ "onload", "onloadeddata", "onloadedmetadata", "onloadstart",
+ "onmousedown", "onmousemove", "onmouseout", "onmouseover",
+ "onmouseup", "onmousewheel", "onpause", "onplay",
+ "onplaying", "onprogress", "onratechange", "onreadystatechange",
+ "onreset", "onscroll", "onseeked", "onseeking",
+ "onselect", "onshow", "onstalled", "onsubmit", "onsuspend",
+ "ontimeupdate", "onvolumechange", "onwaiting",
+
+ // See https://www.w3.org/TR/html4/sgml/dtd.html
+ // Most of the attributes that take a %Script are also defined as event handlers
+ // in HTML 5. The one exception is onunload.
+ // "onchange", "onclick", "ondblclick", "onfocus",
+ // "onkeydown", "onkeypress", "onkeyup", "onload",
+ // "onmousedown", "onmousemove", "onmouseout", "onmouseover",
+ // "onmouseup", "onreset", "onselect", "onsubmit",
+ "onunload"
+ ));
+ }
+
+ private void initURIAttrs() {
+ uriAttrs = new HashSet(Arrays.asList(
+ // See https://www.w3.org/TR/html4/sgml/dtd.html
+ // https://www.w3.org/TR/html5/
+ // These are all the attributes that take a %URI or a valid URL potentially surrounded
+ // by spaces
+ "action", "cite", "classid", "codebase", "data",
+ "datasrc", "for", "href", "longdesc", "profile",
+ "src", "usemap"
+ ));
+ }
+
+}
diff -r cfd609c461fa -r 7d5a50793b6d src/share/classes/com/sun/tools/javadoc/RootDocImpl.java
--- a/src/share/classes/com/sun/tools/javadoc/RootDocImpl.java Wed Nov 23 00:35:47 2016 -0800
+++ b/src/share/classes/com/sun/tools/javadoc/RootDocImpl.java Mon Jul 18 23:53:12 2016 +0300
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2016, 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
@@ -361,4 +361,8 @@
public Locale getLocale() {
return env.doclocale.locale;
}
+
+ public JavaScriptScanner initJavaScriptScanner(boolean allowScriptInComments) {
+ return env.initJavaScriptScanner(allowScriptInComments);
+ }
}
diff -r cfd609c461fa -r 7d5a50793b6d src/share/classes/com/sun/tools/javadoc/resources/javadoc.properties
--- a/src/share/classes/com/sun/tools/javadoc/resources/javadoc.properties Wed Nov 23 00:35:47 2016 -0800
+++ b/src/share/classes/com/sun/tools/javadoc/resources/javadoc.properties Mon Jul 18 23:53:12 2016 +0300
@@ -1,5 +1,5 @@
#
-# Copyright (c) 1997, 2004, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 1997, 2016, 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
@@ -101,6 +101,8 @@
javadoc.Body_missing_from_html_file=Body tag missing from HTML
javadoc.End_body_missing_from_html_file=Close body tag missing from HTML file
javadoc.Multiple_package_comments=Multiple sources of package comments found for package "{0}"
+javadoc.JavaScript_in_comment=JavaScript found in documentation comment.\n\
+ Use --allow-script-in-comments to allow use of JavaScript.
javadoc.class_not_found=Class {0} not found.
javadoc.error=error
javadoc.warning=warning
diff -r cfd609c461fa -r 7d5a50793b6d test/tools/javadoc/TestScriptInComment.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javadoc/TestScriptInComment.java Mon Jul 18 23:53:12 2016 +0300
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2016, 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+/**
+ * @test
+ * @bug 8138725
+ * @summary test --allow-script-in-comments
+ * @run main TestScriptInComment
+ */
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.RandomAccessFile;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Combo-style test, exercising combinations of different HTML fragments that may contain
+ * JavaScript, different places to place those fragments, and whether or not to allow the use
+ * of JavaScript.
+ */
+public class TestScriptInComment {
+ public static void main(String... args) throws Exception {
+ new TestScriptInComment().run();
+ }
+
+ /**
+ * Representative samples of different fragments of HTML that may contain JavaScript.
+ * To facilitate checking the output manually in a browser, the text "#ALERT" will be
+ * replaced by a JavaScript call of "alert(msg)", using a message string that is specific
+ * to the test case.
+ */
+ enum Comment {
+ LC("", true), // script tag in Lower Case
+ UC("", true), // script tag in Upper Case
+ WS("< script >#ALERT", false, "-Xdoclint:none"), // script tag with invalid white space
+ SA("", true), // script tag with an attribute
+ ON("x", true), // event handler attribute
+ URI("x", true); // javadcript URI
+
+ /**
+ * Creates an HTML fragment to be injected into a template.
+ * @param text the HTML fragment to put into a doc comment or option.
+ * @param hasScript whether or not this fragment does contain legal JavaScript
+ * @param opts any additional options to be specified when javadoc is run
+ */
+ Comment(String text, boolean hasScript, String... opts) {
+ this.text = text;
+ this.hasScript = hasScript;
+ this.opts = Arrays.asList(opts);
+ }
+
+ final String text;
+ final boolean hasScript;
+ final List opts;
+ };
+
+ /**
+ * Representative samples of positions in which javadoc may find JavaScript.
+ * Each template contains a series of strings, which are written to files or inferred as options.
+ * The first source file implies a corresponding output file which should not be written
+ * if the comment contains JavaScript and JavaScript is not allowed.
+ */
+ enum Template {
+ OVR(" overview #COMMENT ", "package p; public class C { }"),
+ PKGINFO("#COMMENT package p;", "package p; public class C { }"),
+ PKGHTML("#COMMENT package p;", "package p; public class C { }"),
+ CLS("package p; #COMMENT public class C { }"),
+ CON("package p; public class C { #COMMENT public C() { } }"),
+ FLD("package p; public class C { #COMMENT public int f; }"),
+ MTH("package p; public class C { #COMMENT public void m() { } }"),
+ TOP("-top", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ HDR("-header", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ FTR("-footer", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ BTM("-bottom", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ DTTL("-doctitle", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ PHDR("-packagesheader", "lorem #COMMENT ipsum", "package p; public class C { }");
+
+ Template(String... args) {
+ opts = new ArrayList();
+ sources = new ArrayList();
+ int i = 0;
+ while (args[i].startsWith("-")) {
+ // all options being tested have a single argument that follow the option
+ opts.add(args[i++]);
+ opts.add(args[i++]);
+ }
+ while(i < args.length) {
+ sources.add(args[i++]);
+ }
+ }
+
+ // groups: 1 or not; 2: package name; 3: class name
+ private final Pattern pat =
+ Pattern.compile("(?i)()?.*?(?:package ([a-z]+);.*?(?:class ([a-z]+).*)?)?");
+
+ /**
+ * Infer the file in which to write the given source.
+ * @param dir the base source directory
+ * @param src the source text
+ * @return the file in which the source should be written
+ */
+ File getSrcFile(File srcDir, String src) {
+ String f;
+ Matcher m = pat.matcher(src);
+ if (!m.matches())
+ throw new Error("match failed");
+ if (m.group(3) != null) {
+ f = m.group(2) + "/" + m.group(3) + ".java";
+ } else if (m.group(2) != null) {
+ f = m.group(2) + "/" + (m.group(1) == null ? "package-info.java" : "package.html");
+ } else {
+ f = "overview.html";
+ }
+ return new File(srcDir, f);
+ }
+
+ /**
+ * Get the options to give to javadoc.
+ * @param srcDir the srcDir to use -overview is needed
+ * @return
+ */
+ List getOpts(File srcDir) {
+ if (!opts.isEmpty()) {
+ return opts;
+ } else if (sources.get(0).contains("overview")) {
+ return Arrays.asList("-overview", getSrcFile(srcDir, sources.get(0)).getPath());
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Gets the output file corresponding to the first source file.
+ * This file should not be written if the comment contains JavaScript and JavaScripot is
+ * not allowed.
+ * @param dir the base output directory
+ * @return the output file
+ */
+ File getOutFile(File outDir) {
+ String f;
+ Matcher m = pat.matcher(sources.get(0));
+ if (!m.matches())
+ throw new Error("match failed");
+ if (m.group(3) != null) {
+ f = m.group(2) + "/" + m.group(3) + ".html";
+ } else if (m.group(2) != null) {
+ f = m.group(2) + "/package-summary.html";
+ } else {
+ f = "overview-summary.html";
+ }
+ return new File(outDir, f);
+ }
+
+ final List opts;
+ final List sources;
+ };
+
+ enum Option {
+ OFF(null),
+ ON("--allow-script-in-comments");
+
+ Option(String text) {
+ this.text = text;
+ }
+
+ final String text;
+ };
+
+ private PrintStream out = System.err;
+
+ public void run() throws Exception {
+ int count = 0;
+ for (Template template: Template.values()) {
+ for (Comment comment: Comment.values()) {
+ for (Option option: Option.values()) {
+ if (test(template, comment, option)) {
+ count++;
+ }
+ }
+ }
+ }
+
+ out.println(count + " test cases run");
+ if (errors > 0) {
+ throw new Exception(errors + " errors occurred");
+ }
+ }
+
+ boolean test(Template template, Comment comment, Option option) throws IOException {
+ if (option == Option.ON && !comment.hasScript) {
+ // skip --allowScriptInComments if comment does not contain JavaScript
+ return false;
+ }
+
+ String test = template + "-" + comment + "-" + option;
+ out.println("Test: " + test);
+
+ File dir = new File(test);
+ dir.mkdirs();
+ File srcDir = new File(dir, "src");
+ File outDir = new File(dir, "out");
+
+ String alert = "alert(\"" + test + "\");";
+ for (String src: template.sources) {
+ writeFile(template.getSrcFile(srcDir, src),
+ src.replace("#COMMENT",
+ "/** " + comment.text.replace("#ALERT", alert) + " **/"));
+ }
+
+ List opts = new ArrayList();
+ opts.add("-sourcepath");
+ opts.add(srcDir.getPath());
+ opts.add("-d");
+ opts.add(outDir.getPath());
+ if (option.text != null)
+ opts.add(option.text);
+ for (String opt: template.getOpts(srcDir)) {
+ opts.add(opt.replace("#COMMENT", comment.text.replace("#ALERT", alert)));
+ }
+ opts.addAll(comment.opts);
+ opts.add("-noindex"); // index not required; save time/space writing files
+ opts.add("p");
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ int rc = javadoc(opts, pw);
+ pw.close();
+ String log = sw.toString();
+ writeFile(new File(dir, "log.txt"), log);
+
+ out.println("opts: " + opts);
+ out.println(" rc: " + rc);
+ out.println(" log:");
+ out.println(log);
+
+ String ERROR = "Use --allow-script-in-comment";
+ File outFile = template.getOutFile(outDir);
+
+ boolean expectErrors = comment.hasScript && (option == Option.OFF);
+
+ if (expectErrors) {
+ check(rc != 0, "unexpected exit code: " + rc);
+ check(log.contains(ERROR), "expected error message not found");
+ check(!existsAndNotEmpty(outFile), "output file found unexpectedly");
+ } else {
+ check(rc == 0, "unexpected exit code: " + rc);
+ check(!log.contains(ERROR), "error message found");
+ check(outFile.exists(), "output file not found");
+ }
+
+ out.println();
+ return true;
+ }
+
+ int javadoc(List opts, PrintWriter pw) {
+ return com.sun.tools.javadoc.Main.execute("javadoc", pw, pw, pw,
+ "com.sun.tools.doclets.standard.Standard", opts.toArray(new String[opts.size()]));
+ }
+
+ File writeFile(File f, String text) throws IOException {
+ f.getParentFile().mkdirs();
+ FileWriter fw = new FileWriter(f);
+ try {
+ fw.write(text);
+ } finally {
+ fw.close();
+ }
+ return f;
+ }
+
+ void check(boolean cond, String errMessage) {
+ if (!cond) {
+ error(errMessage);
+ }
+ }
+
+ void error(String message) {
+ out.println("Error: " + message);
+ errors++;
+ }
+
+ int errors = 0;
+
+ boolean existsAndNotEmpty(File f) throws IOException {
+ if (!f.exists()) {
+ return false;
+ }
+ RandomAccessFile raf = new RandomAccessFile(f, "r");
+ return (raf.length() > 0);
+ }
+}
+