changeset 43261:d377e97291d8

8138725: Add options for Javadoc generation Reviewed-by: ksrini, bpatel, ahgross
author jjg
date Tue, 12 Jul 2016 14:41:14 -0700
parents df68602dc422
children 61eeb6ee2289
files langtools/src/jdk.compiler/share/classes/com/sun/source/util/DocTreePath.java langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/Checker.java langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/HtmlTag.java langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/ConfigurationImpl.java langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/HtmlDoclet.java langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/markup/HtmlWriter.java langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/AbstractDoclet.java langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/builders/AbstractBuilder.java langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclets.properties langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/FatalError.java langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/DocEnv.java langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/DocImpl.java langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/JavaScriptScanner.java langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/RootDocImpl.java langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/resources/javadoc.properties langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConfigurationImpl.java langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/AbstractDoclet.java langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/Configuration.java langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/AbstractBuilder.java langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/JavaScriptScanner.java langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/UncheckedDocletException.java langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Messager.java langtools/test/Makefile langtools/test/jdk/javadoc/tool/TestScriptInComment.java langtools/test/tools/doclint/html/OtherTagsTest.out langtools/test/tools/javadoc/TestScriptInComment.java
diffstat 32 files changed, 2165 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/langtools/src/jdk.compiler/share/classes/com/sun/source/util/DocTreePath.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/source/util/DocTreePath.java	Tue Jul 12 14:41:14 2016 -0700
@@ -98,7 +98,7 @@
      * @param t the DocCommentTree to create the path for.
      */
     public DocTreePath(TreePath treePath, DocCommentTree t) {
-        this.treePath = Objects.requireNonNull(treePath);
+        this.treePath = treePath;
         this.docComment = Objects.requireNonNull(t);
         this.parent = null;
         this.leaf = t;
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/Checker.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/Checker.java	Tue Jul 12 14:41:14 2016 -0700
@@ -423,7 +423,16 @@
                 break;
 
             case OTHER:
-                env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);
+                switch (t) {
+                    case SCRIPT:
+                        // <script> may or may not be allowed, depending on --allow-script-in-comments
+                        // but we allow it here, and rely on a separate scanner to detect all uses
+                        // of JavaScript, including <script> tags, and use in attributes, etc.
+                        break;
+
+                    default:
+                        env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);
+                }
                 return;
         }
 
@@ -552,15 +561,19 @@
                 if (!first)
                     env.messages.error(HTML, tree, "dc.attr.repeated", name);
             }
