Skip to content

Commit 142b1de

Browse files
authored
Remove runtime classpath scanning (#911)
* Remove runtime classpath scanning Operations, custom XStream types,and publishable classes are stored in registry files in the META-INF directory of the core project JAR. * Use annotation processor to generate class lists
1 parent a2c3264 commit 142b1de

File tree

57 files changed

+506
-160
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+506
-160
lines changed

annotation/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# GRIP Annotation Processor
2+
3+
This subproject contains an annotation processor used to generate manifest files in the core project,
4+
used by the GRIP runtime to discover operations, publishable data types, and aliases for XStream
5+
serialization for save files.
6+
7+
The annotation processor generates these files:
8+
9+
| Annotation | File |
10+
|---|---|
11+
| `@Description` | `/META-INF/operations` |
12+
| `@PublishableObject` | `/META-INF/publishables` |
13+
| `@XStreamAlias` | `/META-INF/xstream-aliases` |
14+
15+
Each file contains a list of the names of the classes annotated with the corresponding annotation,
16+
which is then read by the `MetaInfReader` class in the GRIP core module.

annotation/annotation.gradle.kts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
plugins {
2+
`java-library`
3+
}
4+
5+
repositories {
6+
mavenCentral()
7+
}
8+
9+
dependencies {
10+
compileOnly(group = "com.google.auto.service", name = "auto-service", version = "1.0-rc4")
11+
annotationProcessor(group = "com.google.auto.service", name = "auto-service", version = "1.0-rc4")
12+
}

core/src/main/java/edu/wpi/grip/core/Description.java renamed to annotation/src/main/java/edu/wpi/grip/annotation/operation/Description.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
package edu.wpi.grip.core;
1+
package edu.wpi.grip.annotation.operation;
22

33
import java.lang.annotation.ElementType;
44
import java.lang.annotation.Retention;
55
import java.lang.annotation.RetentionPolicy;
66
import java.lang.annotation.Target;
77

8-
import static edu.wpi.grip.core.OperationDescription.Category;
9-
import static edu.wpi.grip.core.OperationDescription.Category.MISCELLANEOUS;
8+
import static edu.wpi.grip.annotation.operation.OperationCategory.MISCELLANEOUS;
9+
1010

1111
/**
12-
* Annotates an {@link Operation} subclass to describe it. This annotation gets transformed into a
13-
* {@link OperationDescription}. All operation classes with this annotation will be automatically
12+
* Annotates an {@code Operation} subclass to describe it. This annotation gets transformed into a
13+
* {@code OperationDescription}. All operation classes with this annotation will be automatically
1414
* discovered and added to the palette at startup.
1515
*/
1616
@Target(ElementType.TYPE)
@@ -30,9 +30,9 @@
3030

3131
/**
3232
* The category the operation belongs to. Defaults to
33-
* {@link OperationDescription.Category#MISCELLANEOUS MISCELLANEOUS}.
33+
* {@link OperationCategory#MISCELLANEOUS MISCELLANEOUS}.
3434
*/
35-
Category category() default MISCELLANEOUS;
35+
OperationCategory category() default MISCELLANEOUS;
3636

3737
/**
3838
* All known aliases of the operation. If the name of the operation changes, the previous name
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package edu.wpi.grip.annotation.operation;
2+
3+
/**
4+
* The categories that entries can be in.
5+
*/
6+
public enum OperationCategory {
7+
IMAGE_PROCESSING,
8+
FEATURE_DETECTION,
9+
NETWORK,
10+
LOGICAL,
11+
OPENCV,
12+
MISCELLANEOUS,
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package edu.wpi.grip.annotation.operation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* Marks a type as being publishable by a network operation.
10+
*/
11+
@Target(ElementType.TYPE)
12+
@Retention(RetentionPolicy.SOURCE)
13+
public @interface PublishableObject {
14+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package edu.wpi.grip.annotation.processor;
2+
3+
import edu.wpi.grip.annotation.operation.Description;
4+
import edu.wpi.grip.annotation.operation.PublishableObject;
5+
6+
import com.google.auto.service.AutoService;
7+
import com.google.common.collect.ImmutableMap;
8+
9+
import java.io.IOException;
10+
import java.io.Writer;
11+
import java.util.Map;
12+
import java.util.Set;
13+
import java.util.stream.Collectors;
14+
15+
import javax.annotation.processing.AbstractProcessor;
16+
import javax.annotation.processing.Filer;
17+
import javax.annotation.processing.Processor;
18+
import javax.annotation.processing.RoundEnvironment;
19+
import javax.annotation.processing.SupportedAnnotationTypes;
20+
import javax.annotation.processing.SupportedSourceVersion;
21+
import javax.lang.model.SourceVersion;
22+
import javax.lang.model.element.Element;
23+
import javax.lang.model.element.TypeElement;
24+
import javax.lang.model.type.DeclaredType;
25+
import javax.lang.model.type.TypeKind;
26+
import javax.lang.model.util.SimpleTypeVisitor8;
27+
import javax.tools.Diagnostic;
28+
import javax.tools.FileObject;
29+
import javax.tools.StandardLocation;
30+
31+
/**
32+
* Processes elements with the GRIP annotations and generates class list files for them.
33+
*/
34+
@SupportedAnnotationTypes({
35+
"edu.wpi.grip.annotation.*",
36+
"com.thoughtworks.xstream.annotations.XStreamAlias"
37+
})
38+
@SupportedSourceVersion(SourceVersion.RELEASE_8)
39+
@AutoService(Processor.class)
40+
public class ClassListProcessor extends AbstractProcessor {
41+
42+
public static final String OPERATIONS_FILE_NAME = "operations";
43+
public static final String PUBLISHABLES_FILE_NAME = "publishables";
44+
public static final String XSTREAM_ALIASES_FILE_NAME = "xstream-aliases";
45+
46+
private final Map<String, String> fileNames = ImmutableMap.of(
47+
Description.class.getName(), OPERATIONS_FILE_NAME,
48+
PublishableObject.class.getName(), PUBLISHABLES_FILE_NAME,
49+
"com.thoughtworks.xstream.annotations.XStreamAlias", XSTREAM_ALIASES_FILE_NAME
50+
);
51+
52+
@Override
53+
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
54+
for (TypeElement annotation : annotations) {
55+
String fileName = fileNames.get(annotation.asType().toString());
56+
if (fileName != null) {
57+
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Processing " + annotation);
58+
createFile(fileName, classesAnnotatedWith(annotation, roundEnv));
59+
}
60+
}
61+
return false;
62+
}
63+
64+
private Iterable<String> classesAnnotatedWith(TypeElement element, RoundEnvironment roundEnv) {
65+
return roundEnv.getElementsAnnotatedWith(element)
66+
.stream()
67+
.map(Element::asType)
68+
.map(t -> t.accept(TypeNameExtractor.INSTANCE, null))
69+
.collect(Collectors.toList());
70+
}
71+
72+
private void createFile(String fileName, Iterable<String> classNames) {
73+
Filer filer = processingEnv.getFiler();
74+
75+
String resource = "META-INF/" + fileName;
76+
77+
try {
78+
FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", resource);
79+
try (Writer writer = fileObject.openWriter()) {
80+
for (String className : classNames) {
81+
writer.write(className);
82+
writer.write('\n');
83+
}
84+
}
85+
} catch (IOException e) {
86+
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
87+
"Unable to create resource file " + resource + ": " + e.getMessage());
88+
}
89+
}
90+
91+
private static final class TypeNameExtractor extends SimpleTypeVisitor8<String, Void> {
92+
93+
static final TypeNameExtractor INSTANCE = new TypeNameExtractor();
94+
95+
@Override
96+
public String visitDeclared(DeclaredType t, Void o) {
97+
String typeName = t.toString();
98+
if (typeName.contains("<")) {
99+
typeName = typeName.substring(0, typeName.indexOf('<'));
100+
}
101+
if (t.getEnclosingType().getKind() != TypeKind.NONE) {
102+
// Inner class, replace '.' with '$'
103+
int lastDot = typeName.lastIndexOf('.');
104+
String first = typeName.substring(0, lastDot);
105+
String second = typeName.substring(lastDot + 1);
106+
typeName = first + "$" + second;
107+
}
108+
return typeName;
109+
}
110+
}
111+
}

core/core.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ val os = osdetector.classifier.replace("osx", "macosx").replace("x86_32", "x86")
1616
val arch = osdetector.arch.replace("x86_64", "x64")
1717

1818
dependencies {
19+
api(project(":annotation"))
20+
annotationProcessor(project(":annotation"))
1921
api(group = "com.google.code.findbugs", name = "jsr305", version = "3.0.1")
2022
api(group = "org.bytedeco", name = "javacv", version = "1.1")
2123
api(group = "org.bytedeco.javacpp-presets", name = "opencv", version = "3.0.0-1.1")

core/src/main/java/edu/wpi/grip/core/OperationDescription.java

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package edu.wpi.grip.core;
22

3+
import edu.wpi.grip.annotation.operation.Description;
4+
import edu.wpi.grip.annotation.operation.OperationCategory;
35
import edu.wpi.grip.core.util.Icon;
46

57
import com.google.common.base.MoreObjects;
@@ -23,7 +25,7 @@ public class OperationDescription {
2325

2426
private final String name;
2527
private final String summary;
26-
private final Category category;
28+
private final OperationCategory category;
2729
private final Icon icon;
2830
private final ImmutableSet<String> aliases;
2931

@@ -59,25 +61,26 @@ public static OperationDescription from(Class<? extends Operation> clazz) {
5961
*/
6062
private OperationDescription(String name,
6163
String summary,
62-
Category category,
64+
OperationCategory category,
6365
Icon icon,
6466
Set<String> aliases) {
6567
this.name = checkNotNull(name, "Name cannot be null");
6668
this.summary = checkNotNull(summary, "Summary cannot be null");
67-
this.category = checkNotNull(category, "Category cannot be null");
69+
this.category = checkNotNull(category, "OperationCategory cannot be null");
6870
this.icon = icon; // This is allowed to be null
6971
this.aliases = ImmutableSet.copyOf(checkNotNull(aliases, "Aliases cannot be null"));
7072
}
7173

7274
/**
7375
* Creates a new {@link Builder} instance to create a new {@code OperationDescription} object. The
74-
* created descriptor has a default category of {@link Category#MISCELLANEOUS MISCELLANEOUS} and
75-
* no icon; use the {@link Builder#category(Category) .category()} and {@link Builder#icon(Icon)
76-
* .icon()} methods to override the default values.
76+
* created descriptor has a default category of
77+
* {@link OperationCategory#MISCELLANEOUS MISCELLANEOUS} and no icon; use the
78+
* {@link Builder#category(OperationCategory) .category()} and {@link Builder#icon(Icon) .icon()}
79+
* methods to override the default values.
7780
*/
7881
public static Builder builder() {
7982
return new Builder()
80-
.category(Category.MISCELLANEOUS)
83+
.category(OperationCategory.MISCELLANEOUS)
8184
.icon(null);
8285
}
8386

@@ -98,7 +101,7 @@ public String summary() {
98101
/**
99102
* @return What category the operation falls under. This is used to organize them in the GUI.
100103
*/
101-
public Category category() {
104+
public OperationCategory category() {
102105
return category;
103106
}
104107

@@ -155,25 +158,13 @@ public String toString() {
155158
.toString();
156159
}
157160

158-
/**
159-
* The categories that entries can be in.
160-
*/
161-
public enum Category {
162-
IMAGE_PROCESSING,
163-
FEATURE_DETECTION,
164-
NETWORK,
165-
LOGICAL,
166-
OPENCV,
167-
MISCELLANEOUS,
168-
}
169-
170161
/**
171162
* Builder class for {@code OperationDescription}.
172163
*/
173164
public static final class Builder {
174165
private String name;
175166
private String summary = "PLEASE PROVIDE A DESCRIPTION TO THE OPERATION DESCRIPTION!";
176-
private Category category;
167+
private OperationCategory category;
177168
private Icon icon;
178169
private ImmutableSet<String> aliases = ImmutableSet.of(); // default to empty Set to
179170
// avoid NPE if not assigned
@@ -203,7 +194,7 @@ public Builder summary(String summary) {
203194
/**
204195
* Sets the category.
205196
*/
206-
public Builder category(Category category) {
197+
public Builder category(OperationCategory category) {
207198
this.category = checkNotNull(category);
208199
return this;
209200
}

core/src/main/java/edu/wpi/grip/core/operations/Operations.java

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package edu.wpi.grip.core.operations;
22

3-
import edu.wpi.grip.core.Description;
3+
import edu.wpi.grip.annotation.operation.Description;
4+
import edu.wpi.grip.annotation.processor.ClassListProcessor;
45
import edu.wpi.grip.core.FileManager;
56
import edu.wpi.grip.core.Operation;
67
import edu.wpi.grip.core.OperationDescription;
@@ -17,11 +18,11 @@
1718
import edu.wpi.grip.core.operations.network.ros.ROSNetworkPublisherFactory;
1819
import edu.wpi.grip.core.operations.network.ros.ROSPublishOperation;
1920
import edu.wpi.grip.core.sockets.InputSocket;
21+
import edu.wpi.grip.core.util.MetaInfReader;
2022

2123
import com.google.common.annotations.VisibleForTesting;
2224
import com.google.common.collect.ImmutableList;
2325
import com.google.common.eventbus.EventBus;
24-
import com.google.common.reflect.ClassPath;
2526
import com.google.inject.Inject;
2627
import com.google.inject.Injector;
2728
import com.google.inject.Singleton;
@@ -56,7 +57,7 @@ public class Operations {
5657
private final ROSNetworkPublisherFactory rosManager; //NOPMD
5758
private final ImmutableList<OperationMetaData> operations;
5859

59-
private List<Class<Publishable>> publishableTypes = null;
60+
private List<Class<? extends Publishable>> publishableTypes = null;
6061

6162
/**
6263
* Creates a new Operations instance. This should only be used in tests.
@@ -109,16 +110,9 @@ private OperationDescription descriptionFor(Class<? extends Operation> clazz) {
109110
return OperationDescription.from(clazz.getAnnotation(Description.class));
110111
}
111112

112-
@SuppressWarnings("unchecked")
113113
private List<OperationMetaData> createBasicOperations() {
114114
try {
115-
ClassPath cp = ClassPath.from(getClass().getClassLoader());
116-
return cp.getAllClasses().stream()
117-
.filter(ci -> ci.getName().startsWith("edu.wpi.grip.core.operations"))
118-
.map(ClassPath.ClassInfo::load)
119-
.filter(Operation.class::isAssignableFrom)
120-
.map(c -> (Class<? extends Operation>) c)
121-
.filter(c -> c.isAnnotationPresent(Description.class))
115+
return MetaInfReader.<Operation>readClasses(ClassListProcessor.OPERATIONS_FILE_NAME)
122116
.map(c -> new OperationMetaData(descriptionFor(c), () -> injector.getInstance(c)))
123117
.collect(Collectors.toList());
124118
} catch (IOException e) {
@@ -131,21 +125,12 @@ private List<OperationMetaData> createBasicOperations() {
131125
* Finds all subclasses of {@link Publishable} in {@code edu.wpi.grip.core.operation}.
132126
*/
133127
@SuppressWarnings("unchecked")
134-
private List<Class<Publishable>> findPublishables() {
128+
private List<Class<? extends Publishable>> findPublishables() {
135129
if (publishableTypes == null) {
136130
// Only need to search once
137131
try {
138-
ClassPath cp = ClassPath.from(getClass().getClassLoader());
139-
publishableTypes = cp.getAllClasses().stream()
140-
// only look in our namespace (don't want to wade through tens of thousands of classes)
141-
.filter(ci -> ci.getName().startsWith("edu.wpi.grip.core.operation"))
142-
.map(ClassPath.ClassInfo::load)
143-
.filter(Publishable.class::isAssignableFrom)
144-
// only accept concrete top-level subclasses
145-
.filter(c -> !c.isAnonymousClass() && !c.isInterface() && !c.isLocalClass()
146-
&& !c.isMemberClass())
147-
.filter(c -> Modifier.isPublic(c.getModifiers()))
148-
.map(c -> (Class<Publishable>) c)
132+
String fileName = ClassListProcessor.PUBLISHABLES_FILE_NAME;
133+
publishableTypes = MetaInfReader.<Publishable>readClasses(fileName)
149134
.collect(Collectors.toList());
150135
} catch (IOException e) {
151136
logger.log(Level.WARNING, "Could not find the publishable types.", e);

core/src/main/java/edu/wpi/grip/core/operations/PythonScriptOperation.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package edu.wpi.grip.core.operations;
22

3+
import edu.wpi.grip.annotation.operation.OperationCategory;
34
import edu.wpi.grip.core.Operation;
45
import edu.wpi.grip.core.OperationDescription;
56
import edu.wpi.grip.core.sockets.InputSocket;
@@ -81,7 +82,7 @@ public static OperationDescription descriptionFor(PythonScriptFile pythonScriptF
8182
.name(pythonScriptFile.name())
8283
.summary(pythonScriptFile.summary())
8384
.icon(Icon.iconStream("python"))
84-
.category(OperationDescription.Category.MISCELLANEOUS)
85+
.category(OperationCategory.MISCELLANEOUS)
8586
.build();
8687
}
8788

0 commit comments

Comments
 (0)