36 | * The library manager can resolve a dependency jar through the configured 37 | * Maven repositories, download it into a local cache, relocate it and then 38 | * load it into the plugin's classpath. 39 | *
40 | * Transitive dependencies for a library aren't downloaded automatically and 41 | * must be explicitly loaded like every other library. 42 | *
43 | * It's recommended that libraries are relocated to prevent any namespace
44 | * conflicts with different versions of the same library bundled with other
45 | * plugins or maybe even bundled with the server itself.
46 | *
47 | * @see Library
48 | */
49 | public abstract class LibraryManager {
50 | /**
51 | * Wrapped plugin logger
52 | */
53 | protected final Logger logger;
54 |
55 | /**
56 | * Directory where downloaded library jars are saved to
57 | */
58 | protected final Path saveDirectory;
59 |
60 | /**
61 | * Maven repositories used to resolve artifacts
62 | */
63 | private final List
101 | * By setting this value, the library manager's logger will not log any
102 | * messages with a level less severe than the configured level. This can be
103 | * useful for silencing the download and relocation logging.
104 | *
105 | * Setting this value to {@link LogLevel#WARN} would silence informational
106 | * logging but still print important things like invalid checksum warnings.
107 | *
108 | * @param level the log level to set
109 | */
110 | public void setLogLevel(LogLevel level) {
111 | logger.setLevel(level);
112 | }
113 |
114 | /**
115 | * Gets the currently added repositories used to resolve artifacts.
116 | *
117 | * For each library this list is traversed to download artifacts after the
118 | * direct download URLs have been attempted.
119 | *
120 | * @return current repositories
121 | */
122 | public Collection
134 | * Artifacts will be resolved using this repository when attempts to locate
135 | * the artifact through previously added repositories are all unsuccessful.
136 | *
137 | * @param url repository URL to add
138 | */
139 | public void addRepository(String url) {
140 | String repo = requireNonNull(url, "url").endsWith("/") ? url : url + '/';
141 | synchronized (repositories) {
142 | repositories.add(repo);
143 | }
144 | }
145 |
146 | /**
147 | * Adds the current user's local Maven repository.
148 | */
149 | public void addMavenLocal() {
150 | addRepository(Paths.get(System.getProperty("user.home")).resolve(".m2/repository").toUri().toString());
151 | }
152 |
153 | /**
154 | * Adds the Maven Central repository.
155 | */
156 | public void addMavenCentral() {
157 | addRepository("https://repo1.maven.org/maven2/");
158 | }
159 |
160 | /**
161 | * Adds the Sonatype OSS repository.
162 | */
163 | public void addSonatype() {
164 | addRepository("https://oss.sonatype.org/content/groups/public/");
165 | }
166 |
167 | /**
168 | * Adds the Bintray JCenter repository.
169 | */
170 | public void addJCenter() {
171 | addRepository("https://jcenter.bintray.com/");
172 | }
173 |
174 | /**
175 | * Adds the JitPack repository.
176 | */
177 | public void addJitPack() {
178 | addRepository("https://jitpack.io/");
179 | }
180 |
181 | /**
182 | * Gets all of the possible download URLs for this library. Entries are
183 | * ordered by direct download URLs first and then repository download URLs.
184 | *
185 | * @param library the library to resolve
186 | * @return download URLs
187 | */
188 | public Collection
249 | * If the library has a checksum, it will be compared against the
250 | * downloaded jar's checksum to verify the integrity of the download. If
251 | * the checksums don't match, a warning is generated and the next download
252 | * URL is attempted.
253 | *
254 | * Checksum comparison is ignored if the library doesn't have a checksum
255 | * or if the library jar already exists in the save directory.
256 | *
257 | * Most of the time it is advised to use {@link #loadLibrary(Library)}
258 | * instead of this method because this one is only concerned with
259 | * downloading the jar and returning the local path. It's usually more
260 | * desirable to download the jar and add it to the plugin's classpath in
261 | * one operation.
262 | *
263 | * @param library the library to download
264 | * @return local file path to library
265 | * @see #loadLibrary(Library)
266 | */
267 | public Path downloadLibrary(Library library) {
268 | Path file = saveDirectory.resolve(requireNonNull(library, "library").getPath());
269 | if (Files.exists(file)) {
270 | return file;
271 | }
272 |
273 | Collection
378 | * If the provided library has any relocations, they will be applied to
379 | * create a relocated jar and the relocated jar will be loaded instead.
380 | *
381 | * @param library the library to load
382 | * @see #downloadLibrary(Library)
383 | */
384 | public void loadLibrary(Library library) {
385 | Path file = downloadLibrary(requireNonNull(library, "library"));
386 | if (library.hasRelocations()) {
387 | file = relocate(file, library.getRelocatedPath(), library.getRelocations());
388 | }
389 |
390 | addToClasspath(file);
391 | }
392 | }
393 |
--------------------------------------------------------------------------------
/core/src/main/java/net/byteflux/libby/classloader/IsolatedClassLoader.java:
--------------------------------------------------------------------------------
1 | package net.byteflux.libby.classloader;
2 |
3 | import java.net.MalformedURLException;
4 | import java.net.URL;
5 | import java.net.URLClassLoader;
6 | import java.nio.file.Path;
7 |
8 | import static java.util.Objects.requireNonNull;
9 |
10 | /**
11 | * This class loader is a simple child of {@code URLClassLoader} that uses
12 | * the JVM's Extensions Class Loader as the parent instead of the system class
13 | * loader to provide an unpolluted classpath.
14 | */
15 | public class IsolatedClassLoader extends URLClassLoader {
16 | static {
17 | ClassLoader.registerAsParallelCapable();
18 | }
19 |
20 | /**
21 | * Creates a new isolated class loader for the given URLs.
22 | *
23 | * @param urls the URLs to add to the classpath
24 | */
25 | public IsolatedClassLoader(URL... urls) {
26 | super(requireNonNull(urls, "urls"), ClassLoader.getSystemClassLoader().getParent());
27 | }
28 |
29 | /**
30 | * Adds a URL to the classpath.
31 | *
32 | * @param url the URL to add
33 | */
34 | @Override
35 | public void addURL(URL url) {
36 | super.addURL(url);
37 | }
38 |
39 | /**
40 | * Adds a path to the classpath.
41 | *
42 | * @param path the path to add
43 | */
44 | public void addPath(Path path) {
45 | try {
46 | addURL(requireNonNull(path, "path").toUri().toURL());
47 | } catch (MalformedURLException e) {
48 | throw new IllegalArgumentException(e);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/core/src/main/java/net/byteflux/libby/classloader/URLClassLoaderHelper.java:
--------------------------------------------------------------------------------
1 | package net.byteflux.libby.classloader;
2 |
3 | import java.lang.reflect.Method;
4 | import java.net.MalformedURLException;
5 | import java.net.URL;
6 | import java.net.URLClassLoader;
7 | import java.nio.file.Path;
8 |
9 | import static java.util.Objects.requireNonNull;
10 |
11 | /**
12 | * A reflection-based wrapper around {@link URLClassLoader} for adding URLs to
13 | * the classpath.
14 | */
15 | public class URLClassLoaderHelper {
16 | /**
17 | * The class loader being managed by this helper.
18 | */
19 | private final URLClassLoader classLoader;
20 |
21 | /**
22 | * A reflected method in {@link URLClassLoader}, when invoked adds a URL
23 | * to the classpath
24 | */
25 | private final Method addURLMethod;
26 |
27 | /**
28 | * Creates a new URL class loader helper.
29 | *
30 | * @param classLoader the class loader to manage
31 | */
32 | public URLClassLoaderHelper(URLClassLoader classLoader) {
33 | this.classLoader = requireNonNull(classLoader, "classLoader");
34 |
35 | try {
36 | addURLMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
37 | addURLMethod.setAccessible(true);
38 | } catch (NoSuchMethodException e) {
39 | throw new RuntimeException(e);
40 | }
41 | }
42 |
43 | /**
44 | * Adds a URL to the class loader's classpath.
45 | *
46 | * @param url the URL to add
47 | */
48 | public void addToClasspath(URL url) {
49 | try {
50 | addURLMethod.invoke(classLoader, requireNonNull(url, "url"));
51 | } catch (ReflectiveOperationException e) {
52 | throw new RuntimeException(e);
53 | }
54 | }
55 |
56 | /**
57 | * Adds a path to the class loader's classpath.
58 | *
59 | * @param path the path to add
60 | */
61 | public void addToClasspath(Path path) {
62 | try {
63 | addToClasspath(requireNonNull(path, "path").toUri().toURL());
64 | } catch (MalformedURLException e) {
65 | throw new IllegalArgumentException(e);
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/core/src/main/java/net/byteflux/libby/logging/LogLevel.java:
--------------------------------------------------------------------------------
1 | package net.byteflux.libby.logging;
2 |
3 | /**
4 | * Represents the severity of a log message in {@link Logger}.
5 | */
6 | public enum LogLevel {
7 | /**
8 | * Stuff that isn't useful to end-users
9 | */
10 | DEBUG,
11 |
12 | /**
13 | * Stuff that might be useful to know
14 | */
15 | INFO,
16 |
17 | /**
18 | * Non-fatal, often recoverable errors or notices
19 | */
20 | WARN,
21 |
22 | /**
23 | * Probably an unrecoverable error
24 | */
25 | ERROR
26 | }
27 |
--------------------------------------------------------------------------------
/core/src/main/java/net/byteflux/libby/logging/Logger.java:
--------------------------------------------------------------------------------
1 | package net.byteflux.libby.logging;
2 |
3 | import net.byteflux.libby.logging.adapters.LogAdapter;
4 |
5 | import static java.util.Objects.requireNonNull;
6 |
7 | /**
8 | * A logging wrapper that logs to a log adapter and can be configured to filter
9 | * log messages by severity.
10 | */
11 | public class Logger {
12 | /**
13 | * Log adapter for the current platform
14 | */
15 | private final LogAdapter adapter;
16 |
17 | /**
18 | * Log level controlling which messages are logged
19 | */
20 | private LogLevel level = LogLevel.INFO;
21 |
22 | /**
23 | * Creates a new logger with the provided adapter.
24 | *
25 | * @param adapter the adapter to wrap
26 | */
27 | public Logger(LogAdapter adapter) {
28 | this.adapter = requireNonNull(adapter, "adapter");
29 | }
30 |
31 | /**
32 | * Gets the current log level.
33 | *
34 | * @return current log level
35 | */
36 | public LogLevel getLevel() {
37 | return level;
38 | }
39 |
40 | /**
41 | * Sets a new log level.
42 | *
43 | * @param level new log level
44 | */
45 | public void setLevel(LogLevel level) {
46 | this.level = requireNonNull(level, "level");
47 | }
48 |
49 | /**
50 | * Gets whether messages matching the provided level can be logged under
51 | * the current log level setting.
52 | *
53 | * Returns true if provided log level is equal to or more severe than the
54 | * logger's configured log level.
55 | *
56 | * @param level the level to check
57 | * @return true if message can be logged, or false
58 | */
59 | private boolean canLog(LogLevel level) {
60 | return requireNonNull(level, "level").compareTo(this.level) >= 0;
61 | }
62 |
63 | /**
64 | * Logs a message with the provided level.
65 | *
66 | * If the provided log level is less severe than the logger's
67 | * configured log level, this message won't be logged.
68 | *
69 | * @param level message severity level
70 | * @param message the message to log
71 | * @see #debug(String)
72 | * @see #info(String)
73 | * @see #warn(String)
74 | * @see #error(String)
75 | */
76 | public void log(LogLevel level, String message) {
77 | if (canLog(level)) {
78 | adapter.log(level, message);
79 | }
80 | }
81 |
82 | /**
83 | * Logs a message and stack trace with the provided level.
84 | *
85 | * If the provided log level is less severe than the logger's
86 | * configured log level, this message won't be logged.
87 | *
88 | * @param level message severity level
89 | * @param message the message to log
90 | * @param throwable the throwable to print
91 | * @see #debug(String, Throwable)
92 | * @see #info(String, Throwable)
93 | * @see #warn(String, Throwable)
94 | * @see #error(String, Throwable)
95 | */
96 | public void log(LogLevel level, String message, Throwable throwable) {
97 | if (canLog(level)) {
98 | adapter.log(level, message, throwable);
99 | }
100 | }
101 |
102 | /**
103 | * Logs a debug message.
104 | *
105 | * If the logger's configured log level is more severe than
106 | * {@link LogLevel#DEBUG}, this message won't be logged.
107 | *
108 | * @param message the message to log
109 | */
110 | public void debug(String message) {
111 | log(LogLevel.DEBUG, message);
112 | }
113 |
114 | /**
115 | * Logs a debug message with a stack trace.
116 | *
117 | * If the logger's configured log level is more severe than
118 | * {@link LogLevel#DEBUG}, this message won't be logged.
119 | *
120 | * @param message the message to log
121 | * @param throwable the throwable to print
122 | */
123 | public void debug(String message, Throwable throwable) {
124 | log(LogLevel.DEBUG, message, throwable);
125 | }
126 |
127 | /**
128 | * Logs an informational message.
129 | *
130 | * If the logger's configured log level is more severe than
131 | * {@link LogLevel#INFO}, this message won't be logged.
132 | *
133 | * @param message the message to log
134 | */
135 | public void info(String message) {
136 | log(LogLevel.INFO, message);
137 | }
138 |
139 | /**
140 | * Logs an informational message with a stack trace.
141 | *
142 | * If the logger's configured log level is more severe than
143 | * {@link LogLevel#INFO}, this message won't be logged.
144 | *
145 | * @param message the message to log
146 | * @param throwable the throwable to print
147 | */
148 | public void info(String message, Throwable throwable) {
149 | log(LogLevel.INFO, message, throwable);
150 | }
151 |
152 | /**
153 | * Logs a warning message.
154 | *
155 | * If the logger's configured log level is more severe than
156 | * {@link LogLevel#WARN}, this message won't be logged.
157 | *
158 | * @param message the message to log
159 | */
160 | public void warn(String message) {
161 | log(LogLevel.WARN, message);
162 | }
163 |
164 | /**
165 | * Logs a warning message with a stack trace.
166 | *
167 | * If the logger's configured log level is more severe than
168 | * {@link LogLevel#WARN}, this message won't be logged.
169 | *
170 | * @param message the message to log
171 | * @param throwable the throwable to print
172 | */
173 | public void warn(String message, Throwable throwable) {
174 | log(LogLevel.WARN, message, throwable);
175 | }
176 |
177 | /**
178 | * Logs an error message.
179 | *
180 | * @param message the message to log
181 | */
182 | public void error(String message) {
183 | log(LogLevel.ERROR, message);
184 | }
185 |
186 | /**
187 | * Logs an error message with a stack trace.
188 | *
189 | * @param message message to log
190 | * @param throwable the throwable to print
191 | */
192 | public void error(String message, Throwable throwable) {
193 | log(LogLevel.ERROR, message, throwable);
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/core/src/main/java/net/byteflux/libby/logging/adapters/JDKLogAdapter.java:
--------------------------------------------------------------------------------
1 | package net.byteflux.libby.logging.adapters;
2 |
3 | import net.byteflux.libby.logging.LogLevel;
4 |
5 | import java.util.logging.Level;
6 | import java.util.logging.Logger;
7 |
8 | import static java.util.Objects.requireNonNull;
9 |
10 | /**
11 | * Logging adapter that logs to a JDK logger.
12 | */
13 | public class JDKLogAdapter implements LogAdapter {
14 | /**
15 | * JDK logger
16 | */
17 | private final Logger logger;
18 |
19 | /**
20 | * Creates a new JDK log adapter that logs to a {@link Logger}.
21 | *
22 | * @param logger the JDK logger to wrap
23 | */
24 | public JDKLogAdapter(Logger logger) {
25 | this.logger = requireNonNull(logger, "logger");
26 | }
27 |
28 | /**
29 | * Logs a message with the provided level to the JDK logger.
30 | *
31 | * @param level message severity level
32 | * @param message the message to log
33 | */
34 | @Override
35 | public void log(LogLevel level, String message) {
36 | switch (requireNonNull(level, "level")) {
37 | case DEBUG:
38 | logger.log(Level.FINE, message);
39 | break;
40 | case INFO:
41 | logger.log(Level.INFO, message);
42 | break;
43 | case WARN:
44 | logger.log(Level.WARNING, message);
45 | break;
46 | case ERROR:
47 | logger.log(Level.SEVERE, message);
48 | break;
49 | }
50 | }
51 |
52 | /**
53 | * Logs a message and stack trace with the provided level to the JDK
54 | * logger.
55 | *
56 | * @param level message severity level
57 | * @param message the message to log
58 | * @param throwable the throwable to print
59 | */
60 | @Override
61 | public void log(LogLevel level, String message, Throwable throwable) {
62 | switch (requireNonNull(level, "level")) {
63 | case DEBUG:
64 | logger.log(Level.FINE, message, throwable);
65 | break;
66 | case INFO:
67 | logger.log(Level.INFO, message, throwable);
68 | break;
69 | case WARN:
70 | logger.log(Level.WARNING, message, throwable);
71 | break;
72 | case ERROR:
73 | logger.log(Level.SEVERE, message, throwable);
74 | break;
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/core/src/main/java/net/byteflux/libby/logging/adapters/LogAdapter.java:
--------------------------------------------------------------------------------
1 | package net.byteflux.libby.logging.adapters;
2 |
3 | import net.byteflux.libby.logging.LogLevel;
4 |
5 | /**
6 | * Logging interface for adapting platform-specific loggers to our logging API.
7 | */
8 | public interface LogAdapter {
9 | /**
10 | * Logs a message with the provided level.
11 | *
12 | * @param level message severity level
13 | * @param message the message to log
14 | */
15 | void log(LogLevel level, String message);
16 |
17 | /**
18 | * Logs a message and stack trace with the provided level.
19 | *
20 | * @param level message severity level
21 | * @param message the message to log
22 | * @param throwable the throwable to print
23 | */
24 | void log(LogLevel level, String message, Throwable throwable);
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/main/java/net/byteflux/libby/relocation/Relocation.java:
--------------------------------------------------------------------------------
1 | package net.byteflux.libby.relocation;
2 |
3 | import java.util.Collection;
4 | import java.util.Collections;
5 | import java.util.LinkedList;
6 |
7 | import static java.util.Objects.requireNonNull;
8 |
9 | /**
10 | * Relocations are used to describe a search and replace pattern for renaming
11 | * packages in a library jar for the purpose of preventing namespace conflicts
12 | * with other plugins that bundle their own version of the same library.
13 | */
14 | public class Relocation {
15 | /**
16 | * Search pattern
17 | */
18 | private final String pattern;
19 |
20 | /**
21 | * Replacement pattern
22 | */
23 | private final String relocatedPattern;
24 |
25 | /**
26 | * Classes and resources to include
27 | */
28 | private final Collection