Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/Java/Openjdk/test/langtools/tools/lib/snippets/   (Sun/Oracle ©)  Datei vom 13.11.2022 mit Größe 21 kB image not shown  

Quelle  SnippetUtils.java   Sprache: JAVA

 
/*
 * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


package snippets;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleElementVisitor14;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import com.sun.source.doctree.AttributeTree;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.SnippetTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.util.DocTreeScanner;
import com.sun.source.util.DocTrees;
import com.sun.source.util.JavacTask;

/**
 * Utilities for analyzing snippets.
 *
 * Support is provided for the following:
 * <ul>
 * <li>creating an instance of {@link JavacTask} suitable for looking up
 *     elements by name, in order to access any corresponding documentation comment,
 * <li>scanning elements to find all associated snippets,
 * <li>locating instances of snippets by their {@code id},
 * <li>parsing snippets, and
 * <li>accessing the body of snippets, for any additional analysis.
 * </ul>
 *
 * @apiNote
 * The utilities do not provide support for compiling and running snippets,
 * because in general, this requires too much additional context. However,
 * the utilities do provide support for locating snippets in various ways,
 * and accessing the body of those snippets, to simplify the task of writing
 * code to compile and run snippets, where that is appropriate.
 */

public class SnippetUtils {
    /**
     * Exception used to report a configuration issue that prevents
     * the test from executing as expected.
     */

    public static class ConfigurationException extends Exception {
        public ConfigurationException(String message) {
            super(message);
        }
    }

    /**
     * Exception used to report that a snippet could not be found.
     */

    public static class SnippetNotFoundException extends Exception {
        public SnippetNotFoundException(String message) {
            super(message);
        }
    }

    /**
     * Exception used to report that a doc comment could not be found.
     */

    public static class DocCommentNotFoundException extends Exception {
        public DocCommentNotFoundException(String message) {
            super(message);
        }
    }

    private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    private final StandardJavaFileManager fileManager;
    private final Path srcDir;
    private final JavacTask javacTask;
    private final Elements elements;
    private final DocTrees docTrees;

    /**
     * Creates an instance for analysing snippets in one or more JDK modules.
     *
     * The source for the modules is derived from the value of the
     * {@code test.src} system property.
     *
     * Any messages, including error messages, will be written to {@code System.err}.
     *
     * @param modules the modules
     *
     * @throws IllegalArgumentException if no modules are specified
     * @throws ConfigurationException if the main source directory cannot be found
     *                                or if a module's source directory cannot be found
     */

    public SnippetUtils(String... modules) throws ConfigurationException {
        this(findSourceDir(), nullnull, Set.of(modules));
    }

    /**
     * Creates an instance for analysing snippets in one or more modules.
     *
     * @param srcDir the location for the source of the modules;
     *               the location for the source of a specific module should be
     *               in <em>srcDir</em>{@code /}<em>module</em>{@code /share/module}
     *
     * @param pw     a writer for any text messages that may be generated;
     *               if null, messages will be written to {@code System.err}
     *
     * @param dl     a diagnostic listener for any diagnostic messages that may be generated;
     *               if null, messages will be written to {@code System.err}
     *
     * @param modules the modules
     *
     * @throws IllegalArgumentException if no modules are specified
     * @throws ConfigurationException if {@code srcDir} does not exist
     *                                or if a module's source directory cannot be found
     */

    public SnippetUtils(Path srcDir, PrintWriter pw, DiagnosticListener<JavaFileObject> dl, Set<String> modules)
            throws ConfigurationException {
        if (modules.isEmpty()) {
            throw new IllegalArgumentException("no modules specified");
        }

        if (!Files.exists(srcDir)) {
            throw new ConfigurationException("directory not found: " + srcDir);
        }

        this.srcDir = srcDir;

        for (var m : modules) {
            var moduleSourceDir = getModuleSourceDir(m);
            if (!Files.exists(moduleSourceDir)) {
                throw new ConfigurationException(("cannot find source directory for " + m
                        + ": " + moduleSourceDir));
            }
        }

        fileManager = compiler.getStandardFileManager(dl, nullnull);

        List<String> opts = new ArrayList<>();
        opts.addAll(List.of("--add-modules", String.join(",", modules)));  // could use CompilationTask.addModules
        modules.forEach(m -> opts.addAll(List.of("--patch-module", m + "=" + getModuleSourceDir(m))));
        opts.add("-proc:only");

        javacTask = (JavacTask) compiler.getTask(pw, fileManager, dl, opts, nullnull);
        elements = javacTask.getElements();
        elements.getModuleElement("java.base"); // forces module graph to be instantiated, etc

        docTrees = DocTrees.instance(javacTask);
    }

