Skip to content

Commit f6e5b38

Browse files
authored
feat(api)!: add plugin bootstrap (#265)
* Add ChameleonPluginBoostrap * Remove use of KyoriPowered/blossom * Tidy up some Javadoc * Make all platform Chameleon #create methods return their corresponding bootstrap class directly, allowing for potential platform-specific methods in the future. * Rename Chameleon#getDataFolder to Chameleon#getDataDirectory to align with platforms * Update Luis' username (SLLCoding -> LooFifteen) * Update example to use another package and use correct relocations * Update example to show dispatching custom events in the main class BREAKING CHANGE: Platform Chameleon #create methods no longer accept the plugin class, instead a plugin bootstrap is required. The easiest plugin bootstrap implementation would be MyPlugin::new.
1 parent c58c887 commit f6e5b38

File tree

47 files changed

+601
-447
lines changed

Some content is hidden

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

47 files changed

+601
-447
lines changed

CONTRIBUTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,4 @@ If you have spotted a problem or mistake in someone else's pull request, feel fr
108108
## Supporting the authors
109109
If you wish to support this project in a way other than reporting bugs, suggesting ideas, or contributing code, the authors accept donations via GitHub Sponsors and Ko-fi, these donations go towards allowing the authors to spend more time working on this project, or paying for infrastructure for the author's personal projects.
110110
- [Joshua (joshuasing)](https://github.com/sponsors/joshuasing)
111-
- [Luis (SLLCoding)](https://ko-fi.com/SLLCoding)
111+
- [Luis (LooFifteen)](https://ko-fi.com/loofifteen)

SECURITY.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ address security vulnerabilities that affect the most recent version of this fra
1010

1111
| Version | Supported |
1212
|---------------------|--------------------|
13-
| `0.16.0-SNAPSHOT` | :white_check_mark: |
14-
| < `0.16.0-SNAPSHOT` | :x: |
13+
| `0.17.0-SNAPSHOT` | :white_check_mark: |
14+
| < `0.17.0-SNAPSHOT` | :x: |
1515

1616
### Reporting a Vulnerability
1717

annotations/src/main/java/dev/hypera/chameleon/annotations/Plugin.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*/
2424
package dev.hypera.chameleon.annotations;
2525

26+
import dev.hypera.chameleon.ChameleonPluginBootstrap;
2627
import java.lang.annotation.ElementType;
2728
import java.lang.annotation.Retention;
2829
import java.lang.annotation.RetentionPolicy;
@@ -58,7 +59,7 @@
5859
@NotNull String version();
5960

6061
/**
61-
* The plugin's description, generally a short explaination of what the plugin is used for.
62+
* The plugin's description, generally a short explanation of what the plugin is used for.
6263
*
6364
* @return the plugin's description, or an empty string.
6465
*/
@@ -94,4 +95,13 @@
9495
*/
9596
@NotNull String[] platforms() default {};
9697

98+
/**
99+
* Returns the plugin bootstrap to use when bootstrapping Chameleon.
100+
* <p><strong>If this is not provided, the annotated class must have a public constructor with a
101+
* single Chameleon parameter.</strong></p>
102+
*
103+
* @return plugin bootstrap class.
104+
*/
105+
@NotNull Class<? extends ChameleonPluginBootstrap> bootstrap() default ChameleonPluginBootstrap.class;
106+
97107
}

annotations/src/main/java/dev/hypera/chameleon/annotations/processing/ChameleonAnnotationProcessor.java

+74-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
*/
2424
package dev.hypera.chameleon.annotations.processing;
2525

26+
import dev.hypera.chameleon.Chameleon;
27+
import dev.hypera.chameleon.ChameleonPluginBootstrap;
2628
import dev.hypera.chameleon.annotations.Plugin;
2729
import dev.hypera.chameleon.annotations.exception.ChameleonAnnotationException;
2830
import dev.hypera.chameleon.annotations.processing.generation.Generator;
@@ -33,15 +35,23 @@
3335
import dev.hypera.chameleon.annotations.processing.generation.sponge.SpongeGenerator;
3436
import dev.hypera.chameleon.annotations.processing.generation.velocity.VelocityGenerator;
3537
import dev.hypera.chameleon.platform.Platform;
38+
import java.util.Map;
39+
import java.util.Optional;
3640
import java.util.Set;
3741
import javax.annotation.processing.AbstractProcessor;
3842
import javax.annotation.processing.RoundEnvironment;
3943
import javax.annotation.processing.SupportedAnnotationTypes;
4044
import javax.lang.model.SourceVersion;
45+
import javax.lang.model.element.AnnotationMirror;
46+
import javax.lang.model.element.AnnotationValue;
4147
import javax.lang.model.element.Element;
4248
import javax.lang.model.element.ElementKind;
49+
import javax.lang.model.element.ExecutableElement;
4350
import javax.lang.model.element.Modifier;
4451
import javax.lang.model.element.TypeElement;
52+
import javax.lang.model.type.TypeMirror;
53+
import org.jetbrains.annotations.NotNull;
54+
import org.jetbrains.annotations.Nullable;
4555

4656
/**
4757
* Chameleon Annotation Processor.
@@ -74,8 +84,12 @@ public synchronized boolean process(Set<? extends TypeElement> annotations, Roun
7484
}
7585

7686
TypeElement plugin = (TypeElement) element;
77-
7887
Plugin data = plugin.getAnnotation(Plugin.class);
88+
89+
// Plugin bootstrap
90+
TypeElement pluginBootstrap = retrieveBootstrap(plugin);
91+
92+
// Platform "main class" generation
7993
for (String platform : data.platforms()) {
8094
Generator generator;
8195
switch (platform) {
@@ -98,14 +112,71 @@ public synchronized boolean process(Set<? extends TypeElement> annotations, Roun
98112
generator = new VelocityGenerator();
99113
break;
100114
default:
101-
throw new IllegalStateException("Invalid platform: " + platform);
115+
throw new IllegalStateException("Invalid or unknown platform: " + platform);
102116
}
103117

104-
generator.generate(data, plugin, processingEnv);
118+
generator.generate(data, plugin, pluginBootstrap, processingEnv);
105119
}
106120
}
107121

108122
return false;
109123
}
110124

125+
private @Nullable TypeElement retrieveBootstrap(@NotNull TypeElement plugin) {
126+
TypeMirror bootstrapMirror = getBootstrapFromAnnotation(plugin);
127+
TypeElement bootstrapElement = bootstrapMirror != null ?
128+
(TypeElement) this.processingEnv.getTypeUtils().asElement(bootstrapMirror) : null;
129+
if (bootstrapElement != null && !bootstrapElement.toString().equals(ChameleonPluginBootstrap.class.getName())) {
130+
return bootstrapElement;
131+
}
132+
checkDefaultBootstrapConstructorPresent(plugin);
133+
return null;
134+
}
135+
136+
private void checkDefaultBootstrapConstructorPresent(@NotNull TypeElement plugin) {
137+
// A plugin bootstrap class was not provided, make sure there is a public
138+
// constructor that has a single Chameleon parameter.
139+
Optional<ExecutableElement> constructor = plugin.getEnclosedElements()
140+
.parallelStream()
141+
.filter(e -> e.getKind() == ElementKind.CONSTRUCTOR && e.getModifiers().contains(Modifier.PUBLIC))
142+
.map(e -> (ExecutableElement) e)
143+
.filter(e -> e.getParameters().size() == 1)
144+
.filter(e -> this.processingEnv.getTypeUtils().asElement(e.getParameters().get(0).asType()).toString().equals(Chameleon.class.getName()))
145+
.findAny();
146+
if (constructor.isEmpty()) {
147+
throw new ChameleonAnnotationException(
148+
"If a plugin bootstrap is not provided to @Plugin, the annotated class must have a constructor with a single Chameleon parameter."
149+
);
150+
}
151+
}
152+
153+
/**
154+
* Returns the bootstrap class type mirror from the Plugin annotation.
155+
*
156+
* @param typeElement Type element to read annotation on.
157+
*
158+
* @return bootstrap type mirror, if found, otherwise {@code null}.
159+
*/
160+
private @Nullable TypeMirror getBootstrapFromAnnotation(@NotNull TypeElement typeElement) {
161+
AnnotationMirror mirror = getPluginAnnotationMirror(typeElement);
162+
if (mirror == null) {
163+
return null;
164+
}
165+
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : mirror.getElementValues().entrySet()) {
166+
if (entry.getKey().getSimpleName().toString().equals("bootstrap")) {
167+
return (TypeMirror) entry.getValue().getValue();
168+
}
169+
}
170+
return null;
171+
}
172+
173+
private @Nullable AnnotationMirror getPluginAnnotationMirror(@NotNull TypeElement typeElement) {
174+
for (AnnotationMirror mirror : typeElement.getAnnotationMirrors()) {
175+
if (mirror.getAnnotationType().toString().equals(Plugin.class.getName())) {
176+
return mirror;
177+
}
178+
}
179+
return null;
180+
}
181+
111182
}

annotations/src/main/java/dev/hypera/chameleon/annotations/processing/generation/Generator.java

+21-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
package dev.hypera.chameleon.annotations.processing.generation;
2525

2626
import com.squareup.javapoet.ClassName;
27+
import com.squareup.javapoet.MethodSpec;
2728
import com.squareup.javapoet.ParameterizedTypeName;
2829
import com.squareup.javapoet.TypeName;
2930
import dev.hypera.chameleon.annotations.Plugin;
@@ -32,6 +33,7 @@
3233
import javax.lang.model.element.TypeElement;
3334
import org.jetbrains.annotations.ApiStatus.NonExtendable;
3435
import org.jetbrains.annotations.NotNull;
36+
import org.jetbrains.annotations.Nullable;
3537

3638
/**
3739
* Platform main class generator.
@@ -45,13 +47,14 @@ public abstract class Generator {
4547
/**
4648
* Generate main class and any required files.
4749
*
48-
* @param data Plugin data.
49-
* @param plugin Chameleon plugin main class.
50-
* @param env Processing environment.
50+
* @param data Plugin data.
51+
* @param plugin Chameleon plugin main class.
52+
* @param bootstrap Chameleon plugin bootstrap.
53+
* @param env Processing environment.
5154
*
5255
* @throws ChameleonAnnotationException if something goes wrong while generating the files.
5356
*/
54-
public abstract void generate(@NotNull Plugin data, @NotNull TypeElement plugin, @NotNull ProcessingEnvironment env) throws ChameleonAnnotationException;
57+
public abstract void generate(@NotNull Plugin data, @NotNull TypeElement plugin, @Nullable TypeElement bootstrap, @NotNull ProcessingEnvironment env) throws ChameleonAnnotationException;
5558

5659
protected @NotNull ParameterizedTypeName generic(@NotNull ClassName clazz, @NotNull TypeName... arguments) {
5760
return ParameterizedTypeName.get(clazz, arguments);
@@ -61,4 +64,18 @@ public abstract class Generator {
6164
return ClassName.get(p, n);
6265
}
6366

67+
protected @NotNull MethodSpec.Builder addBootstrap(@NotNull MethodSpec.Builder builder, @NotNull ClassName clazz, @NotNull TypeElement plugin, @Nullable TypeElement bootstrap) {
68+
if (bootstrap == null) {
69+
// Default bootstrap, MyPlugin::new (chameleon -> new MyPlugin(chameleon))
70+
return builder.addStatement(
71+
"this.$N = $T.create($T::new, this).load()",
72+
CHAMELEON_VAR, clazz, plugin
73+
);
74+
}
75+
return builder.addStatement(
76+
"this.$N = $T.create(new $T(), this).load()",
77+
CHAMELEON_VAR, clazz, bootstrap
78+
);
79+
}
80+
6481
}

annotations/src/main/java/dev/hypera/chameleon/annotations/processing/generation/bukkit/BukkitGenerator.java

+7-12
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,21 @@
3232
import dev.hypera.chameleon.annotations.exception.ChameleonAnnotationException;
3333
import dev.hypera.chameleon.annotations.processing.generation.Generator;
3434
import dev.hypera.chameleon.annotations.utils.MapBuilder;
35-
import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException;
3635
import dev.hypera.chameleon.platform.Platform;
3736
import java.io.BufferedWriter;
3837
import java.io.IOException;
3938
import java.nio.file.Files;
4039
import java.nio.file.Paths;
4140
import java.util.Arrays;
4241
import java.util.Objects;
43-
import java.util.logging.Level;
4442
import java.util.stream.Collectors;
4543
import javax.annotation.processing.ProcessingEnvironment;
4644
import javax.lang.model.element.Modifier;
4745
import javax.lang.model.element.PackageElement;
4846
import javax.lang.model.element.TypeElement;
4947
import javax.tools.StandardLocation;
5048
import org.jetbrains.annotations.NotNull;
49+
import org.jetbrains.annotations.Nullable;
5150
import org.yaml.snakeyaml.Yaml;
5251

5352
/**
@@ -67,16 +66,12 @@ public class BukkitGenerator extends Generator {
6766
* @throws ChameleonAnnotationException if something goes wrong while creating the files.
6867
*/
6968
@Override
70-
public void generate(@NotNull Plugin data, @NotNull TypeElement plugin, @NotNull ProcessingEnvironment env) throws ChameleonAnnotationException {
71-
MethodSpec constructorSpec = MethodSpec.constructorBuilder()
72-
.addModifiers(Modifier.PUBLIC)
73-
.beginControlFlow("try")
74-
.addStatement("this.$N = $T.create($T.class, this).load()", CHAMELEON_VAR, clazz("dev.hypera.chameleon.platform.bukkit", "BukkitChameleon"), plugin)
75-
.nextControlFlow("catch ($T ex)", ChameleonInstantiationException.class)
76-
.addStatement("getLogger().log($T.SEVERE, \"An error occurred while loading Chameleon\", $N)", Level.class, "ex")
77-
.addStatement("throw new $T($N)", clazz("dev.hypera.chameleon.exception", "ChameleonRuntimeException"), "ex")
78-
.endControlFlow()
79-
.build();
69+
public void generate(@NotNull Plugin data, @NotNull TypeElement plugin, @Nullable TypeElement bootstrap, @NotNull ProcessingEnvironment env) throws ChameleonAnnotationException {
70+
MethodSpec constructorSpec = addBootstrap(
71+
MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC),
72+
clazz("dev.hypera.chameleon.platform.bukkit", "BukkitChameleon"),
73+
plugin, bootstrap
74+
).build();
8075

8176
MethodSpec enableSpec = MethodSpec.methodBuilder("onEnable")
8277
.addAnnotation(Override.class).addModifiers(Modifier.PUBLIC)

annotations/src/main/java/dev/hypera/chameleon/annotations/processing/generation/bungeecord/BungeeCordGenerator.java

+9-13
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,21 @@
3232
import dev.hypera.chameleon.annotations.exception.ChameleonAnnotationException;
3333
import dev.hypera.chameleon.annotations.processing.generation.Generator;
3434
import dev.hypera.chameleon.annotations.utils.MapBuilder;
35-
import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException;
3635
import dev.hypera.chameleon.platform.Platform;
3736
import java.io.BufferedWriter;
3837
import java.io.IOException;
3938
import java.nio.file.Files;
4039
import java.nio.file.Paths;
4140
import java.util.Arrays;
4241
import java.util.Objects;
43-
import java.util.logging.Level;
4442
import java.util.stream.Collectors;
4543
import javax.annotation.processing.ProcessingEnvironment;
4644
import javax.lang.model.element.Modifier;
4745
import javax.lang.model.element.PackageElement;
4846
import javax.lang.model.element.TypeElement;
4947
import javax.tools.StandardLocation;
5048
import org.jetbrains.annotations.NotNull;
49+
import org.jetbrains.annotations.Nullable;
5150
import org.yaml.snakeyaml.Yaml;
5251

5352
/**
@@ -67,17 +66,14 @@ public class BungeeCordGenerator extends Generator {
6766
* @throws ChameleonAnnotationException if something goes wrong while creating the files.
6867
*/
6968
@Override
70-
public void generate(@NotNull Plugin data, @NotNull TypeElement plugin, @NotNull ProcessingEnvironment env) throws ChameleonAnnotationException {
71-
MethodSpec loadSpec = MethodSpec.methodBuilder("onLoad")
72-
.addAnnotation(Override.class)
73-
.addModifiers(Modifier.PUBLIC)
74-
.beginControlFlow("try")
75-
.addStatement("this.$N = $T.create($T.class, this).load()", CHAMELEON_VAR, clazz("dev.hypera.chameleon.platform.bungeecord", "BungeeCordChameleon"), plugin)
76-
.nextControlFlow("catch ($T ex)", ChameleonInstantiationException.class)
77-
.addStatement("getLogger().log($T.SEVERE, \"An error occurred while loading Chameleon\", $N)", Level.class, "ex")
78-
.addStatement("throw new $T($N)", clazz("dev.hypera.chameleon.exception", "ChameleonRuntimeException"), "ex")
79-
.endControlFlow()
80-
.build();
69+
public void generate(@NotNull Plugin data, @NotNull TypeElement plugin, @Nullable TypeElement bootstrap, @NotNull ProcessingEnvironment env) throws ChameleonAnnotationException {
70+
MethodSpec loadSpec = addBootstrap(
71+
MethodSpec.methodBuilder("onLoad")
72+
.addAnnotation(Override.class)
73+
.addModifiers(Modifier.PUBLIC),
74+
clazz("dev.hypera.chameleon.platform.bungeecord", "BungeeCordChameleon"),
75+
plugin, bootstrap
76+
).build();
8177

8278
MethodSpec enableSpec = MethodSpec.methodBuilder("onEnable")
8379
.addAnnotation(Override.class).addModifiers(Modifier.PUBLIC)

annotations/src/main/java/dev/hypera/chameleon/annotations/processing/generation/folia/FoliaGenerator.java

+8-12
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,21 @@
3131
import dev.hypera.chameleon.annotations.exception.ChameleonAnnotationException;
3232
import dev.hypera.chameleon.annotations.processing.generation.Generator;
3333
import dev.hypera.chameleon.annotations.utils.MapBuilder;
34-
import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException;
3534
import dev.hypera.chameleon.platform.Platform;
3635
import java.io.BufferedWriter;
3736
import java.io.IOException;
3837
import java.nio.file.Files;
3938
import java.nio.file.Paths;
4039
import java.util.Arrays;
4140
import java.util.Objects;
42-
import java.util.logging.Level;
4341
import java.util.stream.Collectors;
4442
import javax.annotation.processing.ProcessingEnvironment;
4543
import javax.lang.model.element.Modifier;
4644
import javax.lang.model.element.PackageElement;
4745
import javax.lang.model.element.TypeElement;
4846
import javax.tools.StandardLocation;
4947
import org.jetbrains.annotations.NotNull;
48+
import org.jetbrains.annotations.Nullable;
5049
import org.yaml.snakeyaml.Yaml;
5150

5251
/**
@@ -66,16 +65,13 @@ public final class FoliaGenerator extends Generator {
6665
* @throws ChameleonAnnotationException if something goes wrong while creating the files.
6766
*/
6867
@Override
69-
public void generate(@NotNull Plugin data, @NotNull TypeElement plugin, @NotNull ProcessingEnvironment env) throws ChameleonAnnotationException {
70-
MethodSpec constructorSpec = MethodSpec.constructorBuilder()
71-
.addModifiers(Modifier.PUBLIC)
72-
.beginControlFlow("try")
73-
.addStatement("this.$N = $T.create($T.class, this).load()", CHAMELEON_VAR, clazz("dev.hypera.chameleon.platform.folia", "FoliaChameleon"), plugin)
74-
.nextControlFlow("catch ($T ex)", ChameleonInstantiationException.class)
75-
.addStatement("getLogger().log($T.SEVERE, \"An error occurred while loading Chameleon\", $N)", Level.class, "ex")
76-
.addStatement("throw new $T($N)", clazz("dev.hypera.chameleon.exception", "ChameleonRuntimeException"), "ex")
77-
.endControlFlow()
78-
.build();
68+
public void generate(@NotNull Plugin data, @NotNull TypeElement plugin, @Nullable TypeElement bootstrap, @NotNull ProcessingEnvironment env) throws ChameleonAnnotationException {
69+
MethodSpec constructorSpec = addBootstrap(
70+
MethodSpec.constructorBuilder()
71+
.addModifiers(Modifier.PUBLIC),
72+
clazz("dev.hypera.chameleon.platform.folia", "FoliaChameleon"),
73+
plugin, bootstrap
74+
).build();
7975

8076
MethodSpec enableSpec = MethodSpec.methodBuilder("onEnable")
8177
.addAnnotation(Override.class).addModifiers(Modifier.PUBLIC)

0 commit comments

Comments
 (0)