-            AttrKind k = currTag.getAttrKind(name);
-            switch (env.htmlVersion) {
-                case HTML4:
-                    validateHtml4Attrs(tree, name, k);
-                    break;
+            // for now, doclint allows all attribute names beginning with "on" as event handler names,
+            // without checking the validity or applicability of the name
+            if (!name.toString().startsWith("on")) {
+                AttrKind k = currTag.getAttrKind(name);
+                switch (env.htmlVersion) {
+                    case HTML4:
+                        validateHtml4Attrs(tree, name, k);
+                        break;
 
-                case HTML5:
-                    validateHtml5Attrs(tree, name, k);
-                    break;
+                    case HTML5:
+                        validateHtml5Attrs(tree, name, k);
+                        break;
+                }
             }
 
             if (attr != null) {
@@ -722,6 +735,9 @@
     }
 
     private void checkURI(AttributeTree tree, String uri) {
+        // allow URIs beginning with javascript:, which would otherwise be rejected by the URI API.
+        if (uri.startsWith("javascript:"))
+            return;
         try {
             URI u = new URI(uri);
         } catch (URISyntaxException e) {
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/HtmlTag.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/HtmlTag.java	Tue Jul 12 14:41:14 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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
@@ -287,7 +287,8 @@
     SAMP(BlockType.INLINE, EndKind.REQUIRED,
             EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
 
-    SCRIPT(BlockType.OTHER, EndKind.REQUIRED),
+    SCRIPT(BlockType.OTHER, EndKind.REQUIRED,
+            attrs(AttrKind.ALL, SRC)),
 
     SECTION(HtmlVersion.HTML5, BlockType.BLOCK, EndKind.REQUIRED,
             EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)),
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java	Tue Jul 12 14:41:14 2016 -0700
@@ -30,7 +30,6 @@
 import java.util.Map;
 
 import com.sun.source.doctree.AttributeTree.ValueKind;
-import com.sun.source.doctree.DocTree;
 import com.sun.tools.javac.parser.DocCommentParser.TagParser.Kind;
 import com.sun.tools.javac.parser.Tokens.Comment;
 import com.sun.tools.javac.parser.Tokens.TokenKind;
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java	Tue Jul 12 14:41:14 2016 -0700
@@ -588,7 +588,7 @@
     /**
      * Ident = IDENTIFIER
      */
-    protected Name ident() {
+    public Name ident() {
         return ident(false);
     }
 
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/ConfigurationImpl.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/ConfigurationImpl.java	Tue Jul 12 14:41:14 2016 -0700
@@ -40,6 +40,7 @@
 import com.sun.tools.javac.file.JavacFileManager;
 import com.sun.tools.javac.util.Context;
 import com.sun.tools.javac.util.StringUtils;
+import com.sun.tools.javadoc.main.JavaScriptScanner;
 import com.sun.tools.javadoc.main.RootDocImpl;
 
 /**
@@ -189,6 +190,11 @@
     public Set<String> doclintOpts = new LinkedHashSet<>();
 
     /**
+     * Whether or not to check for JavaScript in doc comments.
+     */
+    private boolean allowScriptInComments;
+
+    /**
      * Unique Resource Handler for this package.
      */
     public final MessageRetriever standardmessage;
@@ -309,8 +315,11 @@
                 doclintOpts.add(DocLint.XMSGS_CUSTOM_PREFIX + opt.substring(opt.indexOf(":") + 1));
             } else if (opt.startsWith("-xdoclint/package:")) {
                 doclintOpts.add(DocLint.XCHECK_PACKAGE + opt.substring(opt.indexOf(":") + 1));
+            } else if (opt.equals("--allow-script-in-comments")) {
+                allowScriptInComments = true;
             }
         }
+
         if (root.specifiedClasses().length > 0) {
             Map<String,PackageDoc> map = new HashMap<>();
             PackageDoc pd;
@@ -322,15 +331,37 @@
                 }
             }
         }