    /**
     * {@return the source directory for the task used to access snippets}
     */

    public Path getSourceDir() {
        return srcDir;
    }

    /**
     * {@return the file manager for the task used to access snippets}
     */

    public StandardJavaFileManager  getFileManager() {
        return fileManager;
    }

    /**
     * {@return the instance of {@code Elements} for the task used to access snippets}
     */

    public Elements getElements() {
        return elements;
    }

    /**
     * {@return the instance of {@code DocTrees} for the task used to access snippets}
     */

    public DocTrees getDocTrees() {
        return docTrees;
    }

    /**
     * {@return the doc comment tree for an element}
     *
     * @param element the element
     */

    public DocCommentTree getDocCommentTree(Element element) {
        return docTrees.getDocCommentTree(element);
    }

    /**
     * {@return the snippet with a given id in a doc comment tree}
     *
     * @param tree the doc comment tree
     * @param id   the id
     *
     * @throws SnippetNotFoundException if the snippet cannot be found
     */

    public SnippetTree getSnippetById(DocCommentTree tree, String id) throws SnippetNotFoundException {
        SnippetTree result = new SnippetFinder().scan(tree, id);
        if (result == null) {
            throw new SnippetNotFoundException(id);
        }
        return result;
    }

    /**
     * {@return the snippet with a given id in the doc comment tree for an element}
     *
     * @param element the element
     * @param id      the id
     *
     * @throws DocCommentNotFoundException if the doc comment for the element cannot be found
     * @throws SnippetNotFoundException if the snippet cannot be found
     */

    public SnippetTree getSnippetById(Element element, String id)
            throws DocCommentNotFoundException, SnippetNotFoundException {
        DocCommentTree docCommentTree = getDocCommentTree(element);
        if (docCommentTree == null) {
            var name = (element instanceof QualifiedNameable q) ? q.getQualifiedName() : element.getSimpleName();
            throw new DocCommentNotFoundException(element.getKind() + " " + name);
        }
        return getSnippetById(docCommentTree, id);
    }

    /**
     * A scanner to locate the tree for a snippet with a given id.
     * Note: the scanner is use-once.
     */

    private static class SnippetFinder extends DocTreeScanner<SnippetTree,String> {
        private SnippetTree result;
        private SnippetTree inSnippet;

        @Override
        public SnippetTree scan(DocTree tree, String id) {
            // stop scanning once the result has been found
            return result != null ? result : super.scan(tree, id);
        }

        @Override
        public SnippetTree visitSnippet(SnippetTree tree, String id) {
            inSnippet = tree;
            try {
                return super.visitSnippet(tree, id);
            } finally {
                inSnippet = null;
            }
        }

        @Override
        public SnippetTree visitAttribute(AttributeTree tree, String id) {
            if (tree.getName().contentEquals("id")
                    && tree.getValue().toString().equals(id)) {
                result = inSnippet;
                return result;
            } else {
                return null;
            }
        }
    }

    /**
     * Scans an element and appropriate enclosed elements for doc comments,
     * and call a handler to handle any snippet trees in those doc comments.
     *
     * Only the public and protected members of type elements are scanned.
     * The enclosed elements of modules and packages are <em>not</em> scanned.
     *
     * @param element the element
     * @param handler the handler
     * @throws IllegalArgumentException if any inappropriate element is scanned
     */

    public void scan(Element element, BiConsumer<Element, SnippetTree> handler) {
        new ElementScanner(docTrees).scan(element, handler);
    }

    private static class ElementScanner extends SimpleElementVisitor14<Void, DocTreeScanner<Void, Element>> {
        private final DocTrees trees;

        public ElementScanner(DocTrees trees) {
            this.trees = trees;
        }

        public void scan(Element e, BiConsumer<Element, SnippetTree> snippetHandler) {
            visit(e, new DocTreeScanner<>() {
                @Override
                public Void visitSnippet(SnippetTree tree, Element e) {
                    snippetHandler.accept(e, tree);
                    return null;
                }
            });
        }

        @Override
        public Void visitModule(ModuleElement me, DocTreeScanner<Void, Element> treeScanner) {
            scanDocComment(me, treeScanner);
            return null;
        }

        @Override
        public Void visitPackage(PackageElement pe, DocTreeScanner<Void, Element> treeScanner) {
            scanDocComment(pe, treeScanner);
            return null;
        }

