Skip to content

Commit

Permalink
Partially implement CodeFragments for source files - see #2, #6
Browse files Browse the repository at this point in the history
  • Loading branch information
LunNova authored Oct 2, 2018
1 parent b1acd57 commit a615d93
Show file tree
Hide file tree
Showing 32 changed files with 1,513 additions and 390 deletions.
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ minimallyCorrectDefaults {

dependencies {
testCompile "junit:junit:4.12"
//Can we make these dependencies optional? Reduced jar size if not using source for patching
compile 'org.ow2.asm:asm-debug-all:5.0.4'
compile 'com.github.javaparser:javaparser-core:3.2.4'
compile 'com.github.javaparser:javaparser-symbol-solver-core:3.6.24'
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.function.Function;
import java.util.stream.Stream;

import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;

import org.minimallycorrect.javatransformer.internal.util.CollectionUtil;
Expand Down Expand Up @@ -38,8 +39,10 @@ else if (member instanceof FieldInfo)
@Nullable
Type getSuperType();

@Contract(pure = true)
List<Type> getInterfaceTypes();

@Contract(pure = true)
@Nullable
default ClassMember get(ClassMember member) {
if (member instanceof MethodInfo)
Expand All @@ -50,6 +53,7 @@ else if (member instanceof FieldInfo)
throw new TransformationException("Can't get member of type " + member.getClass().getCanonicalName() + " in " + this);
}

@Contract(pure = true)
@Nullable
default MethodInfo get(MethodInfo like) {
for (MethodInfo methodInfo : CollectionUtil.iterable(getMethods())) {
Expand All @@ -60,6 +64,7 @@ default MethodInfo get(MethodInfo like) {
return null;
}

@Contract(pure = true)
@Nullable
default FieldInfo get(FieldInfo like) {
for (FieldInfo fieldInfo : CollectionUtil.iterable(getFields())) {
Expand All @@ -70,23 +75,33 @@ default FieldInfo get(FieldInfo like) {
return null;
}

@Contract(pure = true)
default Type getType() {
return Type.of(getName());
}

@Contract(pure = true)
Stream<MethodInfo> getMethods();

@Contract(pure = true)
Stream<FieldInfo> getFields();

@Contract(pure = true)
default Stream<MethodInfo> getConstructors() {
return getMethods().filter(MethodInfo::isConstructor);
}

@Contract(pure = true)
default Stream<ClassMember> getMembers() {
return Stream.concat(getFields(), getMethods());
}

default void accessFlags(Function<AccessFlags, AccessFlags> c) {
setAccessFlags(c.apply(getAccessFlags()));
}

@Override
default ClassInfo getClassInfo() {
return this;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.minimallycorrect.javatransformer.api;

import org.jetbrains.annotations.Contract;

public interface ClassMember extends Annotated, Accessible, HasCodeFragment, Named {
@Contract(pure = true)
ClassInfo getClassInfo();
}
195 changes: 45 additions & 150 deletions src/main/java/org/minimallycorrect/javatransformer/api/ClassPath.java
Original file line number Diff line number Diff line change
@@ -1,178 +1,73 @@
package org.minimallycorrect.javatransformer.api;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
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.Collection;
import java.util.HashSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.List;

import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.val;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.TypeDeclaration;

import org.minimallycorrect.javatransformer.internal.util.JVMUtil;
import org.minimallycorrect.javatransformer.internal.util.Joiner;

// TODO: make this faster by using dumb regexes instead of JavaParser?
// probably not worth doing
public class ClassPath {
private final HashSet<String> classes = new HashSet<>();
private final HashSet<Path> inputPaths = new HashSet<>();
private final ClassPath parent;
private boolean loaded;

private ClassPath(@Nullable ClassPath parent) {
this.parent = parent;
}

public ClassPath() {
this((ClassPath) null);
}

public ClassPath(Collection<Path> paths) {
this();
addPaths(paths);
}

public ClassPath createChildWithExtraPaths(Collection<Path> paths) {
val searchPath = new ClassPath(this);
searchPath.addPaths(paths);
return searchPath;
}

@Override
public String toString() {
initialise();
return "[" + parent.toString() + ", " + inputPaths + " classes:\n" + Joiner.on("\n").join(classes.stream().sorted()) + "]";
}
import org.minimallycorrect.javatransformer.internal.ClassPaths;

public interface ClassPath extends Iterable<ClassInfo> {
/**
* Returns whether the given class name exists in this class path
* Returns whether the given class name exists
*
* @param className class name in JLS format: package1.package2.ClassName, package1.package2.ClassName$InnerClass
* @param className class name in JLS format: {@code package1.package2.ClassName}, {@code package1.package2.ClassName$InnerClass}
* @return true if the class exists
*/
@Contract("null -> fail")
public boolean classExists(@NonNull String className) {
initialise();
return classes.contains(className) || (parent != null && parent.classExists(className));
@Contract(value = "null -> fail", pure = true)
default boolean classExists(@Nonnull String className) {
return getClassInfo(className) != null;
}

/**
* Adds a {@link Path} to this {@link ClassPath}
* Returns the class info for the given class
*
* @param path {@link Path} to add
* @return true if the path was added, false if the path already existed in this {@link ClassPath}
* This instance is only guaranteed to provide information required for type resolution. Other information such as method bodies and annotations may be stripped
*
* @param className class name in JLS format: {@code package1.package2.ClassName}, {@code package1.package2.ClassName$InnerClass}
* @return ClassInfo for the given name, or null if not found
*/
@Contract("null -> fail")
public boolean addPath(@NonNull Path path) {
path = path.normalize().toAbsolutePath();
val add = !parentHasPath(path) && inputPaths.add(path);
if (add && loaded)
loadPath(path);
return add;
}

@Contract("null -> fail")
public void addPaths(@NonNull Collection<Path> paths) {
for (Path path : paths)
addPath(path);
}
@Contract(value = "null -> fail", pure = true)
@Nullable
ClassInfo getClassInfo(@Nonnull String className);

/**
* @param path path must be normalized and absolute
* Adds the given paths to this {@link ClassPath}
*
* @param paths Paths to add
*/
private boolean parentHasPath(Path path) {
val parent = this.parent;
return parent != null && (parent.inputPaths.contains(path) || parent.parentHasPath(path));
}

private void findPaths(ZipEntry e, ZipInputStream zis) {
val entryName = e.getName();
if (entryName.endsWith(".java"))
findJavaPaths(zis);

if (entryName.endsWith(".class"))
classes.add(JVMUtil.fileNameToClassName(entryName));
}

private void findJavaPaths(ZipInputStream zis) {
val parsed = JavaParser.parse(new InputStream() {
public int read(@NonNull byte[] b, int off, int len) throws IOException {
return zis.read(b, off, len);
}

public void close() throws IOException {}

public int read() throws IOException {
return zis.read();
}
});
findJavaPaths(parsed);
default void addPaths(List<Path> paths) {
for (Path extraPath : paths)
addPath(extraPath);
}

private void findJavaPaths(CompilationUnit compilationUnit) {
val typeNames = compilationUnit.getTypes();
val packageDeclaration = compilationUnit.getPackageDeclaration().orElse(null);
val prefix = packageDeclaration == null ? "" : packageDeclaration.getNameAsString() + '.';
for (TypeDeclaration<?> typeDeclaration : typeNames)
findJavaPaths(typeDeclaration, prefix);
}
/**
* Adds a path to this {@link ClassPath}
*
* @param path Path to add
* @return True if the path was added, false if it was already in this classpath
* @throws UnsupportedOperationException if this {@link ClassPath} is immutable
*/
boolean addPath(Path path);

private void findJavaPaths(TypeDeclaration<?> typeDeclaration, String packagePrefix) {
val name = packagePrefix + typeDeclaration.getNameAsString();
classes.add(name);
for (val node : typeDeclaration.getChildNodes())
if (node instanceof TypeDeclaration)
findJavaPaths((TypeDeclaration<?>) node, name + '.');
}
/**
* Checks if the given path is in this {@link ClassPath}
*
* @param path Path to check
* @return True if this {@link ClassPath} contains the given path
*/
boolean hasPath(Path path);

private void initialise() {
if (loaded)
return;
loaded = true;
for (Path path : inputPaths)
loadPath(path);
@Contract(pure = true)
static @Nonnull ClassPath of(@Nonnull Path... paths) {
return of(ClassPaths.SystemClassPath.SYSTEM_CLASS_PATH, paths);
}

@SneakyThrows
private void loadPath(Path path) {
if (Files.isDirectory(path))
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
val entryName = path.relativize(file).toString().replace(File.separatorChar, '/');
if (entryName.endsWith(".java")) {
val parsed = JavaParser.parse(file);
findJavaPaths(parsed);
}
return super.visitFile(file, attrs);
}
});
else if (Files.isRegularFile(path))
try (val zis = new ZipInputStream(Files.newInputStream(path))) {
ZipEntry e;
while ((e = zis.getNextEntry()) != null) {
try {
findPaths(e, zis);
} finally {
zis.closeEntry();
}
}
}
@Contract(pure = true)
static @Nonnull ClassPath of(@Nullable ClassPath parent, Path... paths) {
return ClassPaths.of(parent, paths);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.minimallycorrect.javatransformer.api;

import java.util.Collections;
import java.util.List;

import lombok.val;

Expand All @@ -14,7 +15,7 @@ default CodeFragment.Body getCodeFragment() {
return null;
}

default <T extends CodeFragment> Iterable<T> findFragments(Class<T> fragmentType) {
default <T extends CodeFragment> List<T> findFragments(Class<T> fragmentType) {
val fragment = getCodeFragment();
return fragment == null ? Collections.emptyList() : fragment.findFragments(fragmentType);
}
Expand Down
Loading

0 comments on commit a615d93

Please sign in to comment.