diff --git a/src/main/java/xyz/naofal/jtags/ArchiveExtractor.java b/src/main/java/xyz/naofal/jtags/ArchiveExtractor.java new file mode 100644 index 0000000..89c81a9 --- /dev/null +++ b/src/main/java/xyz/naofal/jtags/ArchiveExtractor.java @@ -0,0 +1,45 @@ +package xyz.naofal.jtags; + +import static xyz.naofal.jtags.JtagsLogger.logger; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class ArchiveExtractor { + static List extractArchive(Path archive, Path destination, String pattern) { + logger.info("Extracting " + archive.toString() + "..."); + List extractedPaths = new ArrayList<>(); + Pattern compiledPattern = Pattern.compile(pattern); + try (InputStream in = new FileInputStream(archive.toFile()); + ZipInputStream zis = new ZipInputStream(in); ) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + logger.finest("Extracting " + entry.getName() + "..."); + if (!compiledPattern.matcher(entry.getName()).matches()) { + zis.closeEntry(); + entry = zis.getNextEntry(); + continue; + } + Path entryPath = destination.resolve(entry.getName()); + Files.createDirectories(entryPath.getParent()); + logger.finest(() -> "Extracting " + entryPath.toString() + "..."); + var bytes = zis.readAllBytes(); + Files.write(entryPath, bytes); + extractedPaths.add(entryPath.toString()); + zis.closeEntry(); + } + return extractedPaths; + } catch (IOException ex) { + logger.severe(ex.toString()); + return List.of(); + } + } +} diff --git a/src/main/java/xyz/naofal/jtags/Jtags.java b/src/main/java/xyz/naofal/jtags/Jtags.java index 029b730..80f3dbe 100644 --- a/src/main/java/xyz/naofal/jtags/Jtags.java +++ b/src/main/java/xyz/naofal/jtags/Jtags.java @@ -7,6 +7,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; public class Jtags { static class Options { @@ -17,6 +18,7 @@ public class Jtags { boolean excludeAnonymous = false; List> fields = List.of(TagField.StaticTag.class, TagField.Package.class, TagField.EnclosingType.class); + Optional extractPath = Optional.empty(); } public static void main(String[] args) { @@ -104,11 +106,25 @@ public class Jtags { System.exit(1); yield null; } - case String output -> Path.of(output); + case String output -> Path.of(output).toAbsolutePath(); }; logger.config("Writing tags to " + options.output.toString()); break; + case "-extract-dir": + options.extractPath = + switch (arguments.poll()) { + case null -> { + logger.severe("Expected argument after " + argument); + printUsage(); + System.exit(1); + yield null; + } + case String dir -> Optional.of(Path.of(dir).toAbsolutePath()); + }; + logger.config("Extracting JARs to " + options.extractPath.toString()); + break; + default: options.sources.add(argument); break; @@ -137,7 +153,7 @@ public class Jtags { Usage: jtags [options] Options: -o, -output Write tags to specified - Use - for standard output + Use - for standard output. Default: tags -lib Treat sources as third-party libraries (alias for -no-non-public -no-anonymous) -no-anonymous Exclude anonymous classes @@ -148,6 +164,7 @@ Options: s static tag p package t enclosing type + -extract-dir Extract JAR and ZIP archives to -h, -help Show this message """); } diff --git a/src/main/java/xyz/naofal/jtags/TagCollector.java b/src/main/java/xyz/naofal/jtags/TagCollector.java index a067429..b8849cd 100644 --- a/src/main/java/xyz/naofal/jtags/TagCollector.java +++ b/src/main/java/xyz/naofal/jtags/TagCollector.java @@ -6,7 +6,11 @@ import com.sun.source.tree.CompilationUnitTree; import com.sun.source.util.JavacTask; import com.sun.source.util.Trees; import java.io.IOException; +import java.nio.file.Path; import java.util.AbstractQueue; +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Stream; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; @@ -20,8 +24,10 @@ public class TagCollector { public static AbstractQueue collectTags(Options options) { try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) { - Iterable compilationUnits = - fileManager.getJavaFileObjects(options.sources.toArray(String[]::new)); + String[] sources = resolveSources(options); + logger.finest(() -> "Collecting tags from sources: " + Arrays.toString(sources)); + + Iterable compilationUnits = fileManager.getJavaFileObjects(sources); JavacTask task = (JavacTask) compiler.getTask(null, fileManager, null, null, null, compilationUnits); @@ -42,4 +48,37 @@ public class TagCollector { return null; } } + + static String[] resolveSources(Options options) { + return options.sources.stream() + .flatMap( + path -> + switch (path) { + case String _ when path.endsWith(".java") -> Stream.of(path); + case String _ when path.endsWith(".jar") || path.endsWith(".zip") -> { + if (options.extractPath.isEmpty()) { + logger.severe("Archive file specified, but no -extract-dir"); + System.exit(1); + } + Path archivePath = Path.of(path); + String archiveName = archivePath.getFileName().toString(); + yield ArchiveExtractor.extractArchive( + archivePath, + options + .extractPath + .get() + .resolve(archiveName.substring(0, archiveName.lastIndexOf('.'))), + "^.*\\.java$") + .stream(); + } + default -> { + logger.warning("Ignoring unsupported source: " + path); + yield Stream.of(); + } + }) + .filter(Objects::nonNull) + // TODO: support package-info and module-info + .filter(it -> !it.endsWith("package-info.java") && !it.endsWith("module-info.java")) + .toArray(String[]::new); + } } diff --git a/src/main/java/xyz/naofal/jtags/TagsWriter.java b/src/main/java/xyz/naofal/jtags/TagsWriter.java index 9f547da..f2fc3b9 100644 --- a/src/main/java/xyz/naofal/jtags/TagsWriter.java +++ b/src/main/java/xyz/naofal/jtags/TagsWriter.java @@ -17,6 +17,7 @@ public record TagsWriter(Options options) { private static final int MAX_PATTERN_LENGTH = 96; public boolean writeTagsFile(AbstractQueue tags) { + logger.fine("Writing tags to " + options().output.toString()); try (var outputStream = options().output.toString().equals("-") ? System.out diff --git a/src/main/java/xyz/naofal/jtags/TreeVisitor.java b/src/main/java/xyz/naofal/jtags/TreeVisitor.java index d7128d0..6146f2f 100644 --- a/src/main/java/xyz/naofal/jtags/TreeVisitor.java +++ b/src/main/java/xyz/naofal/jtags/TreeVisitor.java @@ -41,10 +41,9 @@ public class TreeVisitor extends TreePathScanner { @Override public Void visitPackage(PackageTree node, TreeVisitorContext p) { Tag tag = - new Tag( - TagKind.PACKAGE, node.getPackageName().toString(), p.getLocation().getParent(), ""); + new Tag(TagKind.PACKAGE, node.getPackageName().toString(), p.getLocation().getParent(), ""); - if (tags.contains(tag)) { + if (tags.contains(tag)) { return null; } diff --git a/src/main/java/xyz/naofal/jtags/TreeVisitorContext.java b/src/main/java/xyz/naofal/jtags/TreeVisitorContext.java index b4ef522..724a1a1 100644 --- a/src/main/java/xyz/naofal/jtags/TreeVisitorContext.java +++ b/src/main/java/xyz/naofal/jtags/TreeVisitorContext.java @@ -32,10 +32,12 @@ public class TreeVisitorContext { long offset = getOffsetInSource(compilationUnitTree, node); try (var reader = new BufferedReader(compilationUnitTree.getSourceFile().openReader(true))) { long skipped = 0; + String prevLine = ""; while (true) { String line = reader.readLine(); - if (line == null) throw new RuntimeException("Reached the end of the stream"); + if (line == null) return prevLine; skipped += line.length() + 1; + prevLine = line; if (skipped >= offset) return line; } } catch (Exception e) {