        @Override
        public Void visitType(TypeElement te, DocTreeScanner<Void, Element> treeScanner) {
            scanDocComment(te, treeScanner);
            for (Element e : te.getEnclosedElements()) {
                Set<Modifier> mods = e.getModifiers();
                if (mods.contains(Modifier.PUBLIC) || mods.contains(Modifier.PROTECTED)) {
                    e.accept(this, treeScanner);
                }
            }
            return null;
        }

        @Override
        public Void visitExecutable(ExecutableElement ee, DocTreeScanner<Void, Element> treeScanner) {
            scanDocComment(ee, treeScanner);
            return null;
        }

        @Override
        public Void visitVariable(VariableElement ve, DocTreeScanner<Void, Element> treeScanner) {
            switch (ve.getKind()) {
                case ENUM_CONSTANT, FIELD -> scanDocComment(ve, treeScanner);
                default -> defaultAction(ve, treeScanner);
            }
            return null;
        }

        @Override
        public Void defaultAction(Element e, DocTreeScanner<Void, Element> treeScanner) {
            throw new IllegalArgumentException(e.getKind() + " " + e.getSimpleName());
        }

        private void scanDocComment(Element e, DocTreeScanner<Void, Element> treeScanner) {
            DocCommentTree dc = trees.getDocCommentTree(e);
            if (dc != null) {
                treeScanner.scan(dc, e);
            }
        }
    }

    /**
     * {@return the string content of an inline or hybrid snippet, or {@code null} for an external snippet}
     *
     * @param tree the snippet
     */

    public String getBody(SnippetTree tree) {
        TextTree body = tree.getBody();
        return body == null ? null : body.getBody();
    }

    /**
     * {@return the string content of an external or inline snippet}
     *
     * @param element the element whose documentation contains the snippet
     * @param tree    the snippet
     */

    public String getBody(Element element, SnippetTree tree) throws IOException {
        Path externalSnippetPath = getExternalSnippetPath(element, tree);
        return externalSnippetPath == null ? getBody(tree) : Files.readString(externalSnippetPath);
    }

    /**
     * {@return the path for the {@code snippet-files} directory for an element}
     *
     * @param element the element
     *
     * @return the path
     */

    public Path getSnippetFilesDir(Element element) {
        var moduleElem = elements.getModuleOf(element);
        var modulePath = getModuleSourceDir(moduleElem);

        var packageElem = elements.getPackageOf(element); // null for a module
        var packagePath = packageElem == null
                ? modulePath
                : modulePath.resolve(packageElem.getQualifiedName().toString().replace(".", File.separator));

        return packagePath.resolve("snippet-files");
    }

    /**
     * {@return the path for an external snippet, or {@code null} if the snippet is inline}
     *
     * @param element the element whose documentation contains the snippet
     * @param tree    the snippet
     */

    public Path getExternalSnippetPath(Element element, SnippetTree tree) {
        var classAttr = getAttr(tree, "class");
        String file = (classAttr != null)
            ? classAttr.replace(".""/") + ".java"
            : getAttr(tree, "file");
        return file == null ? null : getSnippetFilesDir(element).resolve(file.replace("/", File.separator));
    }

    /**
     * {@return the value of an attribute defined by a snippet}
     *
     * @param tree the snippet
     * @param name the name of the attribute
     */

    public String getAttr(SnippetTree tree, String name) {
        for (DocTree t : tree.getAttributes()) {
            if (t instanceof AttributeTree at && at.getName().contentEquals(name)) {
                return at.getValue().toString();
            }
        }
        return null;
    }

    /**
     * {@return the primary source directory for a module}
     *
     * The directory is <em>srcDir</em>/<em>module-name</em>/share/classes.
     *
     * @param e the module
     */

    public Path getModuleSourceDir(ModuleElement e) {
        return getModuleSourceDir(e.getQualifiedName().toString());
    }

    /**
     * {@return the primary source directory for a module}
     *
     * The directory is <em>srcDir</em>/<em>moduleName</em>/share/classes.
     *
     * @param moduleName the module name
     */

    public Path getModuleSourceDir(String moduleName) {
        return srcDir.resolve(moduleName).resolve("share").resolve("classes");
    }

    /**
     * Kinds of fragments of source code.
     */

    public enum SourceKind {
        /** A module declaration. */
        MODULE_INFO,
        /** A package declaration. */
        PACKAGE_INFO,
        /** A class or interface declaration. */
        TYPE_DECL,
        /** A member declaration for a class or interface. */
        MEMBER_DECL,
        /** A statement, expression or other kind of fragment. */
        OTHER
    }

