Download
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This source code is provided to illustrate the usage of a given feature
* or technique and has been deliberately simplified. Additional steps
* required for a production-quality application, such as security checks,
* input validation and proper error handling, might not be present in
* this sample code.
*/
package p;
import com.sun.source.doctree.AttributeTree;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.StartElementTree;
import com.sun.source.util.DocSourcePositions;
import com.sun.source.util.DocTreeScanner;
import com.sun.source.util.DocTrees;
import com.sun.source.util.JavacTask;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleElementVisitor14;
import javax.tools.ToolProvider;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Example3 {
public static void main(String... args) throws IOException {
try {
var ok = new Example3().run(args);
if (!ok) {
System.exit(1);
}
} catch (IOException e) {
System.err.println("IO Exception: " + e.getMessage());
System.exit(2);
}
}
public boolean run(String... args) throws IOException {
var options = List.of("-proc:only");
var files = findJavaFiles(Stream.of(args)
.map(Path::of)
.toList());
var c = ToolProvider.getSystemJavaCompiler();
var fm = c.getStandardFileManager(null, Locale.getDefault(), StandardCharsets.UTF_8);
var fileObjects = fm.getJavaFileObjectsFromPaths(files);
var t = (JavacTask) c.getTask(null, fm, null, options, null, fileObjects);
t.setProcessors(List.of(new AbstractProcessor() {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Set.of("*");
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_17;
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
roundEnv.getRootElements().forEach(this::processRootElement);
return false;
}
private void processRootElement(Element e) {
var pe = elements.getPackageOf(e);
if (pe != null) {
var me = elements.getModuleOf(e);
if (me != null) {
var exported = me.getDirectives().stream()
.filter(d -> d.getKind() == ModuleElement.DirectiveKind.EXPORTS)
.map(ModuleElement.ExportsDirective.class::cast)
.anyMatch(ed -> ed.getPackage() == pe);
if (!exported) {
return;
}
}
}
out.println("*** root element " + e);
var linkScanner = new LinkScanner();
declScanner.scan(e, linkScanner);
linkScanner.urls.forEach((u -> out.println(" " + u)));
}
}));
elements = t.getElements();
docTrees = DocTrees.instance(t);
positions = docTrees.getSourcePositions();
t.call();
if (errors == 0) {
return true;
} else {
log.println(errors + " errors");
return false;
}
}
private PrintStream out = System.out;
private PrintStream log = System.err;
private Path userDir = Path.of(System.getProperty("user.dir"));
private int errors;
private Elements elements;
private DocTrees docTrees;
private DocSourcePositions positions;
private DeclScanner declScanner = new DeclScanner();
List<Path> findJavaFiles(List<Path> files) throws IOException {
List<Path> list = new ArrayList<>();
for (var f: files) {
if (Files.isRegularFile(f) && f.getFileName().toString().endsWith(".java")) {
list.add(f);
} else if (Files.isDirectory(f)) {
Files.walkFileTree(f, new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
return dir.getFileName().toString().equals("internal")
? FileVisitResult.SKIP_SUBTREE : FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.getFileName().toString().endsWith(".java")) {
list.add(file);
}
return FileVisitResult.CONTINUE;
}
});
}
}
list.sort(Comparator.naturalOrder());
return list;
}
void error(Path file, String message) {
log.println(file + ": " + message);
errors++;
}
class DeclScanner extends SimpleElementVisitor14<Void, LinkScanner> {
public void scan(Element e, LinkScanner linkScanner) {
e.accept(this, linkScanner);
}
public Void defaultAction(Element e, LinkScanner s) {
var dct = docTrees.getDocCommentTree(e);
if (dct != null) {
s.scan(e, dct);
}
return null;
}
@Override
public Void visitType(TypeElement e, LinkScanner specScanner) {
defaultAction(e, specScanner);
e.getEnclosedElements().stream()
.forEachOrdered(ee -> ee.accept(this, specScanner));
return null;
}
}
class LinkScanner extends DocTreeScanner<Void, Void> {
final Set<String> urls = new TreeSet<>();
private Element element;
private DocCommentTree docComment;
private StartElementTree startTree;
LinkScanner() {
}
void scan(Element element, DocCommentTree docComment) {
this.element = element;
this.docComment = docComment;
startTree = null;
docComment.accept(this, null);
}
@Override
public Void visitStartElement(StartElementTree tree, Void p) {
if (matches(tree.getName(), "a")) {
startTree = tree;
try {
// visit attributes
super.visitStartElement(tree, p);
} finally {
startTree = null;
}
}
return null;
}
@Override
public Void visitAttribute(AttributeTree tree, Void p) {
if (startTree != null && matches(tree.getName(), "href")) {
var url = tree.getValue().stream().map(Object::toString).collect(Collectors.joining());
urls.add(url);
}
return null;
}
private boolean matches(Name tagName, String s) {
return tagName.toString().equalsIgnoreCase(s);
}
}
}