+
         setCreateOverview();
         setTopFile(root);
 
         if (root instanceof RootDocImpl) {
             ((RootDocImpl) root).initDocLint(doclintOpts, tagletManager.getCustomTagNames(),
                     StringUtils.toLowerCase(htmlVersion.name()));
+            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();
+            }
+        });
+    }
+
     /**
      * Returns the "length" of a given option. If an option takes no
      * arguments, its length is one. If it takes one argument, it's
@@ -366,7 +397,8 @@
             option.equals("-html5") ||
             option.equals("-xdoclint") ||
             option.startsWith("-xdoclint:") ||
-            option.startsWith("-xdoclint/package:")) {
+            option.startsWith("-xdoclint/package:") ||
+            option.startsWith("--allow-script-in-comments")) {
             return 1;
         } else if (option.equals("-help")) {
             // Uugh: first, this should not be hidden inside optionLength,
@@ -666,4 +698,13 @@
         }
         tagSearchIndexKeys = tagSearchIndexMap.keySet();
     }
+
+    /**
+     * 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;
+    }
 }
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/HtmlDoclet.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/HtmlDoclet.java	Tue Jul 12 14:41:14 2016 -0700
@@ -237,6 +237,8 @@
                 }
             } catch (IOException e) {
                 throw new DocletAbortException(e);
+            } catch (FatalError fe) {
+                throw fe;
             } catch (DocletAbortException de) {
                 de.printStackTrace();
                 throw de;
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/markup/HtmlWriter.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/markup/HtmlWriter.java	Tue Jul 12 14:41:14 2016 -0700
@@ -148,7 +148,9 @@
 
     public final Content descfrmInterfaceLabel;
 
-    private final Writer writer;
+    private final DocFile file;
+
+    private Writer writer;
 
     protected Content script;
 
@@ -164,7 +166,7 @@
      */
     public HtmlWriter(Configuration configuration, DocPath path)
             throws IOException, UnsupportedEncodingException {
-        writer = DocFile.createFileForOutput(configuration, path).openWriter();
+        file = DocFile.createFileForOutput(configuration, path);
         this.configuration = configuration;
         this.memberDetailsListPrinted = false;
         packageTableHeader = new String[] {
@@ -214,6 +216,7 @@
     }
 
     public void write(Content c) throws IOException {
+        writer = file.openWriter();
         c.write(writer, true);
     }
 
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/AbstractDoclet.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/AbstractDoclet.java	Tue Jul 12 14:41:14 2016 -0700
@@ -90,6 +90,8 @@
         } catch (Configuration.Fault f) {
             root.printError(f.getMessage());
             return false;
+        } catch (FatalError fe) {
+            return false;
         } catch (DocletAbortException e) {
             e.printStackTrace();
             Throwable cause = e.getCause();
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/builders/AbstractBuilder.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/builders/AbstractBuilder.java	Tue Jul 12 14:41:14 2016 -0700
@@ -145,7 +145,14 @@
             configuration.root.printError("Unknown element: " + component);
             throw new DocletAbortException(e);
         } catch (InvocationTargetException e) {
-            throw new DocletAbortException(e.getCause());
+            Throwable cause = e.getCause();
+            if (cause instanceof FatalError) {
+                throw (FatalError) cause;
+            } else if (cause instanceof DocletAbortException) {
+                throw (DocletAbortException) cause;
+            } else {
+                throw new DocletAbortException(cause);
+            }
         } catch (Exception e) {
             e.printStackTrace();
             configuration.root.printError("Exception " +
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclets.properties	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclets.properties	Tue Jul 12 14:41:14 2016 -0700
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/FatalError.java	Tue Jul 12 14:41:14 2016 -0700
@@ -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;
+
+/**
+ *  <p><b>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.</b>
+ */
+@Deprecated
+public class FatalError extends Error {
+    private static final long serialVersionUID = -9131058909576418984L;
+
+    public FatalError() { }
+}
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/DocEnv.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/DocEnv.java	Tue Jul 12 14:41:14 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2015, 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
@@ -97,7 +97,7 @@
     final Enter enter;
 
     /** The name table. */
-    private Names names;
+    private final Names names;
 
     /** The encoding name. */
     private String encoding;
@@ -120,6 +120,7 @@
     JavaFileManager fileManager;
     Context context;
     DocLint doclint;
+    JavaScriptScanner javaScriptScanner;
 
     WeakHashMap<JCTree, TreePath> treePaths = new WeakHashMap<>();
 
@@ -858,6 +859,15 @@
         doclint.init(t, doclintOpts.toArray(new String[doclintOpts.size()]), false);
     }
 
+    JavaScriptScanner initJavaScriptScanner(boolean allowScriptInComments) {
+        if (allowScriptInComments) {
+            javaScriptScanner = null;
+        } else {
+            javaScriptScanner = new JavaScriptScanner();
+        }
+        return javaScriptScanner;
+    }
+
     boolean showTagMessages() {
         return (doclint == null);
     }
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/DocImpl.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/DocImpl.java	Tue Jul 12 14:41:14 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2013, 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
@@ -36,6 +36,8 @@
 
 import com.sun.javadoc.*;
 import com.sun.source.util.TreePath;
+import com.sun.tools.doclets.internal.toolkit.util.DocletAbortException;
+import com.sun.tools.doclets.internal.toolkit.util.FatalError;
 import com.sun.tools.javac.tree.JCTree;
 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
 import com.sun.tools.javac.util.Position;
@@ -128,6 +130,15 @@
     Comment comment() {
         if (comment == null) {
             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();
+                    }
+                });
+            }
             if (env.doclint != null
                     && treePath != null
                     && env.shouldCheck(treePath.getCompilationUnit())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/JavaScriptScanner.java	Tue Jul 12 14:41:14 2016 -0700
@@ -0,0 +1,1103 @@
+/*
+ * Copyright (c) 2012,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.main;
+
+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.main.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<String, TagParser> tagParsers;
+    Set<String> eventAttrs;
+    Set<String> 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 &#xhex-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 <identifier attrs> } or {@literal </identifier> }
+     */
+    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 <a href="http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javadoc.html#javadoctags">Javadoc Tags</a>
+     */
+    @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"
+        ));
+    }
+
+}
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/RootDocImpl.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/RootDocImpl.java	Tue Jul 12 14:41:14 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2015, 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
@@ -383,6 +383,10 @@
         env.initDoclint(opts, customTagNames, htmlVersion);
     }
 
