Skip to content

Commit 3a3548e

Browse files
authored
Partially implement CodeFragments for source files - see #2, #6
1 parent 09bde49 commit 3a3548e

32 files changed

+1513
-390
lines changed

build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ minimallyCorrectDefaults {
2121

2222
dependencies {
2323
testCompile "junit:junit:4.12"
24+
//Can we make these dependencies optional? Reduced jar size if not using source for patching
2425
compile 'org.ow2.asm:asm-debug-all:5.0.4'
25-
compile 'com.github.javaparser:javaparser-core:3.2.4'
26+
compile 'com.github.javaparser:javaparser-symbol-solver-core:3.6.24'
2627
}

src/main/java/org/minimallycorrect/javatransformer/api/ClassInfo.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.function.Function;
55
import java.util.stream.Stream;
66

7+
import org.jetbrains.annotations.Contract;
78
import org.jetbrains.annotations.Nullable;
89

910
import org.minimallycorrect.javatransformer.internal.util.CollectionUtil;
@@ -38,8 +39,10 @@ else if (member instanceof FieldInfo)
3839
@Nullable
3940
Type getSuperType();
4041

42+
@Contract(pure = true)
4143
List<Type> getInterfaceTypes();
4244

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

56+
@Contract(pure = true)
5357
@Nullable
5458
default MethodInfo get(MethodInfo like) {
5559
for (MethodInfo methodInfo : CollectionUtil.iterable(getMethods())) {
@@ -60,6 +64,7 @@ default MethodInfo get(MethodInfo like) {
6064
return null;
6165
}
6266

67+
@Contract(pure = true)
6368
@Nullable
6469
default FieldInfo get(FieldInfo like) {
6570
for (FieldInfo fieldInfo : CollectionUtil.iterable(getFields())) {
@@ -70,23 +75,33 @@ default FieldInfo get(FieldInfo like) {
7075
return null;
7176
}
7277

78+
@Contract(pure = true)
7379
default Type getType() {
7480
return Type.of(getName());
7581
}
7682

83+
@Contract(pure = true)
7784
Stream<MethodInfo> getMethods();
7885

86+
@Contract(pure = true)
7987
Stream<FieldInfo> getFields();
8088

89+
@Contract(pure = true)
8190
default Stream<MethodInfo> getConstructors() {
8291
return getMethods().filter(MethodInfo::isConstructor);
8392
}
8493

94+
@Contract(pure = true)
8595
default Stream<ClassMember> getMembers() {
8696
return Stream.concat(getFields(), getMethods());
8797
}
8898

8999
default void accessFlags(Function<AccessFlags, AccessFlags> c) {
90100
setAccessFlags(c.apply(getAccessFlags()));
91101
}
102+
103+
@Override
104+
default ClassInfo getClassInfo() {
105+
return this;
106+
}
92107
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.minimallycorrect.javatransformer.api;
22

3+
import org.jetbrains.annotations.Contract;
4+
35
public interface ClassMember extends Annotated, Accessible, HasCodeFragment, Named {
6+
@Contract(pure = true)
47
ClassInfo getClassInfo();
58
}
Lines changed: 45 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,178 +1,73 @@
11
package org.minimallycorrect.javatransformer.api;
22

3-
import java.io.File;
4-
import java.io.IOException;
5-
import java.io.InputStream;
6-
import java.nio.file.FileVisitResult;
7-
import java.nio.file.Files;
83
import java.nio.file.Path;
9-
import java.nio.file.SimpleFileVisitor;
10-
import java.nio.file.attribute.BasicFileAttributes;
11-
import java.util.Collection;
12-
import java.util.HashSet;
13-
import java.util.zip.ZipEntry;
14-
import java.util.zip.ZipInputStream;
4+
import java.util.List;
155

16-
import lombok.NonNull;
17-
import lombok.SneakyThrows;
18-
import lombok.val;
6+
import javax.annotation.Nonnull;
7+
import javax.annotation.Nullable;
198

209
import org.jetbrains.annotations.Contract;
21-
import org.jetbrains.annotations.Nullable;
2210

23-
import com.github.javaparser.JavaParser;
24-
import com.github.javaparser.ast.CompilationUnit;
25-
import com.github.javaparser.ast.body.TypeDeclaration;
26-
27-
import org.minimallycorrect.javatransformer.internal.util.JVMUtil;
28-
import org.minimallycorrect.javatransformer.internal.util.Joiner;
29-
30-
// TODO: make this faster by using dumb regexes instead of JavaParser?
31-
// probably not worth doing
32-
public class ClassPath {
33-
private final HashSet<String> classes = new HashSet<>();
34-
private final HashSet<Path> inputPaths = new HashSet<>();
35-
private final ClassPath parent;
36-
private boolean loaded;
37-
38-
private ClassPath(@Nullable ClassPath parent) {
39-
this.parent = parent;
40-
}
41-
42-
public ClassPath() {
43-
this((ClassPath) null);
44-
}
45-
46-
public ClassPath(Collection<Path> paths) {
47-
this();
48-
addPaths(paths);
49-
}
50-
51-
public ClassPath createChildWithExtraPaths(Collection<Path> paths) {
52-
val searchPath = new ClassPath(this);
53-
searchPath.addPaths(paths);
54-
return searchPath;
55-
}
56-
57-
@Override
58-
public String toString() {
59-
initialise();
60-
return "[" + parent.toString() + ", " + inputPaths + " classes:\n" + Joiner.on("\n").join(classes.stream().sorted()) + "]";
61-
}
11+
import org.minimallycorrect.javatransformer.internal.ClassPaths;
6212

13+
public interface ClassPath extends Iterable<ClassInfo> {
6314
/**
64-
* Returns whether the given class name exists in this class path
15+
* Returns whether the given class name exists
6516
*
66-
* @param className class name in JLS format: package1.package2.ClassName, package1.package2.ClassName$InnerClass
17+
* @param className class name in JLS format: {@code package1.package2.ClassName}, {@code package1.package2.ClassName$InnerClass}
6718
* @return true if the class exists
6819
*/
69-
@Contract("null -> fail")
70-
public boolean classExists(@NonNull String className) {
71-
initialise();
72-
return classes.contains(className) || (parent != null && parent.classExists(className));
20+
@Contract(value = "null -> fail", pure = true)
21+
default boolean classExists(@Nonnull String className) {
22+
return getClassInfo(className) != null;
7323
}
7424

7525
/**
76-
* Adds a {@link Path} to this {@link ClassPath}
26+
* Returns the class info for the given class
7727
*
78-
* @param path {@link Path} to add
79-
* @return true if the path was added, false if the path already existed in this {@link ClassPath}
28+
* This instance is only guaranteed to provide information required for type resolution. Other information such as method bodies and annotations may be stripped
29+
*
30+
* @param className class name in JLS format: {@code package1.package2.ClassName}, {@code package1.package2.ClassName$InnerClass}
31+
* @return ClassInfo for the given name, or null if not found
8032
*/
81-
@Contract("null -> fail")
82-
public boolean addPath(@NonNull Path path) {
83-
path = path.normalize().toAbsolutePath();
84-
val add = !parentHasPath(path) && inputPaths.add(path);
85-
if (add && loaded)
86-
loadPath(path);
87-
return add;
88-
}
89-
90-
@Contract("null -> fail")
91-
public void addPaths(@NonNull Collection<Path> paths) {
92-
for (Path path : paths)
93-
addPath(path);
94-
}
33+
@Contract(value = "null -> fail", pure = true)
34+
@Nullable
35+
ClassInfo getClassInfo(@Nonnull String className);
9536

9637
/**
97-
* @param path path must be normalized and absolute
38+
* Adds the given paths to this {@link ClassPath}
39+
*
40+
* @param paths Paths to add
9841
*/
99-
private boolean parentHasPath(Path path) {
100-
val parent = this.parent;
101-
return parent != null && (parent.inputPaths.contains(path) || parent.parentHasPath(path));
102-
}
103-
104-
private void findPaths(ZipEntry e, ZipInputStream zis) {
105-
val entryName = e.getName();
106-
if (entryName.endsWith(".java"))
107-
findJavaPaths(zis);
108-
109-
if (entryName.endsWith(".class"))
110-
classes.add(JVMUtil.fileNameToClassName(entryName));
111-
}
112-
113-
private void findJavaPaths(ZipInputStream zis) {
114-
val parsed = JavaParser.parse(new InputStream() {
115-
public int read(@NonNull byte[] b, int off, int len) throws IOException {
116-
return zis.read(b, off, len);
117-
}
118-
119-
public void close() throws IOException {}
120-
121-
public int read() throws IOException {
122-
return zis.read();
123-
}
124-
});
125-
findJavaPaths(parsed);
42+
default void addPaths(List<Path> paths) {
43+
for (Path extraPath : paths)
44+
addPath(extraPath);
12645
}
12746

128-
private void findJavaPaths(CompilationUnit compilationUnit) {
129-
val typeNames = compilationUnit.getTypes();
130-
val packageDeclaration = compilationUnit.getPackageDeclaration().orElse(null);
131-
val prefix = packageDeclaration == null ? "" : packageDeclaration.getNameAsString() + '.';
132-
for (TypeDeclaration<?> typeDeclaration : typeNames)
133-
findJavaPaths(typeDeclaration, prefix);
134-
}
47+
/**
48+
* Adds a path to this {@link ClassPath}
49+
*
50+
* @param path Path to add
51+
* @return True if the path was added, false if it was already in this classpath
52+
* @throws UnsupportedOperationException if this {@link ClassPath} is immutable
53+
*/
54+
boolean addPath(Path path);
13555

136-
private void findJavaPaths(TypeDeclaration<?> typeDeclaration, String packagePrefix) {
137-
val name = packagePrefix + typeDeclaration.getNameAsString();
138-
classes.add(name);
139-
for (val node : typeDeclaration.getChildNodes())
140-
if (node instanceof TypeDeclaration)
141-
findJavaPaths((TypeDeclaration<?>) node, name + '.');
142-
}
56+
/**
57+
* Checks if the given path is in this {@link ClassPath}
58+
*
59+
* @param path Path to check
60+
* @return True if this {@link ClassPath} contains the given path
61+
*/
62+
boolean hasPath(Path path);
14363

144-
private void initialise() {
145-
if (loaded)
146-
return;
147-
loaded = true;
148-
for (Path path : inputPaths)
149-
loadPath(path);
64+
@Contract(pure = true)
65+
static @Nonnull ClassPath of(@Nonnull Path... paths) {
66+
return of(ClassPaths.SystemClassPath.SYSTEM_CLASS_PATH, paths);
15067
}
15168

152-
@SneakyThrows
153-
private void loadPath(Path path) {
154-
if (Files.isDirectory(path))
155-
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
156-
@Override
157-
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
158-
val entryName = path.relativize(file).toString().replace(File.separatorChar, '/');
159-
if (entryName.endsWith(".java")) {
160-
val parsed = JavaParser.parse(file);
161-
findJavaPaths(parsed);
162-
}
163-
return super.visitFile(file, attrs);
164-
}
165-
});
166-
else if (Files.isRegularFile(path))
167-
try (val zis = new ZipInputStream(Files.newInputStream(path))) {
168-
ZipEntry e;
169-
while ((e = zis.getNextEntry()) != null) {
170-
try {
171-
findPaths(e, zis);
172-
} finally {
173-
zis.closeEntry();
174-
}
175-
}
176-
}
69+
@Contract(pure = true)
70+
static @Nonnull ClassPath of(@Nullable ClassPath parent, Path... paths) {
71+
return ClassPaths.of(parent, paths);
17772
}
17873
}

src/main/java/org/minimallycorrect/javatransformer/api/HasCodeFragment.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.minimallycorrect.javatransformer.api;
22

33
import java.util.Collections;
4+
import java.util.List;
45

56
import lombok.val;
67

@@ -14,7 +15,7 @@ default CodeFragment.Body getCodeFragment() {
1415
return null;
1516
}
1617

17-
default <T extends CodeFragment> Iterable<T> findFragments(Class<T> fragmentType) {
18+
default <T extends CodeFragment> List<T> findFragments(Class<T> fragmentType) {
1819
val fragment = getCodeFragment();
1920
return fragment == null ? Collections.emptyList() : fragment.findFragments(fragmentType);
2021
}

0 commit comments

Comments
 (0)