Extract tags from JAR and ZIP archives

This commit is contained in:
2025-04-02 23:29:17 +03:00
parent 09c972a92e
commit eab4f44e51
6 changed files with 111 additions and 8 deletions

View File

@@ -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<String> extractArchive(Path archive, Path destination, String pattern) {
logger.info("Extracting " + archive.toString() + "...");
List<String> 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();
}
}
}

View File

@@ -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<Class<? extends TagField>> fields =
List.of(TagField.StaticTag.class, TagField.Package.class, TagField.EnclosingType.class);
Optional<Path> 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] <sources...>
Options:
-o, -output <file> Write tags to specified <file>
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 <dir> Extract JAR and ZIP archives to <dir>
-h, -help Show this message
""");
}

View File

@@ -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<Tag> collectTags(Options options) {
try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) {
Iterable<? extends JavaFileObject> compilationUnits =
fileManager.getJavaFileObjects(options.sources.toArray(String[]::new));
String[] sources = resolveSources(options);
logger.finest(() -> "Collecting tags from sources: " + Arrays.toString(sources));
Iterable<? extends JavaFileObject> 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);
}
}

View File

@@ -17,6 +17,7 @@ public record TagsWriter(Options options) {
private static final int MAX_PATTERN_LENGTH = 96;
public boolean writeTagsFile(AbstractQueue<Tag> tags) {
logger.fine("Writing tags to " + options().output.toString());
try (var outputStream =
options().output.toString().equals("-")
? System.out

View File

@@ -41,10 +41,9 @@ public class TreeVisitor extends TreePathScanner<Void, TreeVisitorContext> {
@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;
}

View File

@@ -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) {