+    public JavaScriptScanner initJavaScriptScanner(boolean allowScriptInComments) {
+        return env.initJavaScriptScanner(allowScriptInComments);
+    }
+
     public boolean isFunctionalInterface(AnnotationDesc annotationDesc) {
         return env.source.allowLambda()
             && annotationDesc.annotationType().qualifiedName().equals(
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/resources/javadoc.properties	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/resources/javadoc.properties	Tue Jul 12 14:41:14 2016 -0700
@@ -145,6 +145,8 @@
 javadoc.Body_missing_from_html_file=Body tag missing from HTML file
 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
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConfigurationImpl.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConfigurationImpl.java	Tue Jul 12 14:41:14 2016 -0700
@@ -296,9 +296,21 @@
                 return false;
             }
         }
+
+        // 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.
+        utils.checkJavaScriptInOption("-header", header);
+        utils.checkJavaScriptInOption("-footer", footer);
+        utils.checkJavaScriptInOption("-top", top);
+        utils.checkJavaScriptInOption("-bottom", bottom);
+        utils.checkJavaScriptInOption("-doctitle", doctitle);
+        utils.checkJavaScriptInOption("-packagesheader", packagesheader);
+
         return true;
     }
 
+
     @Override
     public boolean finishOptionSettings() {
         if (!validateOptions()) {
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties	Tue Jul 12 14:41:14 2016 -0700
@@ -363,6 +363,9 @@
 doclet.usage.no-frames.description=\
     Disable the use of frames in the generated output
 
+doclet.usage.allow-script-in-comments.description=\
+    Allow JavaScript in options and comments
+
 doclet.usage.xdocrootparent.parameters=\
     <url>
 doclet.usage.xdocrootparent.description=\
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/AbstractDoclet.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/AbstractDoclet.java	Tue Jul 12 14:41:14 2016 -0700
@@ -40,6 +40,7 @@
 import jdk.javadoc.internal.doclets.toolkit.builders.BuilderFactory;
 import jdk.javadoc.internal.doclets.toolkit.util.ClassTree;
 import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
+import jdk.javadoc.internal.doclets.toolkit.util.UncheckedDocletException;
 import jdk.javadoc.internal.doclets.toolkit.util.InternalException;
 import jdk.javadoc.internal.doclets.toolkit.util.PackageListWriter;
 import jdk.javadoc.internal.doclets.toolkit.util.ResourceIOException;
@@ -112,8 +113,12 @@
         }
 
         try {
-            startGeneration(docEnv);
-            return true;
+            try {
+                startGeneration(docEnv);
+                return true;
+            } catch (UncheckedDocletException e) {
+                throw (DocletException) e.getCause();
+            }
 
         } catch (DocFileIOException e) {
             switch (e.mode) {
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java	Tue Jul 12 14:41:14 2016 -0700
@@ -34,6 +34,8 @@
 
 package jdk.javadoc.internal.doclets.toolkit;
 
+import java.net.URI;
+
 import com.sun.source.doctree.DocCommentTree;
 import com.sun.source.doctree.DocTree;
 import com.sun.source.doctree.IdentifierTree;
@@ -43,9 +45,11 @@
 import com.sun.source.util.DocTreePath;
 import com.sun.source.util.DocTrees;
 import com.sun.source.util.TreePath;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.ExecutableElement;
@@ -54,6 +58,11 @@
 import javax.lang.model.element.VariableElement;
 import javax.lang.model.util.Elements;
 import javax.tools.FileObject;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+
+import com.sun.tools.javac.util.DefinedBy;
+import com.sun.tools.javac.util.DefinedBy.Api;
 import jdk.javadoc.internal.doclets.toolkit.util.Utils;
 
 public class CommentUtils {
@@ -185,6 +194,16 @@
         return new DocCommentDuo(treePath.getTreePath(), dcTree);
     }
 
+    public DocCommentTree parse(URI uri, String text) {
+        return trees.getDocCommentTree(new SimpleJavaFileObject(
+                uri, JavaFileObject.Kind.SOURCE) {
+            @Override @DefinedBy(Api.COMPILER)
+            public CharSequence getCharContent(boolean ignoreEncoding) {
+                return text;
+            }
+        });
+    }
+
     public void setDocCommentTree(Element element, List<DocTree> fullBody,
             List<DocTree> blockTags, Utils utils) {
         DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, blockTags);
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/Configuration.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/Configuration.java	Tue Jul 12 14:41:14 2016 -0700
@@ -228,6 +228,11 @@
     public boolean showversion = false;
 
     /**
+     * Allow JavaScript in doc comments.
+     */
+    private boolean allowScriptInComments = false;
+
+    /**
      * Sourcepath from where to read the source files. Default is classpath.
      *
      */
@@ -646,6 +651,13 @@
                     dumpOnError = true;
                     return true;
                 }
+            },
+            new Option(resources, "--allow-script-in-comments") {
+                @Override
+                public boolean process(String opt, List<String> args) {
+                    allowScriptInComments = true;
+                    return true;
+                }
             }
         };
         Set<Doclet.Option> set = new TreeSet<>();