    /**
     * Parses a fragment of source code, after trying to infer the kind of the fragment.
     *
     * @param body      the string to be parsed
     * @param showDiag  a function to handle any diagnostics that may be generated
     * @return          {@code true} if the parse succeeded, and {@code false} otherwise
     *
     * @throws IOException if an IO exception occurs
     */

    public boolean parse(String body, Consumer<? super Diagnostic<? extends JavaFileObject>> showDiag) throws IOException {
        DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
        parse(body, null, collector);
        var diags = collector.getDiagnostics();
        diags.forEach(showDiag);
        return diags.isEmpty();
    }

    /**
     * Parses a fragment of source code, after trying to infer the kind of the fragment.
     *
     * @param body the string to be parsed
     * @param pw   a stream for diagnostics, or {@code null} to use {@code System.err}
     * @param dl   a diagnostic listener, or {@code null} to report diagnostics to {@code pw} or {@code System.err}
     * @throws IOException if an IO exception occurs
     */

    public void parse(String body, PrintWriter pw, DiagnosticListener<JavaFileObject> dl)
            throws IOException {
        parse(inferSourceKind(body), body, pw, dl);
    }

    /**
     * Parses a fragment of source code of a given kind.
     *
     * @param kind the kind of code to be parsed
     * @param body the string to be parsed
     * @param pw   a stream for diagnostics, or {@code null} to use {@code System.err}
     * @param dl   a diagnostic listener, or {@code null} to report diagnostics to {@code pw} or {@code System.err}.
     * @throws IOException if an IO exception occurs
     */

    public void parse(SourceKind kind, String body, PrintWriter pw, DiagnosticListener<JavaFileObject> dl)
            throws IOException {
        String fileBase = switch (kind) {
            case MODULE_INFO -> "module-info";
            case PACKAGE_INFO -> "package-info";
            default -> "C";  // the exact name doesn't matter if just parsing (the filename check for public types comes later on)
        };
        URI uri = URI.create("mem://%s.java".formatted(fileBase));

        String compUnit = switch (kind) {
            case MODULE_INFO, PACKAGE_INFO, TYPE_DECL -> body;
            case MEMBER_DECL -> """
                    class C {
                    %s
                    }""".formatted(body);
            case OTHER -> """
                    class C {
                        void m() {
                        %s
                        ;
                        }
                    }""".formatted(body);
        };
        JavaFileObject fo = new SimpleJavaFileObject(uri, JavaFileObject.Kind.SOURCE) {
            public CharSequence getCharContent(boolean ignoreEncodingErrors) {
                return compUnit;
            }
        };

        JavaFileManager fm = compiler.getStandardFileManager(dl, nullnull);

        List<String> opts = new ArrayList<>();
        JavacTask javacTask = (JavacTask) compiler.getTask(pw, fm, dl, opts, null, List.of(fo));

        javacTask.parse();
    }

    public SourceKind inferSourceKind(String s) {
        Pattern typeDecl = Pattern.compile("(?s)(^|\\R)([A-Za-z0-9_$ ])*\\b(?module|package|class|interface|record|enum)\\s+(?[A-Za-z0-9_$]+)");
        Matcher m1 = typeDecl.matcher(s);
        if (m1.find()) {
            return switch (m1.group("kw")) {
                case "module" -> SourceKind.MODULE_INFO;
                case "package" -> m1.find() ? SourceKind.TYPE_DECL : SourceKind.PACKAGE_INFO;
                default -> SourceKind.TYPE_DECL;
            };
        }

        Pattern methodDecl = Pattern.compile("(?s)(^|\\R)([A-Za-z0-9<>,]+ )+\\b(?[A-Za-z0-9_$]+)([(;]| +=)");
        Matcher m2 = methodDecl.matcher(s);
        if (m2.find()) {
            return SourceKind.MEMBER_DECL;
        }

        return SourceKind.OTHER;
    }

    private static Path findSourceDir() throws ConfigurationException {
        String testSrc = System.getProperty("test.src");
        Path p = Path.of(testSrc).toAbsolutePath();
        while (p.getParent() != null) {
            Path srcDir = p.resolve("src");
            if (Files.exists(srcDir.resolve("java.base"))) {
                return srcDir;
            }
            p = p.getParent();
        }
        throw new ConfigurationException("Cannot find src/ from " + testSrc);
    }
}

Messung V0.5
C=95 H=86 G=90

¤ Dauer der Verarbeitung: 0.16 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.