Collect tags using the Java Compiler Tree API
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import static nobuild.NoBuild.*;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Build {
|
||||
static final String program = "jtags";
|
||||
@@ -18,7 +20,12 @@ public class Build {
|
||||
compileJava(classPaths, sourcePaths);
|
||||
}
|
||||
|
||||
runJava(classPaths, mainClass, args);
|
||||
var argumentPartitions =
|
||||
Arrays.stream(args).collect(Collectors.partitioningBy(it -> it.startsWith("-D")));
|
||||
String[] javaArguments = argumentPartitions.get(true).toArray(String[]::new);
|
||||
String[] programArguments = argumentPartitions.get(false).toArray(String[]::new);
|
||||
|
||||
runJava(classPaths, javaArguments, mainClass, programArguments);
|
||||
}
|
||||
|
||||
private static void ensureDependencies() {
|
||||
|
||||
@@ -231,6 +231,36 @@ public class NoBuild {
|
||||
return command(commandLineStream.toArray(String[]::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a Java class
|
||||
*
|
||||
* @param additionalClassPaths Additional class paths to pass to the compiler
|
||||
* @param javaArguments Arguments to pass to the java binary
|
||||
* @param mainClass The fully qualified class name, e.g. {@code com.example.MyClass$MySubClass}
|
||||
* @param args Arguments to pass to the main method
|
||||
* @return Exit status code
|
||||
*/
|
||||
public static int runJava(
|
||||
String[] additionalClassPaths, String[] javaArguments, String mainClass, String... args) {
|
||||
String pathSeparator = System.getProperty("path.separator");
|
||||
|
||||
String classPaths =
|
||||
String.join(
|
||||
pathSeparator,
|
||||
Stream.concat(Stream.of(javaClassPath), Arrays.stream(additionalClassPaths))
|
||||
.toArray(String[]::new));
|
||||
|
||||
Stream<String> commandLineStream =
|
||||
Stream.of(
|
||||
Stream.of(javaBin.toString(), "-cp", classPaths),
|
||||
Arrays.stream(javaArguments),
|
||||
Stream.of(mainClass),
|
||||
Arrays.stream(args))
|
||||
.flatMap(it -> it);
|
||||
|
||||
return command(commandLineStream.toArray(String[]::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles java sources
|
||||
*
|
||||
|
||||
@@ -1,7 +1,35 @@
|
||||
package xyz.naofal.jtags;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Jtags {
|
||||
static class Options {
|
||||
String[] sources;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello");
|
||||
var arguments = new ArrayDeque<>(Arrays.asList(args));
|
||||
|
||||
if (arguments.size() == 0) {
|
||||
printUsage();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
Options options = new Options();
|
||||
options.sources = args;
|
||||
|
||||
System.exit(run(options) ? 0 : 1);
|
||||
}
|
||||
|
||||
static boolean run(Options options) {
|
||||
return TagCollector.run(options);
|
||||
}
|
||||
|
||||
static void printUsage() {
|
||||
System.err.println(
|
||||
"""
|
||||
USAGE: jtags [options] <sources...>
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
63
src/main/java/xyz/naofal/jtags/JtagsLogger.java
Normal file
63
src/main/java/xyz/naofal/jtags/JtagsLogger.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package xyz.naofal.jtags;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class JtagsLogger {
|
||||
public static Logger logger = Logger.getLogger("logger");
|
||||
public static Handler loggingHandler = new LogHandler();
|
||||
|
||||
static {
|
||||
logger.setUseParentHandlers(false);
|
||||
logger.addHandler(loggingHandler);
|
||||
String level;
|
||||
if ((level = System.getProperty("logger.level")) != null) {
|
||||
logger.setLevel(Level.parse(level));
|
||||
}
|
||||
}
|
||||
|
||||
public static class LogHandler extends Handler {
|
||||
@Override
|
||||
public void close() {
|
||||
flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
System.err.flush();
|
||||
}
|
||||
|
||||
public static Map<Level, Integer> colors =
|
||||
new HashMap<Level, Integer>() {
|
||||
{
|
||||
put(Level.FINEST, 5);
|
||||
put(Level.FINE, 4);
|
||||
put(Level.FINER, 6);
|
||||
put(Level.INFO, 2);
|
||||
put(Level.WARNING, 3);
|
||||
put(Level.SEVERE, 1);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
if (record.getMessage() != null && !record.getMessage().isEmpty()) {
|
||||
// if (System.console().isTerminal()) {
|
||||
int color = colors.get(record.getLevel());
|
||||
System.err.printf(
|
||||
"\u001b[38;5;%dm[%s]\u001b[0m %s%n",
|
||||
color, record.getLevel().getName(), record.getMessage());
|
||||
// } else {
|
||||
// System.err.printf("[%s] %s%n", record.getLevel().getName(), record.getMessage());
|
||||
// }
|
||||
}
|
||||
if (record.getThrown() != null) {
|
||||
System.err.println(record.getThrown().toString().indent(4));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/main/java/xyz/naofal/jtags/Tag.java
Normal file
10
src/main/java/xyz/naofal/jtags/Tag.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package xyz.naofal.jtags;
|
||||
|
||||
public record Tag(TagKind kind, String name, String location, String line)
|
||||
implements Comparable<Tag> {
|
||||
|
||||
@Override
|
||||
public int compareTo(Tag o) {
|
||||
return String.CASE_INSENSITIVE_ORDER.compare(name, o.name());
|
||||
}
|
||||
}
|
||||
40
src/main/java/xyz/naofal/jtags/TagCollector.java
Normal file
40
src/main/java/xyz/naofal/jtags/TagCollector.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package xyz.naofal.jtags;
|
||||
|
||||
import static xyz.naofal.jtags.JtagsLogger.logger;
|
||||
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.Trees;
|
||||
import java.io.IOException;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import javax.tools.ToolProvider;
|
||||
import xyz.naofal.jtags.Jtags.Options;
|
||||
|
||||
public class TagCollector {
|
||||
|
||||
static JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
|
||||
public static boolean run(Options options) {
|
||||
try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) {
|
||||
|
||||
Iterable<? extends JavaFileObject> compilationUnits =
|
||||
fileManager.getJavaFileObjects(options.sources);
|
||||
|
||||
JavacTask task =
|
||||
(JavacTask) compiler.getTask(null, fileManager, null, null, null, compilationUnits);
|
||||
Iterable<? extends CompilationUnitTree> trees = task.parse();
|
||||
TreeVisitorContext context = new TreeVisitorContext(Trees.instance(task));
|
||||
TreeVisitor treeVisitor = new TreeVisitor();
|
||||
for (CompilationUnitTree compilationUnitTree : trees) {
|
||||
compilationUnitTree.accept(treeVisitor, context);
|
||||
}
|
||||
|
||||
} catch (IOException ex) {
|
||||
logger.severe(ex.toString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
12
src/main/java/xyz/naofal/jtags/TagKind.java
Normal file
12
src/main/java/xyz/naofal/jtags/TagKind.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package xyz.naofal.jtags;
|
||||
|
||||
public enum TagKind {
|
||||
PACKAGE,
|
||||
CLASS,
|
||||
INTERFACE,
|
||||
ANNOTATION,
|
||||
ENUM,
|
||||
FIELD,
|
||||
ENUM_CONSTANT,
|
||||
METHOD;
|
||||
}
|
||||
78
src/main/java/xyz/naofal/jtags/TreeVisitor.java
Normal file
78
src/main/java/xyz/naofal/jtags/TreeVisitor.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package xyz.naofal.jtags;
|
||||
|
||||
import static xyz.naofal.jtags.JtagsLogger.logger;
|
||||
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.PackageTree;
|
||||
import com.sun.source.util.SimpleTreeVisitor;
|
||||
import java.util.PriorityQueue;
|
||||
|
||||
public class TreeVisitor extends SimpleTreeVisitor<Void, TreeVisitorContext> {
|
||||
public PriorityQueue<Tag> tags = new PriorityQueue<>();
|
||||
|
||||
@Override
|
||||
public Void visitCompilationUnit(CompilationUnitTree node, TreeVisitorContext p) {
|
||||
p.compilationUnitTree = node;
|
||||
|
||||
logger.finer("Collecting tags in file: " + p.getLocation());
|
||||
|
||||
PackageTree packageTree = node.getPackage();
|
||||
if (packageTree != null) {
|
||||
packageTree.accept(this, p);
|
||||
}
|
||||
|
||||
node.getTypeDecls().forEach(it -> it.accept(this, p));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitPackage(PackageTree node, TreeVisitorContext p) {
|
||||
Tag tag =
|
||||
new Tag(
|
||||
TagKind.PACKAGE, node.getPackageName().toString(), p.getLocation(), p.getLine(node));
|
||||
|
||||
logger.finer(() -> "Package: " + tag);
|
||||
|
||||
tags.add(tag);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitClass(ClassTree node, TreeVisitorContext p) {
|
||||
Tag tag =
|
||||
new Tag(
|
||||
switch (node.getKind()) {
|
||||
case CLASS -> TagKind.CLASS;
|
||||
case INTERFACE -> TagKind.INTERFACE;
|
||||
case ENUM -> TagKind.ENUM;
|
||||
case ANNOTATION_TYPE -> TagKind.ANNOTATION;
|
||||
default -> {
|
||||
logger.warning("Unknown class kind " + node.getKind());
|
||||
yield TagKind.CLASS;
|
||||
}
|
||||
},
|
||||
node.getSimpleName().toString(),
|
||||
p.getLocation(),
|
||||
p.getLine(node));
|
||||
|
||||
logger.finer(() -> "Type: " + tag);
|
||||
|
||||
tags.add(tag);
|
||||
|
||||
node.getMembers().forEach(it -> it.accept(this, p));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitMethod(MethodTree node, TreeVisitorContext p) {
|
||||
Tag tag = new Tag(TagKind.METHOD, node.getName().toString(), p.getLocation(), p.getLine(node));
|
||||
logger.fine(() -> "Method: " + tag);
|
||||
tags.add(tag);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
57
src/main/java/xyz/naofal/jtags/TreeVisitorContext.java
Normal file
57
src/main/java/xyz/naofal/jtags/TreeVisitorContext.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package xyz.naofal.jtags;
|
||||
|
||||
import static xyz.naofal.jtags.JtagsLogger.logger;
|
||||
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class TreeVisitorContext {
|
||||
private static final int MAX_LINE_LENGTH = 4096;
|
||||
|
||||
final Trees trees;
|
||||
final SourcePositions sourcePositions;
|
||||
public CompilationUnitTree compilationUnitTree;
|
||||
|
||||
public TreeVisitorContext(Trees trees) {
|
||||
this.trees = trees;
|
||||
sourcePositions = trees.getSourcePositions();
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
assert compilationUnitTree != null;
|
||||
return Paths.get(".")
|
||||
.toAbsolutePath()
|
||||
.relativize(Paths.get(compilationUnitTree.getSourceFile().toUri()).toAbsolutePath())
|
||||
.toString();
|
||||
}
|
||||
|
||||
public String getLine(Tree node) {
|
||||
assert compilationUnitTree != null;
|
||||
long offset = sourcePositions.getStartPosition(compilationUnitTree, node);
|
||||
try (var reader = new BufferedReader(compilationUnitTree.getSourceFile().openReader(true))) {
|
||||
long skipped = 0;
|
||||
while (skipped <= offset) {
|
||||
reader.mark(MAX_LINE_LENGTH);
|
||||
String line = reader.readLine();
|
||||
if (line == null) break;
|
||||
int lineLength = line.length();
|
||||
if (lineLength >= MAX_LINE_LENGTH) {
|
||||
logger.severe("Line length exceeded");
|
||||
System.exit(1);
|
||||
}
|
||||
skipped += lineLength + 1;
|
||||
}
|
||||
reader.reset();
|
||||
return reader.readLine();
|
||||
} catch (IOException e) {
|
||||
logger.severe(e.toString());
|
||||
System.exit(1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/main/java/xyz/naofal/jtags/example/Example.java
Normal file
11
src/main/java/xyz/naofal/jtags/example/Example.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package xyz.naofal.jtags.example;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Example {}
|
||||
//
|
||||
Reference in New Issue
Block a user