@@ -1054,7 +1066,7 @@
         private final int argCount;
 
         protected Option(Resources resources, String name, int argCount) {
-            this(resources, "doclet.usage." + name.toLowerCase().replaceAll("^-*", ""), name, argCount);
+            this(resources, "doclet.usage." + name.toLowerCase().replaceAll("^-+", ""), name, argCount);
         }
 
         protected Option(Resources resources, String keyBase, String name, int argCount) {
@@ -1228,4 +1240,13 @@
             }
         }
     }
+
+    /**
+     * 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;
+    }
 }
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/AbstractBuilder.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/AbstractBuilder.java	Tue Jul 12 14:41:14 2016 -0700
@@ -35,6 +35,7 @@
 import jdk.javadoc.internal.doclets.toolkit.DocletException;
 import jdk.javadoc.internal.doclets.toolkit.Messages;
 import jdk.javadoc.internal.doclets.toolkit.Resources;
+import jdk.javadoc.internal.doclets.toolkit.util.UncheckedDocletException;
 import jdk.javadoc.internal.doclets.toolkit.util.InternalException;
 import jdk.javadoc.internal.doclets.toolkit.util.SimpleDocletException;
 import jdk.javadoc.internal.doclets.toolkit.util.Utils;
@@ -167,6 +168,8 @@
             Throwable cause = e.getCause();
             if (cause instanceof DocletException) {
                 throw (DocletException) cause;
+            } else if (cause instanceof UncheckedDocletException) {
+                throw (DocletException) cause.getCause();
             } else {
                 // use InternalException, so that a stacktrace showing the position of
                 // the internal exception is generated
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties	Tue Jul 12 14:41:14 2016 -0700
@@ -42,6 +42,10 @@
 doclet.Building_Index=Building index for all the packages and classes...
 doclet.Building_Index_For_All_Classes=Building index for all classes...
 doclet.sourcetab_warning=The argument for -sourcetab must be an integer greater than 0.
+doclet.JavaScript_in_comment=JavaScript found in documentation comment.\n\
+Use --allow-script-in-comments to allow use of JavaScript.
+doclet.JavaScript_in_option=option {0} contains JavaScript.\n\
+Use --allow-script-in-comments to allow use of JavaScript.
 doclet.Packages=Packages
 doclet.Modules=Modules
 doclet.Other_Packages=Other Packages
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/JavaScriptScanner.java	Tue Jul 12 14:41:14 2016 -0700
@@ -0,0 +1,116 @@
+/*
+ * 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 jdk.javadoc.internal.doclets.toolkit.util;
+
+
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Consumer;
+
+import com.sun.source.doctree.AttributeTree;
+import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.doctree.DocTree;
+import com.sun.source.doctree.DocTree.Kind;
+import com.sun.source.doctree.StartElementTree;
+import com.sun.source.util.DocTreePath;
+import com.sun.source.util.DocTreePathScanner;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.util.DefinedBy;
+import com.sun.tools.javac.util.DefinedBy.Api;
+
+/**
+ * A DocTree scanner to detect use of JavaScript in a doc comment tree.
+ */
+public class JavaScriptScanner extends DocTreePathScanner<Void, Consumer<DocTreePath>> {
+
+    public Void scan(DocCommentTree tree, TreePath p, Consumer<DocTreePath> f) {
+        return scan(new DocTreePath(p, tree), f);
+    }
+
+    @Override @DefinedBy(Api.COMPILER_TREE)
+    public Void visitStartElement(StartElementTree tree, Consumer<DocTreePath> f) {
+        String name = tree.getName().toString();
+        if (name.equalsIgnoreCase("script"))
+            f.accept(getCurrentPath());
+        return super.visitStartElement(tree, f);
+    }
+
+    @Override @DefinedBy(Api.COMPILER_TREE)
+    public Void visitAttribute(AttributeTree tree, Consumer<DocTreePath> f) {
+        String name = tree.getName().toString().toLowerCase(Locale.ENGLISH);
+        switch (name) {
+            // See https://www.w3.org/TR/html-markup/global-attributes.html#common.attrs.event-handler
+            case "onabort":  case "onblur":  case "oncanplay":  case "oncanplaythrough":
+            case "onchange":  case "onclick":  case "oncontextmenu":  case "ondblclick":
+            case "ondrag":  case "ondragend":  case "ondragenter":  case "ondragleave":
+            case "ondragover":  case "ondragstart":  case "ondrop":  case "ondurationchange":
+            case "onemptied":  case "onended":  case "onerror":  case "onfocus":  case "oninput":
+            case "oninvalid":  case "onkeydown":  case "onkeypress":  case "onkeyup":
+            case "onload":  case "onloadeddata":  case "onloadedmetadata":  case "onloadstart":
+            case "onmousedown":  case "onmousemove":  case "onmouseout":  case "onmouseover":
+            case "onmouseup":  case "onmousewheel":  case "onpause":  case "onplay":
+            case "onplaying":  case "onprogress":  case "onratechange":  case "onreadystatechange":
+            case "onreset":  case "onscroll":  case "onseeked":  case "onseeking":
+            case "onselect":  case "onshow":  case "onstalled":  case "onsubmit":  case "onsuspend":
+            case "ontimeupdate":  case "onvolumechange":  case "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.
+            // case "onchange":  case "onclick":   case "ondblclick":  case "onfocus":
+            // case "onkeydown":  case "onkeypress":  case "onkeyup":  case "onload":
+            // case "onmousedown":  case "onmousemove":  case "onmouseout":  case "onmouseover":
+            // case "onmouseup":  case "onreset":  case "onselect":  case "onsubmit":
+            case "onunload":
+                f.accept(getCurrentPath());
+                break;
+
+            // 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
+            case "action":  case "cite":  case "classid":  case "codebase":  case "data":
+            case "datasrc":  case "for":  case "href":  case "longdesc":  case "profile":
+            case "src":  case "usemap":
+                List<? extends DocTree> value = tree.getValue();
+                if (!value.isEmpty() && value.get(0).getKind() == Kind.TEXT) {
+                    String v = value.get(0).toString().trim().toLowerCase(Locale.ENGLISH);
+                    if (v.startsWith("javascript:")) {
+                        f.accept(getCurrentPath());
+                    }
+                }
+                break;
+        }
+        return super.visitAttribute(tree, f);
+    }
+
+    /**
+     * Used to indicate a fault when parsing, typically used in
+     * lambda methods.
+     */
+    public static class Fault extends RuntimeException {
+        private static final long serialVersionUID = 0L;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/UncheckedDocletException.java	Tue Jul 12 14:41:14 2016 -0700
@@ -0,0 +1,56 @@
+/*
+ * 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 jdk.javadoc.internal.doclets.toolkit.util;
+
+import jdk.javadoc.internal.doclets.toolkit.DocletException;
+
+/**
+ * An unchecked exception that wraps a DocletException.
+ * It can be used in places where a checked exception
+ * is not permitted, such as in lambda expressions.
+ *
+ *  <p><b>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.</b>
+ */
+public class UncheckedDocletException extends Error {
+    private static final long serialVersionUID = -9131058909576418984L;
+
+    public UncheckedDocletException(DocletException de) {
+        super(de);
+    }
+
+    @Override
+    public synchronized Throwable getCause() {
+        return super.getCause();
+    }
+
+    @Override
+    public synchronized Throwable initCause(Throwable cause) {
+        throw new UnsupportedOperationException();
+    }
+}
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java	Tue Jul 12 14:41:14 2016 -0700
@@ -27,6 +27,7 @@
 
 import java.lang.annotation.Documented;
 import java.lang.ref.SoftReference;
+import java.net.URI;
 import java.text.CollationKey;
 import java.text.Collator;
 import java.util.*;
@@ -108,6 +109,7 @@
     public final DocTrees docTrees;
     public final Elements elementUtils;
     public final Types typeUtils;
+    public final JavaScriptScanner javaScriptScanner;
 
     public Utils(Configuration c) {
         configuration = c;
@@ -115,6 +117,7 @@
         elementUtils = c.docEnv.getElementUtils();
         typeUtils = c.docEnv.getTypeUtils();
         docTrees = c.docEnv.getDocTrees();
+        javaScriptScanner = c.isAllowScriptInComments() ? null : new JavaScriptScanner();
     }
 
     // our own little symbol table
@@ -3025,6 +3028,16 @@
         TreePath path = isValidDuo(duo) ? duo.treePath : null;
         if (!dcTreeCache.containsKey(element)) {
             if (docCommentTree != null && path != null) {
+                if (!configuration.isAllowScriptInComments()) {
+                    try {
+                        javaScriptScanner.scan(docCommentTree, path, p -> {
+                            throw new JavaScriptScanner.Fault();
+                        });
+                    } catch (JavaScriptScanner.Fault jsf) {
+                        String text = configuration.getText("doclet.JavaScript_in_comment");
+                        throw new UncheckedDocletException(new SimpleDocletException(text, jsf));
+                    }
+                }
                 configuration.workArounds.runDocLint(path);
             }
             dcTreeCache.put(element, duo);
@@ -3044,6 +3057,21 @@
         return null;
     }
 
+    public void checkJavaScriptInOption(String name, String value) {
+        if (!configuration.isAllowScriptInComments()) {
+            DocCommentTree dct = configuration.cmtUtils.parse(
+                    URI.create("option://" + name.replace("-", "")), "<body>" + value + "</body>");
+            try {
+                javaScriptScanner.scan(dct, null, p -> {
+                    throw new JavaScriptScanner.Fault();
+                });
+            } catch (JavaScriptScanner.Fault jsf) {
+                String text = configuration.getText("doclet.JavaScript_in_option", name);
+                throw new UncheckedDocletException(new SimpleDocletException(text, jsf));
+            }
+        }
+    }
+
     boolean isValidDuo(DocCommentDuo duo) {
         return duo != null && duo.dcTree != null;
     }
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Messager.java	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Messager.java	Tue Jul 12 14:41:14 2016 -0700
@@ -182,7 +182,7 @@
     }
 
     private String getDiagSource(DocTreePath path) {
-        if (path == null) {
+        if (path == null || path.getTreePath() == null) {
             return programName;
         }
         JavacTrees trees = JavacTrees.instance(context);
--- a/langtools/test/Makefile	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/test/Makefile	Tue Jul 12 14:41:14 2016 -0700
@@ -378,7 +378,7 @@
 	@rm -f -r $(JCK_COMPILER_OUTPUT_DIR)/work $(JCK_COMPILER_OUTPUT_DIR)/report \
 	    $(JCK_COMPILER_OUTPUT_DIR)/diff.html $(JCK_COMPILER_OUTPUT_DIR)/status.txt
 	@mkdir -p $(JCK_COMPILER_OUTPUT_DIR)
-	$(JT_JAVA)/bin/java -Xmx512m \
+	$(JT_JAVA)/bin/java -Xmx1024m \
 	    -jar $(JCK_HOME)/JCK-compiler-9/lib/jtjck.jar \
 	    $(if $(JCK_VERBOSE),$(if $(filter $(JCK_VERBOSE),summary),-v,-v:$(JCK_VERBOSE))) \
             -r:$(JCK_COMPILER_OUTPUT_DIR)/report \
@@ -429,7 +429,7 @@
 	@rm -f -r $(JCK_RUNTIME_OUTPUT_DIR)/work $(JCK_RUNTIME_OUTPUT_DIR)/report \
 	    $(JCK_RUNTIME_OUTPUT_DIR)/diff.html $(JCK_RUNTIME_OUTPUT_DIR)/status.txt
 	@mkdir -p $(JCK_RUNTIME_OUTPUT_DIR)
-	$(JT_JAVA)/bin/java -Xmx512m \
+	$(JT_JAVA)/bin/java -Xmx1024m \
 	    -jar $(JCK_HOME)/JCK-runtime-9/lib/jtjck.jar \
 	    $(if $(JCK_VERBOSE),$(if $(filter $(JCK_VERBOSE),summary),-v,-v:$(JCK_VERBOSE))) \
             -r:$(JCK_RUNTIME_OUTPUT_DIR)/report \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/javadoc/tool/TestScriptInComment.java	Tue Jul 12 14:41:14 2016 -0700
@@ -0,0 +1,313 @@
+/*
+ * 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
+ * @modules jdk.javadoc/jdk.javadoc.internal.tool
+ */
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+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("<script>#ALERT</script>", true), // script tag in Lower Case
+        UC("<SCRIPT>#ALERT</script>", true), // script tag in Upper Case
+        WS("< script >#ALERT</script>", false, "-Xdoclint:none"), // script tag with invalid white space
+        SP("<script src=\"file\"> #ALERT </script>", true), // script tag with an attribute
+        ON("<a onclick='#ALERT'>x</a>", true), // event handler attribute
+        URI("<a href='javascript:#ALERT'>x</a>", true); // javascript 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<String> 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("<html><body> overview #COMMENT </body></html>", "package p; public class C { }"),
+        PKGINFO("#COMMENT package p;", "package p; public class C { }"),
+        PKGHTML("<html><body>#COMMENT package p;</body></html>", "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<String>();
+            sources = new ArrayList<String>();
+            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 <html> or not;  2: package name;  3: class name
+        private final Pattern pat =
+                Pattern.compile("(?i)(<html>)?.*?(?: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<String> 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<String> opts;
+        final List<String> 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<String> opts = new ArrayList<String>();
+        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(!outFile.exists(), "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<String> opts, PrintWriter pw) {
+        return jdk.javadoc.internal.tool.Main.execute(opts.toArray(new String[opts.size()]), pw);
+    }
+
+    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;
+}
+
--- a/langtools/test/tools/doclint/html/OtherTagsTest.out	Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/test/tools/doclint/html/OtherTagsTest.out	Tue Jul 12 14:41:14 2016 -0700
@@ -19,10 +19,7 @@
 OtherTagsTest.java:21: error: element not allowed in documentation comments: <noframes>
      *  <noframes> </noframes>
         ^
-OtherTagsTest.java:22: error: element not allowed in documentation comments: <script>
-     *  <script> </script>
-        ^
 OtherTagsTest.java:23: error: element not allowed in documentation comments: <title>
      *  <title> </title>
         ^
-9 errors
+8 errors
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javadoc/TestScriptInComment.java	Tue Jul 12 14:41:14 2016 -0700
@@ -0,0 +1,314 @@
+/*
+ * 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
+ * @modules jdk.javadoc/com.sun.tools.javadoc
+ */
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+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("<script>#ALERT</script>", true), // script tag in Lower Case
+        UC("<SCRIPT>#ALERT</script>", true), // script tag in Upper Case
+        WS("< script >#ALERT</script>", false, "-Xdoclint:none"), // script tag with invalid white space
+        SA("<script src=\"file\"> #ALERT </script>", true), // script tag with an attribute
+        ON("<a onclick='#ALERT'>x</a>", true), // event handler attribute
+        URI("<a href='javascript:#ALERT'>x</a>", 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<String> 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("<html><body> overview #COMMENT </body></html>", "package p; public class C { }"),
+        PKGINFO("#COMMENT package p;", "package p; public class C { }"),
+        PKGHTML("<html><body>#COMMENT package p;</body></html>", "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<String>();
+            sources = new ArrayList<String>();
+            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 <html> or not;  2: package name;  3: class name
+        private final Pattern pat =
+                Pattern.compile("(?i)(<html>)?.*?(?: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<String> 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<String> opts;
+        final List<String> 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<String> opts = new ArrayList<String>();
+        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(!outFile.exists(), "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<String> 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;
+}
+