org.jetbrains.java.decompiler.main.extern.IContextSource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vineflower Show documentation
Show all versions of vineflower Show documentation
Modern Java & JVM language decompiler aiming to be as accurate as possible, with an emphasis on output quality.
The newest version!
// Copyright 2000-2022 JetBrains s.r.o. and ForgeFlower contributors Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.java.decompiler.main.extern;
import static java.util.Objects.requireNonNull;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* A specific type of context unit.
*
* Implementations do not need to cache the results of any provided methods.
*/
public interface IContextSource {
/**
* The file extension for class files.
*/
static String CLASS_SUFFIX = ".class";
/**
* Get a human-readable name to identify this context source.
*
* @return a human-readable name
*/
String getName();
/**
* Get a listing of all entries in this context unit.
*
* @return the entries in this unit
*/
Entries getEntries();
/**
* Returns whether this context source is lazy. A lazy context source may return {@linkplain Entries#EMPTY} from
* {@linkplain #getEntries()} in case the {@code getEntries()} implementation would be too slow. This may come at a
* small runtime cost.
*
* Only libraries can be lazy. This method has no effect on sources.
*
* @return true if this context source is lazy, false otherwise
*/
default boolean isLazy() {
return false;
}
/**
* Returns whether a class exists in this source.
*
* @param className the class name, with no trailing {@code /}
* @return true if the class exists, false otherwise
* @throws IOException if an error is encountered while checking if the class exists
*/
default boolean hasClass(final String className) throws IOException {
return getClassBytes(className) != null;
}
/**
* Get the full bytes for a class's contents.
*
* @param className the class name, with no trailing {@code /}
* @return the bytes, or {@code null} if no class with that name is present
* @throws IOException if an error is encountered while reading the class data
*/
default byte[] getClassBytes(final String className) throws IOException {
final InputStream is = this.getInputStream(className + CLASS_SUFFIX);
if (is == null)
return null;
try (is) {
return is.readAllBytes();
}
}
/**
* Get an input stream for a specific resource.
*
* This will return {@code null} if a directory is requested.
*
* @param resource the resource to request
* @return an input stream
* @throws IOException if an input stream could not be opened
*/
default InputStream getInputStream(final Entry resource) throws IOException {
return this.getInputStream(resource.path());
}
/**
* Get an input stream for a specific resource.
*
* This will return {@code null} if a directory is requested.
*
* @param resource the resource to request
* @return an input stream
* @throws IOException if an input stream could not be opened
*/
InputStream getInputStream(final String resource) throws IOException;
/**
* Create a sink that can write the decompiled output from this context element.
*
* If this context source type does not support writing, return a null sink.
*
* @param saver the source result saver for this decompiler, for delegation
* @return the output sink, or null if unwritable
*/
default /* @Nullable */ IOutputSink createOutputSink(final IResultSaver saver) {
return null;
}
/**
* A collector for output derived from this specific context entry.
*/
interface IOutputSink extends AutoCloseable {
/**
* Begin this entry, performing any necessary setup work such as creating an archive
*/
void begin();
/**
* Write a class to this entry
*
* @param qualifiedName the qualified name of the class
* @param fileName the file name of the class, relative to its source
* @param content the class text content
* @param mapping a flat array of pairs of (input line number, output line number), null when -bsm=0
*/
void acceptClass(final String qualifiedName, final String fileName, final String content, final int[] mapping);
/**
* Create a directory in this output location.
*
* @param directory the directory to create
*/
void acceptDirectory(final String directory);
/**
* Accept other files, which should be copied directly through from the source.
*
* @param path the path
*/
void acceptOther(final String path);
@Override
void close() throws IOException;
}
/**
* All entries in the context unit.
*
* @param classes class names, with no {@value #CLASS_SUFFIX} suffix
* @param directories directories, with no trailing {@code /}
* @param others other entries
* @param childContexts contexts discovered within this context
*/
public static final class Entries {
public static final Entries EMPTY = new Entries(List.of(), List.of(), List.of(), List.of());
private final List classes;
private final List directories;
private final List others;
private final List childContexts;
public Entries(List classes, List directories, List others) {
this(classes, directories, others, List.of());
}
public Entries(List classes, List directories, List others, List childContexts) {
// defensive copy
this.classes = List.copyOf(classes);
this.directories = List.copyOf(directories);
this.others = List.copyOf(others);
this.childContexts = List.copyOf(childContexts);
}
public List classes() {
return this.classes;
}
public List directories() {
return this.directories;
}
public List others() {
return this.others;
}
public List childContexts() {
return this.childContexts;
}
}
/**
* An entry in a context unit, which may be a multirelease variant.
*
* @param basePath the path of the entry, with any multirelease variant stripped
* @param multirelease the multirelease target version, or {@value #BASE_VERSION} to indicate this entry is not part of a multirelease variant
*/
public static final class Entry {
public static final int BASE_VERSION = -1;
private static final String MULTIRELEASE_PREFIX = "META-INF/versions/";
private final String basePath;
private final int multirelease;
/**
* Parse an entry from a raw jar path.
*
* @param path the path to parse
* @return an entry, which may indicate a multirelease resource
*/
public static Entry parse(final String path) {
if (path.startsWith(MULTIRELEASE_PREFIX)) {
final int nextSlash = path.indexOf('/', MULTIRELEASE_PREFIX.length());
if (nextSlash == -1) return new Entry(path, BASE_VERSION);
final String version = path.substring(MULTIRELEASE_PREFIX.length(), nextSlash);
try {
return new Entry(path.substring(nextSlash), Integer.parseInt(version));
} catch (final NumberFormatException ex) {
// unversioned
}
}
return new Entry(path, BASE_VERSION);
}
/**
* Create an entry at the base version, without attempting to parse any multirelease information.
*
* @param path the path to test
* @return a new entry
*/
public static Entry atBase(final String path) {
return new Entry(path, BASE_VERSION);
}
public Entry(String basePath, int multirelease) {
this.basePath = requireNonNull(basePath, "basePath");
if (multirelease != -1 && multirelease < 9) {
throw new IllegalArgumentException("A multirelease variant must target a Java runtime >= 9");
}
this.multirelease = multirelease;
}
public String basePath() {
return this.basePath;
}
public int multirelease() {
return this.multirelease;
}
public String path() {
if (this.multirelease == BASE_VERSION) {
return this.basePath();
} else {
return MULTIRELEASE_PREFIX + Integer.toString(this.multirelease) + '/' + this.basePath;
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy