├── .gitignore ├── .idea └── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── LICENSE ├── README.md ├── bukkit ├── pom.xml └── src │ └── main │ └── java │ └── net │ └── byteflux │ └── libby │ └── BukkitLibraryManager.java ├── bungee ├── pom.xml └── src │ └── main │ └── java │ └── net │ └── byteflux │ └── libby │ └── BungeeLibraryManager.java ├── core ├── pom.xml └── src │ └── main │ ├── java-templates │ └── net │ │ └── byteflux │ │ └── libby │ │ └── LibbyProperties.java │ └── java │ └── net │ └── byteflux │ └── libby │ ├── Library.java │ ├── LibraryManager.java │ ├── classloader │ ├── IsolatedClassLoader.java │ └── URLClassLoaderHelper.java │ ├── logging │ ├── LogLevel.java │ ├── Logger.java │ └── adapters │ │ ├── JDKLogAdapter.java │ │ └── LogAdapter.java │ └── relocation │ ├── Relocation.java │ └── RelocationHelper.java ├── nukkit ├── pom.xml └── src │ └── main │ └── java │ └── net │ └── byteflux │ └── libby │ ├── NukkitLibraryManager.java │ └── logging │ └── adapters │ └── NukkitLogAdapter.java ├── pom.xml ├── slf4j ├── pom.xml └── src │ └── main │ └── java │ └── net │ └── byteflux │ └── libby │ └── logging │ └── adapters │ └── SLF4JLogAdapter.java ├── sponge ├── pom.xml └── src │ └── main │ └── java │ └── net │ └── byteflux │ └── libby │ └── SpongeLibraryManager.java └── velocity ├── pom.xml └── src └── main └── java └── net └── byteflux └── libby └── VelocityLibraryManager.java /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | *.iml 3 | target/ -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Matthew Harris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Libby 2 | 3 | A runtime dependency management library for plugins running in Java-based Minecraft 4 | server platforms. 5 | 6 | Libraries can be downloaded from Maven repositories (or direct URLs) into a plugin's data 7 | folder, relocated and then loaded into the plugin's classpath at runtime. 8 | 9 | ### Why use runtime dependency management? 10 | 11 | Due to file size constraints on plugin hosting services like SpigotMC, some plugins with 12 | bundled dependencies become too large to be uploaded. 13 | 14 | Using runtime dependency management, dependencies are downloaded and cached by the server 15 | and don't need to be bundled with the plugin, which significantly reduces the size of the 16 | plugin jar. 17 | 18 | A smaller plugin jar also means shorter download times and less network strain for authors 19 | who self-host their plugins on servers with limited bandwidth. 20 | 21 | ## Credits 22 | 23 | Special thanks to: 24 | 25 | * [Luck](https://github.com/lucko) for [LuckPerms](https://github.com/lucko/LuckPerms) 26 | and its dependency management system which was the original inspiration for this project 27 | and another thanks for [jar-relocator](https://github.com/lucko/jar-relocator) which is 28 | used by Libby to perform jar relocations. 29 | * [Glare](https://github.com/darbyjack) for convincing me that I should publish this 30 | library instead of letting it sit around collecting dust :) 31 | -------------------------------------------------------------------------------- /bukkit/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | net.byteflux 9 | libby 10 | 0.0.2-SNAPSHOT 11 | 12 | 13 | libby-bukkit 14 | 15 | 16 | 17 | spigotmc 18 | https://hub.spigotmc.org/nexus/content/groups/public/ 19 | 20 | 21 | 22 | 23 | 24 | net.byteflux 25 | libby-core 26 | 0.0.2-SNAPSHOT 27 | 28 | 29 | org.bukkit 30 | bukkit 31 | 1.12.2-R0.1-SNAPSHOT 32 | provided 33 | 34 | 35 | 36 | 37 | 38 | 39 | maven-compiler-plugin 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /bukkit/src/main/java/net/byteflux/libby/BukkitLibraryManager.java: -------------------------------------------------------------------------------- 1 | package net.byteflux.libby; 2 | 3 | import net.byteflux.libby.classloader.URLClassLoaderHelper; 4 | import net.byteflux.libby.logging.adapters.JDKLogAdapter; 5 | import org.bukkit.plugin.Plugin; 6 | 7 | import java.net.URLClassLoader; 8 | import java.nio.file.Path; 9 | 10 | import static java.util.Objects.requireNonNull; 11 | 12 | /** 13 | * A runtime dependency manager for Bukkit plugins. 14 | */ 15 | public class BukkitLibraryManager extends LibraryManager { 16 | /** 17 | * Plugin classpath helper 18 | */ 19 | private final URLClassLoaderHelper classLoader; 20 | 21 | /** 22 | * Creates a new Bukkit library manager. 23 | * 24 | * @param plugin the plugin to manage 25 | */ 26 | public BukkitLibraryManager(Plugin plugin) { 27 | super(new JDKLogAdapter(requireNonNull(plugin, "plugin").getLogger()), plugin.getDataFolder().toPath()); 28 | classLoader = new URLClassLoaderHelper((URLClassLoader) plugin.getClass().getClassLoader()); 29 | } 30 | 31 | /** 32 | * Adds a file to the Bukkit plugin's classpath. 33 | * 34 | * @param file the file to add 35 | */ 36 | @Override 37 | protected void addToClasspath(Path file) { 38 | classLoader.addToClasspath(file); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bungee/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | net.byteflux 9 | libby 10 | 0.0.2-SNAPSHOT 11 | 12 | 13 | libby-bungee 14 | 15 | 16 | 17 | spigotmc 18 | https://hub.spigotmc.org/nexus/content/groups/public/ 19 | 20 | 21 | 22 | 23 | 24 | net.byteflux 25 | libby-core 26 | 0.0.2-SNAPSHOT 27 | 28 | 29 | net.md-5 30 | bungeecord-api 31 | 1.14-SNAPSHOT 32 | provided 33 | 34 | 35 | 36 | 37 | 38 | 39 | maven-compiler-plugin 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /bungee/src/main/java/net/byteflux/libby/BungeeLibraryManager.java: -------------------------------------------------------------------------------- 1 | package net.byteflux.libby; 2 | 3 | import net.byteflux.libby.classloader.URLClassLoaderHelper; 4 | import net.byteflux.libby.logging.adapters.JDKLogAdapter; 5 | import net.md_5.bungee.api.plugin.Plugin; 6 | 7 | import java.net.URLClassLoader; 8 | import java.nio.file.Path; 9 | 10 | import static java.util.Objects.requireNonNull; 11 | 12 | /** 13 | * A runtime dependency manager for Bungee plugins. 14 | */ 15 | public class BungeeLibraryManager extends LibraryManager { 16 | /** 17 | * Plugin classpath helper 18 | */ 19 | private final URLClassLoaderHelper classLoader; 20 | 21 | /** 22 | * Creates a new Bungee library manager. 23 | * 24 | * @param plugin the plugin to manage 25 | */ 26 | public BungeeLibraryManager(Plugin plugin) { 27 | super(new JDKLogAdapter(requireNonNull(plugin, "plugin").getLogger()), plugin.getDataFolder().toPath()); 28 | classLoader = new URLClassLoaderHelper((URLClassLoader) plugin.getClass().getClassLoader()); 29 | } 30 | 31 | /** 32 | * Adds a file to the Bungee plugin's classpath. 33 | * 34 | * @param file the file to add 35 | */ 36 | @Override 37 | protected void addToClasspath(Path file) { 38 | classLoader.addToClasspath(file); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | net.byteflux 9 | libby 10 | 0.0.2-SNAPSHOT 11 | 12 | 13 | libby-core 14 | 15 | 16 | 17 | 18 | maven-compiler-plugin 19 | 20 | 21 | org.codehaus.mojo 22 | templating-maven-plugin 23 | 1.0.0 24 | 25 | 26 | filtering-java-templates 27 | 28 | filter-sources 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /core/src/main/java-templates/net/byteflux/libby/LibbyProperties.java: -------------------------------------------------------------------------------- 1 | package net.byteflux.libby; 2 | 3 | /** 4 | * Filtered Maven properties and other related constants. 5 | */ 6 | public class LibbyProperties { 7 | /** 8 | * Project version 9 | */ 10 | public static final String VERSION = "${project.version}"; 11 | 12 | /** 13 | * User agent string to use when downloading libraries 14 | */ 15 | public static final String HTTP_USER_AGENT = "libby/" + VERSION; 16 | } 17 | -------------------------------------------------------------------------------- /core/src/main/java/net/byteflux/libby/Library.java: -------------------------------------------------------------------------------- 1 | package net.byteflux.libby; 2 | 3 | import net.byteflux.libby.relocation.Relocation; 4 | 5 | import java.util.Base64; 6 | import java.util.Collection; 7 | import java.util.Collections; 8 | import java.util.LinkedList; 9 | 10 | import static java.util.Objects.requireNonNull; 11 | 12 | /** 13 | * An immutable representation of a Maven artifact that can be downloaded, 14 | * relocated and then loaded into a plugin's classpath at runtime. 15 | * 16 | * @see #builder() 17 | */ 18 | public class Library { 19 | /** 20 | * Direct download URLs for this library 21 | */ 22 | private final Collection urls; 23 | 24 | /** 25 | * Maven group ID 26 | */ 27 | private final String groupId; 28 | 29 | /** 30 | * Maven artifact ID 31 | */ 32 | private final String artifactId; 33 | 34 | /** 35 | * Artifact version 36 | */ 37 | private final String version; 38 | 39 | /** 40 | * Artifact classifier 41 | */ 42 | private final String classifier; 43 | 44 | /** 45 | * Binary SHA-256 checksum for this library's jar file 46 | */ 47 | private final byte[] checksum; 48 | 49 | /** 50 | * Jar relocations to apply 51 | */ 52 | private final Collection relocations; 53 | 54 | /** 55 | * Relative Maven path to this library's artifact 56 | */ 57 | private final String path; 58 | 59 | /** 60 | * Relative path to this library's relocated jar 61 | */ 62 | private final String relocatedPath; 63 | 64 | /** 65 | * Creates a new library. 66 | * 67 | * @param urls direct download URLs 68 | * @param groupId Maven group ID 69 | * @param artifactId Maven artifact ID 70 | * @param version artifact version 71 | * @param classifier artifact classifier or null 72 | * @param checksum binary SHA-256 checksum or null 73 | * @param relocations jar relocations or null 74 | */ 75 | private Library(Collection urls, 76 | String groupId, 77 | String artifactId, 78 | String version, 79 | String classifier, 80 | byte[] checksum, 81 | Collection relocations) { 82 | 83 | this.urls = urls != null ? Collections.unmodifiableList(new LinkedList<>(urls)) : Collections.emptyList(); 84 | this.groupId = requireNonNull(groupId, "groupId").replace("{}", "."); 85 | this.artifactId = requireNonNull(artifactId, "artifactId"); 86 | this.version = requireNonNull(version, "version"); 87 | this.classifier = classifier; 88 | this.checksum = checksum; 89 | this.relocations = relocations != null ? Collections.unmodifiableList(new LinkedList<>(relocations)) : Collections.emptyList(); 90 | 91 | String path = this.groupId.replace('.', '/') + '/' + artifactId + '/' + version + '/' + artifactId + '-' + version; 92 | if (hasClassifier()) { 93 | path += '-' + classifier; 94 | } 95 | 96 | this.path = path + ".jar"; 97 | relocatedPath = hasRelocations() ? path + "-relocated.jar" : null; 98 | } 99 | 100 | /** 101 | * Gets the direct download URLs for this library. 102 | * 103 | * @return direct download URLs 104 | */ 105 | public Collection getUrls() { 106 | return urls; 107 | } 108 | 109 | /** 110 | * Gets the Maven group ID for this library. 111 | * 112 | * @return Maven group ID 113 | */ 114 | public String getGroupId() { 115 | return groupId; 116 | } 117 | 118 | /** 119 | * Gets the Maven artifact ID for this library. 120 | * 121 | * @return Maven artifact ID 122 | */ 123 | public String getArtifactId() { 124 | return artifactId; 125 | } 126 | 127 | /** 128 | * Gets the artifact version for this library. 129 | * 130 | * @return artifact version 131 | */ 132 | public String getVersion() { 133 | return version; 134 | } 135 | 136 | /** 137 | * Gets the artifact classifier for this library. 138 | * 139 | * @return artifact classifier or null 140 | */ 141 | public String getClassifier() { 142 | return classifier; 143 | } 144 | 145 | /** 146 | * Gets whether this library has an artifact classifier. 147 | * 148 | * @return true if library has classifier, false otherwise 149 | */ 150 | public boolean hasClassifier() { 151 | return classifier != null; 152 | } 153 | 154 | /** 155 | * Gets the binary SHA-256 checksum of this library's jar file. 156 | * 157 | * @return checksum or null 158 | */ 159 | public byte[] getChecksum() { 160 | return checksum; 161 | } 162 | 163 | /** 164 | * Gets whether this library has a checksum. 165 | * 166 | * @return true if library has checksum, false otherwise 167 | */ 168 | public boolean hasChecksum() { 169 | return checksum != null; 170 | } 171 | 172 | /** 173 | * Gets the jar relocations to apply to this library. 174 | * 175 | * @return jar relocations to apply 176 | */ 177 | public Collection getRelocations() { 178 | return relocations; 179 | } 180 | 181 | /** 182 | * Gets whether this library has any jar relocations. 183 | * 184 | * @return true if library has relocations, false otherwise 185 | */ 186 | public boolean hasRelocations() { 187 | return !relocations.isEmpty(); 188 | } 189 | 190 | /** 191 | * Gets the relative Maven path to this library's artifact. 192 | * 193 | * @return Maven path for this library 194 | */ 195 | public String getPath() { 196 | return path; 197 | } 198 | 199 | /** 200 | * Gets the relative path to this library's relocated jar. 201 | * 202 | * @return path to relocated artifact or null if has no relocations 203 | */ 204 | public String getRelocatedPath() { 205 | return relocatedPath; 206 | } 207 | 208 | /** 209 | * Gets a concise, human-readable string representation of this library. 210 | * 211 | * @return string representation 212 | */ 213 | @Override 214 | public String toString() { 215 | String name = groupId + ':' + artifactId + ':' + version; 216 | if (hasClassifier()) { 217 | name += ':' + classifier; 218 | } 219 | 220 | return name; 221 | } 222 | 223 | /** 224 | * Creates a new library builder. 225 | * 226 | * @return new library builder 227 | */ 228 | public static Builder builder() { 229 | return new Builder(); 230 | } 231 | 232 | /** 233 | * Due to the constructor complexity of an immutable {@link Library}, 234 | * instead this fluent builder is used to configure and then construct 235 | * a new library. 236 | */ 237 | public static class Builder { 238 | /** 239 | * Direct download URLs for this library 240 | */ 241 | private final Collection urls = new LinkedList<>(); 242 | 243 | /** 244 | * Maven group ID 245 | */ 246 | private String groupId; 247 | 248 | /** 249 | * Maven artifact ID 250 | */ 251 | private String artifactId; 252 | 253 | /** 254 | * Artifact version 255 | */ 256 | private String version; 257 | 258 | /** 259 | * Artifact classifier 260 | */ 261 | private String classifier; 262 | 263 | /** 264 | * Binary SHA-256 checksum for this library's jar file 265 | */ 266 | private byte[] checksum; 267 | 268 | /** 269 | * Jar relocations to apply 270 | */ 271 | private final Collection relocations = new LinkedList<>(); 272 | 273 | /** 274 | * Adds a direct download URL for this library. 275 | * 276 | * @param url direct download URL 277 | * @return this builder 278 | */ 279 | public Builder url(String url) { 280 | urls.add(requireNonNull(url, "url")); 281 | return this; 282 | } 283 | 284 | /** 285 | * Sets the Maven group ID for this library. 286 | * 287 | * @param groupId Maven group ID 288 | * @return this builder 289 | */ 290 | public Builder groupId(String groupId) { 291 | this.groupId = requireNonNull(groupId, "groupId"); 292 | return this; 293 | } 294 | 295 | /** 296 | * Sets the Maven artifact ID for this library. 297 | * 298 | * @param artifactId Maven artifact ID 299 | * @return this builder 300 | */ 301 | public Builder artifactId(String artifactId) { 302 | this.artifactId = requireNonNull(artifactId, "artifactId"); 303 | return this; 304 | } 305 | 306 | /** 307 | * Sets the artifact version for this library. 308 | * 309 | * @param version artifact version 310 | * @return this builder 311 | */ 312 | public Builder version(String version) { 313 | this.version = requireNonNull(version, "version"); 314 | return this; 315 | } 316 | 317 | /** 318 | * Sets the artifact classifier for this library. 319 | * 320 | * @param classifier artifact classifier 321 | * @return this builder 322 | */ 323 | public Builder classifier(String classifier) { 324 | this.classifier = requireNonNull(classifier, "classifier"); 325 | return this; 326 | } 327 | 328 | /** 329 | * Sets the binary SHA-256 checksum for this library. 330 | * 331 | * @param checksum binary SHA-256 checksum 332 | * @return this builder 333 | */ 334 | public Builder checksum(byte[] checksum) { 335 | this.checksum = requireNonNull(checksum, "checksum"); 336 | return this; 337 | } 338 | 339 | /** 340 | * Sets the Base64-encoded SHA-256 checksum for this library. 341 | * 342 | * @param checksum Base64-encoded SHA-256 checksum 343 | * @return this builder 344 | */ 345 | public Builder checksum(String checksum) { 346 | return checksum(Base64.getDecoder().decode(requireNonNull(checksum, "checksum"))); 347 | } 348 | 349 | /** 350 | * Adds a jar relocation to apply to this library. 351 | * 352 | * @param relocation jar relocation to apply 353 | * @return this builder 354 | */ 355 | public Builder relocate(Relocation relocation) { 356 | relocations.add(requireNonNull(relocation, "relocation")); 357 | return this; 358 | } 359 | 360 | /** 361 | * Adds a jar relocation to apply to this library. 362 | * 363 | * @param pattern search pattern 364 | * @param relocatedPattern replacement pattern 365 | * @return this builder 366 | */ 367 | public Builder relocate(String pattern, String relocatedPattern) { 368 | return relocate(new Relocation(pattern, relocatedPattern)); 369 | } 370 | 371 | /** 372 | * Creates a new library using this builder's configuration. 373 | * 374 | * @return new library 375 | */ 376 | public Library build() { 377 | return new Library(urls, groupId, artifactId, version, classifier, checksum, relocations); 378 | } 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /core/src/main/java/net/byteflux/libby/LibraryManager.java: -------------------------------------------------------------------------------- 1 | package net.byteflux.libby; 2 | 3 | import net.byteflux.libby.logging.LogLevel; 4 | import net.byteflux.libby.logging.Logger; 5 | import net.byteflux.libby.logging.adapters.LogAdapter; 6 | import net.byteflux.libby.relocation.Relocation; 7 | import net.byteflux.libby.relocation.RelocationHelper; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.FileNotFoundException; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.UncheckedIOException; 14 | import java.net.MalformedURLException; 15 | import java.net.SocketTimeoutException; 16 | import java.net.URL; 17 | import java.net.URLConnection; 18 | import java.net.UnknownHostException; 19 | import java.nio.file.Files; 20 | import java.nio.file.Path; 21 | import java.nio.file.Paths; 22 | import java.security.MessageDigest; 23 | import java.security.NoSuchAlgorithmException; 24 | import java.util.Arrays; 25 | import java.util.Base64; 26 | import java.util.Collection; 27 | import java.util.Collections; 28 | import java.util.LinkedList; 29 | import java.util.List; 30 | 31 | import static java.util.Objects.requireNonNull; 32 | 33 | /** 34 | * A runtime dependency manager for plugins. 35 | *

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 repositories = new LinkedList<>(); 64 | 65 | /** 66 | * Lazily-initialized relocation helper that uses reflection to call into 67 | * Luck's Jar Relocator 68 | */ 69 | private RelocationHelper relocator; 70 | 71 | /** 72 | * Creates a new library manager. 73 | * 74 | * @param logAdapter plugin logging adapter 75 | * @param dataDirectory plugin's data directory 76 | */ 77 | protected LibraryManager(LogAdapter logAdapter, Path dataDirectory) { 78 | logger = new Logger(requireNonNull(logAdapter, "logAdapter")); 79 | saveDirectory = requireNonNull(dataDirectory, "dataDirectory").toAbsolutePath().resolve("lib"); 80 | } 81 | 82 | /** 83 | * Adds a file to the plugin's classpath. 84 | * 85 | * @param file the file to add 86 | */ 87 | protected abstract void addToClasspath(Path file); 88 | 89 | /** 90 | * Gets the logging level for this library manager. 91 | * 92 | * @return log level 93 | */ 94 | public LogLevel getLogLevel() { 95 | return logger.getLevel(); 96 | } 97 | 98 | /** 99 | * Sets the logging level for this library manager. 100 | *

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 getRepositories() { 123 | List urls; 124 | synchronized (repositories) { 125 | urls = new LinkedList<>(repositories); 126 | } 127 | 128 | return Collections.unmodifiableList(urls); 129 | } 130 | 131 | /** 132 | * Adds a repository URL to this library manager. 133 | *

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 resolveLibrary(Library library) { 189 | List urls = new LinkedList<>(requireNonNull(library, "library").getUrls()); 190 | for (String repository : getRepositories()) { 191 | urls.add(repository + library.getPath()); 192 | } 193 | 194 | return Collections.unmodifiableList(urls); 195 | } 196 | 197 | /** 198 | * Downloads a library jar and returns the contents as a byte array. 199 | * 200 | * @param url the URL to the library jar 201 | * @return downloaded jar as byte array or null if nothing was downloaded 202 | */ 203 | private byte[] downloadLibrary(String url) { 204 | try { 205 | URLConnection connection = new URL(requireNonNull(url, "url")).openConnection(); 206 | 207 | connection.setConnectTimeout(5000); 208 | connection.setReadTimeout(5000); 209 | connection.setRequestProperty("User-Agent", LibbyProperties.HTTP_USER_AGENT); 210 | 211 | try (InputStream in = connection.getInputStream()) { 212 | int len; 213 | byte[] buf = new byte[8192]; 214 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 215 | 216 | try { 217 | while ((len = in.read(buf)) != -1) { 218 | out.write(buf, 0, len); 219 | } 220 | } catch (SocketTimeoutException e) { 221 | logger.warn("Download timed out: " + connection.getURL()); 222 | return null; 223 | } 224 | 225 | logger.info("Downloaded library " + connection.getURL()); 226 | return out.toByteArray(); 227 | } 228 | } catch (MalformedURLException e) { 229 | throw new IllegalArgumentException(e); 230 | } catch (IOException e) { 231 | if (e instanceof FileNotFoundException) { 232 | logger.debug("File not found: " + url); 233 | } else if (e instanceof SocketTimeoutException) { 234 | logger.debug("Connect timed out: " + url); 235 | } else if (e instanceof UnknownHostException) { 236 | logger.debug("Unknown host: " + url); 237 | } else { 238 | logger.debug("Unexpected IOException", e); 239 | } 240 | 241 | return null; 242 | } 243 | } 244 | 245 | /** 246 | * Downloads a library jar to the save directory if it doesn't already 247 | * exist and returns the local file path. 248 | *

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 urls = resolveLibrary(library); 274 | if (urls.isEmpty()) { 275 | throw new RuntimeException("Library '" + library + "' couldn't be resolved, add a repository"); 276 | } 277 | 278 | MessageDigest md = null; 279 | if (library.hasChecksum()) { 280 | try { 281 | md = MessageDigest.getInstance("SHA-256"); 282 | } catch (NoSuchAlgorithmException e) { 283 | throw new RuntimeException(e); 284 | } 285 | } 286 | 287 | Path out = file.resolveSibling(file.getFileName() + ".tmp"); 288 | out.toFile().deleteOnExit(); 289 | 290 | try { 291 | Files.createDirectories(file.getParent()); 292 | 293 | for (String url : urls) { 294 | byte[] bytes = downloadLibrary(url); 295 | if (bytes == null) { 296 | continue; 297 | } 298 | 299 | if (md != null) { 300 | byte[] checksum = md.digest(bytes); 301 | if (!Arrays.equals(checksum, library.getChecksum())) { 302 | logger.warn("*** INVALID CHECKSUM ***"); 303 | logger.warn(" Library : " + library); 304 | logger.warn(" URL : " + url); 305 | logger.warn(" Expected : " + Base64.getEncoder().encodeToString(library.getChecksum())); 306 | logger.warn(" Actual : " + Base64.getEncoder().encodeToString(checksum)); 307 | continue; 308 | } 309 | } 310 | 311 | Files.write(out, bytes); 312 | Files.move(out, file); 313 | 314 | return file; 315 | } 316 | } catch (IOException e) { 317 | throw new UncheckedIOException(e); 318 | } finally { 319 | try { 320 | Files.deleteIfExists(out); 321 | } catch (IOException ignored) { 322 | } 323 | } 324 | 325 | throw new RuntimeException("Failed to download library '" + library + "'"); 326 | } 327 | 328 | /** 329 | * Processes the input jar and generates an output jar with the provided 330 | * relocation rules applied, then returns the path to the relocated jar. 331 | * 332 | * @param in input jar 333 | * @param out output jar 334 | * @param relocations relocations to apply 335 | * @return the relocated file 336 | * @see RelocationHelper#relocate(Path, Path, Collection) 337 | */ 338 | private Path relocate(Path in, String out, Collection relocations) { 339 | requireNonNull(in, "in"); 340 | requireNonNull(out, "out"); 341 | requireNonNull(relocations, "relocations"); 342 | 343 | Path file = saveDirectory.resolve(out); 344 | if (Files.exists(file)) { 345 | return file; 346 | } 347 | 348 | Path tmpOut = file.resolveSibling(file.getFileName() + ".tmp"); 349 | tmpOut.toFile().deleteOnExit(); 350 | 351 | synchronized (this) { 352 | if (relocator == null) { 353 | relocator = new RelocationHelper(this); 354 | } 355 | } 356 | 357 | try { 358 | relocator.relocate(in, tmpOut, relocations); 359 | Files.move(tmpOut, file); 360 | 361 | logger.info("Relocations applied to " + saveDirectory.getParent().relativize(in)); 362 | 363 | return file; 364 | } catch (IOException e) { 365 | throw new UncheckedIOException(e); 366 | } finally { 367 | try { 368 | Files.deleteIfExists(tmpOut); 369 | } catch (IOException ignored) { 370 | } 371 | } 372 | } 373 | 374 | /** 375 | * Loads a library jar into the plugin's classpath. If the library jar 376 | * doesn't exist locally, it will be downloaded. 377 | *

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 includes; 29 | 30 | /** 31 | * Classes and resources to exclude 32 | */ 33 | private final Collection excludes; 34 | 35 | /** 36 | * Creates a new relocation. 37 | * 38 | * @param pattern search pattern 39 | * @param relocatedPattern replacement pattern 40 | * @param includes classes and resources to include 41 | * @param excludes classes and resources to exclude 42 | */ 43 | public Relocation(String pattern, String relocatedPattern, Collection includes, Collection excludes) { 44 | this.pattern = requireNonNull(pattern, "pattern").replace("{}", "."); 45 | this.relocatedPattern = requireNonNull(relocatedPattern, "relocatedPattern").replace("{}", "."); 46 | this.includes = includes != null ? Collections.unmodifiableList(new LinkedList<>(includes)) : Collections.emptyList(); 47 | this.excludes = excludes != null ? Collections.unmodifiableList(new LinkedList<>(excludes)) : Collections.emptyList(); 48 | } 49 | 50 | /** 51 | * Creates a new relocation with empty includes and excludes. 52 | * 53 | * @param pattern search pattern 54 | * @param relocatedPattern replacement pattern 55 | */ 56 | public Relocation(String pattern, String relocatedPattern) { 57 | this(pattern, relocatedPattern, null, null); 58 | } 59 | 60 | /** 61 | * Gets the search pattern. 62 | * 63 | * @return pattern to search 64 | */ 65 | public String getPattern() { 66 | return pattern; 67 | } 68 | 69 | /** 70 | * Gets the replacement pattern. 71 | * 72 | * @return pattern to replace with 73 | */ 74 | public String getRelocatedPattern() { 75 | return relocatedPattern; 76 | } 77 | 78 | /** 79 | * Gets included classes and resources. 80 | * 81 | * @return classes and resources to include 82 | */ 83 | public Collection getIncludes() { 84 | return includes; 85 | } 86 | 87 | /** 88 | * Gets excluded classes and resources. 89 | * 90 | * @return classes and resources to exclude 91 | */ 92 | public Collection getExcludes() { 93 | return excludes; 94 | } 95 | 96 | /** 97 | * Creates a new relocation builder. 98 | * 99 | * @return new relocation builder 100 | */ 101 | public static Builder builder() { 102 | return new Builder(); 103 | } 104 | 105 | /** 106 | * Provides an alternative method of creating a {@link Relocation}. This 107 | * builder may be more intuitive for configuring relocations that also have 108 | * any includes or excludes. 109 | */ 110 | public static class Builder { 111 | /** 112 | * Search pattern 113 | */ 114 | private String pattern; 115 | 116 | /** 117 | * Replacement pattern 118 | */ 119 | private String relocatedPattern; 120 | 121 | /** 122 | * Classes and resources to include 123 | */ 124 | private final Collection includes = new LinkedList<>(); 125 | 126 | /** 127 | * Classes and resources to exclude 128 | */ 129 | private final Collection excludes = new LinkedList<>(); 130 | 131 | /** 132 | * Sets the search pattern. 133 | * 134 | * @param pattern pattern to search 135 | * @return this builder 136 | */ 137 | public Builder pattern(String pattern) { 138 | this.pattern = requireNonNull(pattern, "pattern"); 139 | return this; 140 | } 141 | 142 | /** 143 | * Sets the replacement pattern. 144 | * 145 | * @param relocatedPattern pattern to replace with 146 | * @return this builder 147 | */ 148 | public Builder relocatedPattern(String relocatedPattern) { 149 | this.relocatedPattern = requireNonNull(relocatedPattern, "relocatedPattern"); 150 | return this; 151 | } 152 | 153 | /** 154 | * Adds a class or resource to be included. 155 | * 156 | * @param include class or resource to include 157 | * @return this builder 158 | */ 159 | public Builder include(String include) { 160 | includes.add(requireNonNull(include, "include")); 161 | return this; 162 | } 163 | 164 | /** 165 | * Adds a class or resource to be excluded. 166 | * 167 | * @param exclude class or resource to exclude 168 | * @return this builder 169 | */ 170 | public Builder exclude(String exclude) { 171 | excludes.add(requireNonNull(exclude, "exclude")); 172 | return this; 173 | } 174 | 175 | /** 176 | * Creates a new relocation using this builder's configuration. 177 | * 178 | * @return new relocation 179 | */ 180 | public Relocation build() { 181 | return new Relocation(pattern, relocatedPattern, includes, excludes); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /core/src/main/java/net/byteflux/libby/relocation/RelocationHelper.java: -------------------------------------------------------------------------------- 1 | package net.byteflux.libby.relocation; 2 | 3 | import net.byteflux.libby.Library; 4 | import net.byteflux.libby.LibraryManager; 5 | import net.byteflux.libby.classloader.IsolatedClassLoader; 6 | 7 | import java.io.File; 8 | import java.lang.reflect.Constructor; 9 | import java.lang.reflect.Method; 10 | import java.nio.file.Path; 11 | import java.util.Collection; 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | 15 | import static java.util.Objects.requireNonNull; 16 | 17 | /** 18 | * A reflection-based helper for relocating library jars. It automatically 19 | * downloads and invokes Luck's Jar Relocator to perform jar relocations. 20 | * 21 | * @see Luck's Jar Relocator 22 | */ 23 | public class RelocationHelper { 24 | /** 25 | * Reflected constructor for creating new jar relocator instances 26 | */ 27 | private final Constructor jarRelocatorConstructor; 28 | 29 | /** 30 | * Reflected method for running a jar relocator 31 | */ 32 | private final Method jarRelocatorRunMethod; 33 | 34 | /** 35 | * Reflected constructor for creating relocation instances 36 | */ 37 | private final Constructor relocationConstructor; 38 | 39 | /** 40 | * Creates a new relocation helper using the provided library manager to 41 | * download the dependencies required for runtime relocation. 42 | * 43 | * @param libraryManager the library manager used to download dependencies 44 | */ 45 | public RelocationHelper(LibraryManager libraryManager) { 46 | requireNonNull(libraryManager, "libraryManager"); 47 | 48 | IsolatedClassLoader classLoader = new IsolatedClassLoader(); 49 | 50 | // ObjectWeb ASM Commons 51 | classLoader.addPath(libraryManager.downloadLibrary( 52 | Library.builder() 53 | .groupId("org.ow2.asm") 54 | .artifactId("asm-commons") 55 | .version("6.0") 56 | .checksum("8bzlxkipagF73NAf5dWa+YRSl/17ebgcAVpvu9lxmr8=") 57 | .build() 58 | )); 59 | 60 | // ObjectWeb ASM 61 | classLoader.addPath(libraryManager.downloadLibrary( 62 | Library.builder() 63 | .groupId("org.ow2.asm") 64 | .artifactId("asm") 65 | .version("6.0") 66 | .checksum("3Ylxx0pOaXiZqOlcquTqh2DqbEhtxrl7F5XnV2BCBGE=") 67 | .build() 68 | )); 69 | 70 | // Luck's Jar Relocator 71 | classLoader.addPath(libraryManager.downloadLibrary( 72 | Library.builder() 73 | .groupId("me.lucko") 74 | .artifactId("jar-relocator") 75 | .version("1.3") 76 | .checksum("mmz3ltQbS8xXGA2scM0ZH6raISlt4nukjCiU2l9Jxfs=") 77 | .build() 78 | )); 79 | 80 | try { 81 | Class jarRelocatorClass = classLoader.loadClass("me.lucko.jarrelocator.JarRelocator"); 82 | Class relocationClass = classLoader.loadClass("me.lucko.jarrelocator.Relocation"); 83 | 84 | // me.lucko.jarrelocator.JarRelocator(File, File, Collection) 85 | jarRelocatorConstructor = jarRelocatorClass.getConstructor(File.class, File.class, Collection.class); 86 | 87 | // me.lucko.jarrelocator.JarRelocator#run() 88 | jarRelocatorRunMethod = jarRelocatorClass.getMethod("run"); 89 | 90 | // me.lucko.jarrelocator.Relocation(String, String, Collection, Collection) 91 | relocationConstructor = relocationClass.getConstructor(String.class, String.class, Collection.class, Collection.class); 92 | } catch (ReflectiveOperationException e) { 93 | throw new RuntimeException(e); 94 | } 95 | } 96 | 97 | /** 98 | * Invokes the jar relocator to process the input jar and generate an 99 | * output jar with the provided relocation rules applied. 100 | * 101 | * @param in input jar 102 | * @param out output jar 103 | * @param relocations relocations to apply 104 | */ 105 | public void relocate(Path in, Path out, Collection relocations) { 106 | requireNonNull(in, "in"); 107 | requireNonNull(out, "out"); 108 | requireNonNull(relocations, "relocations"); 109 | 110 | try { 111 | List rules = new LinkedList<>(); 112 | for (Relocation relocation : relocations) { 113 | rules.add(relocationConstructor.newInstance( 114 | relocation.getPattern(), 115 | relocation.getRelocatedPattern(), 116 | relocation.getIncludes(), 117 | relocation.getExcludes() 118 | )); 119 | } 120 | 121 | jarRelocatorRunMethod.invoke(jarRelocatorConstructor.newInstance(in.toFile(), out.toFile(), rules)); 122 | } catch (ReflectiveOperationException e) { 123 | throw new RuntimeException(e); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /nukkit/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | net.byteflux 9 | libby 10 | 0.0.2-SNAPSHOT 11 | 12 | 13 | libby-nukkit 14 | 15 | 16 | 17 | nukkitx 18 | https://repo.nukkitx.com/maven-snapshots/ 19 | 20 | 21 | 22 | 23 | 24 | net.byteflux 25 | libby-core 26 | 0.0.2-SNAPSHOT 27 | 28 | 29 | cn.nukkit 30 | nukkit 31 | 1.0-SNAPSHOT 32 | provided 33 | 34 | 35 | 36 | 37 | 38 | 39 | maven-compiler-plugin 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /nukkit/src/main/java/net/byteflux/libby/NukkitLibraryManager.java: -------------------------------------------------------------------------------- 1 | package net.byteflux.libby; 2 | 3 | import cn.nukkit.plugin.Plugin; 4 | import net.byteflux.libby.classloader.URLClassLoaderHelper; 5 | import net.byteflux.libby.logging.adapters.NukkitLogAdapter; 6 | 7 | import java.net.URLClassLoader; 8 | import java.nio.file.Path; 9 | 10 | import static java.util.Objects.requireNonNull; 11 | 12 | /** 13 | * A runtime dependency manager for Nukkit plugins. 14 | */ 15 | public class NukkitLibraryManager extends LibraryManager { 16 | /** 17 | * Plugin classpath helper 18 | */ 19 | private final URLClassLoaderHelper classLoader; 20 | 21 | /** 22 | * Creates a new Nukkit library manager. 23 | * 24 | * @param plugin the plugin to manage 25 | */ 26 | public NukkitLibraryManager(Plugin plugin) { 27 | super(new NukkitLogAdapter(requireNonNull(plugin, "plugin").getLogger()), plugin.getDataFolder().toPath()); 28 | classLoader = new URLClassLoaderHelper((URLClassLoader) plugin.getClass().getClassLoader()); 29 | } 30 | 31 | /** 32 | * Adds a file to the Nukkit plugin's classpath. 33 | * 34 | * @param file the file to add 35 | */ 36 | @Override 37 | protected void addToClasspath(Path file) { 38 | classLoader.addToClasspath(file); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /nukkit/src/main/java/net/byteflux/libby/logging/adapters/NukkitLogAdapter.java: -------------------------------------------------------------------------------- 1 | package net.byteflux.libby.logging.adapters; 2 | 3 | import cn.nukkit.plugin.PluginLogger; 4 | import net.byteflux.libby.logging.LogLevel; 5 | 6 | import static java.util.Objects.requireNonNull; 7 | 8 | /** 9 | * Logging adapter that logs to a Nukkit plugin logger. 10 | */ 11 | public class NukkitLogAdapter implements LogAdapter { 12 | /** 13 | * Nukkit plugin logger 14 | */ 15 | private final PluginLogger logger; 16 | 17 | /** 18 | * Creates a new Nukkit log adapter that logs to a {@link PluginLogger}. 19 | * 20 | * @param logger the plugin logger to wrap 21 | */ 22 | public NukkitLogAdapter(PluginLogger logger) { 23 | this.logger = requireNonNull(logger, "logger"); 24 | } 25 | 26 | /** 27 | * Logs a message with the provided level to the Nukkit plugin logger. 28 | * 29 | * @param level message severity level 30 | * @param message the message to log 31 | */ 32 | @Override 33 | public void log(LogLevel level, String message) { 34 | switch (requireNonNull(level, "level")) { 35 | case DEBUG: 36 | logger.debug(message); 37 | break; 38 | case INFO: 39 | logger.info(message); 40 | break; 41 | case WARN: 42 | logger.warning(message); 43 | break; 44 | case ERROR: 45 | logger.error(message); 46 | break; 47 | } 48 | } 49 | 50 | /** 51 | * Logs a message and stack trace with the provided level to the Nukkit 52 | * plugin logger. 53 | * 54 | * @param level message severity level 55 | * @param message the message to log 56 | * @param throwable the throwable to print 57 | */ 58 | @Override 59 | public void log(LogLevel level, String message, Throwable throwable) { 60 | switch (requireNonNull(level, "level")) { 61 | case DEBUG: 62 | logger.debug(message, throwable); 63 | break; 64 | case INFO: 65 | logger.info(message, throwable); 66 | break; 67 | case WARN: 68 | logger.warning(message, throwable); 69 | break; 70 | case ERROR: 71 | logger.error(message, throwable); 72 | break; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | net.byteflux 8 | libby 9 | 0.0.2-SNAPSHOT 10 | pom 11 | 12 | 13 | core 14 | bukkit 15 | bungee 16 | nukkit 17 | slf4j 18 | sponge 19 | velocity 20 | 21 | 22 | 23 | UTF-8 24 | 1.8 25 | 1.8 26 | 27 | 28 | 29 | 30 | 31 | 32 | org.apache.maven.plugins 33 | maven-compiler-plugin 34 | 3.8.0 35 | 36 | 37 | -parameters 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | release 48 | 49 | 50 | 51 | org.apache.maven.plugins 52 | maven-source-plugin 53 | 3.1.0 54 | 55 | 56 | attach-sources 57 | 58 | jar 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | byteflux-releases 68 | https://repo.byteflux.net/repository/maven-releases/ 69 | 70 | 71 | byteflux-snapshots 72 | https://repo.byteflux.net/repository/maven-snapshots/ 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /slf4j/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | net.byteflux 9 | libby 10 | 0.0.2-SNAPSHOT 11 | 12 | 13 | libby-slf4j 14 | 15 | 16 | 17 | net.byteflux 18 | libby-core 19 | 0.0.2-SNAPSHOT 20 | 21 | 22 | org.slf4j 23 | slf4j-api 24 | 1.7.25 25 | provided 26 | 27 | 28 | 29 | 30 | 31 | 32 | maven-compiler-plugin 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /slf4j/src/main/java/net/byteflux/libby/logging/adapters/SLF4JLogAdapter.java: -------------------------------------------------------------------------------- 1 | package net.byteflux.libby.logging.adapters; 2 | 3 | import net.byteflux.libby.logging.LogLevel; 4 | import org.slf4j.Logger; 5 | 6 | import static java.util.Objects.requireNonNull; 7 | 8 | /** 9 | * Logging adapter that logs to a SLF4J logger. 10 | */ 11 | public class SLF4JLogAdapter implements LogAdapter { 12 | /** 13 | * SLF4J logger 14 | */ 15 | private final Logger logger; 16 | 17 | /** 18 | * Creates a new SLF4J log adapter that logs to a {@link Logger}. 19 | * 20 | * @param logger the SLF4J logger to wrap 21 | */ 22 | public SLF4JLogAdapter(Logger logger) { 23 | this.logger = requireNonNull(logger, "logger"); 24 | } 25 | 26 | /** 27 | * Logs a message with the provided level to the SLF4J logger. 28 | * 29 | * @param level message severity level 30 | * @param message the message to log 31 | */ 32 | @Override 33 | public void log(LogLevel level, String message) { 34 | switch (requireNonNull(level, "level")) { 35 | case DEBUG: 36 | logger.debug(message); 37 | break; 38 | case INFO: 39 | logger.info(message); 40 | break; 41 | case WARN: 42 | logger.warn(message); 43 | break; 44 | case ERROR: 45 | logger.error(message); 46 | break; 47 | } 48 | } 49 | 50 | /** 51 | * Logs a message and stack trace with the provided level to the SLF4J 52 | * logger. 53 | * 54 | * @param level message severity level 55 | * @param message the message to log 56 | * @param throwable the throwable to print 57 | */ 58 | @Override 59 | public void log(LogLevel level, String message, Throwable throwable) { 60 | switch (requireNonNull(level, "level")) { 61 | case DEBUG: 62 | logger.debug(message, throwable); 63 | break; 64 | case INFO: 65 | logger.info(message, throwable); 66 | break; 67 | case WARN: 68 | logger.warn(message, throwable); 69 | break; 70 | case ERROR: 71 | logger.error(message, throwable); 72 | break; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /sponge/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | net.byteflux 9 | libby 10 | 0.0.2-SNAPSHOT 11 | 12 | 13 | libby-sponge 14 | 15 | 16 | 17 | sponge 18 | https://repo.spongepowered.org/maven/ 19 | 20 | 21 | 22 | 23 | 24 | net.byteflux 25 | libby-core 26 | 0.0.2-SNAPSHOT 27 | 28 | 29 | net.byteflux 30 | libby-slf4j 31 | 0.0.2-SNAPSHOT 32 | 33 | 34 | org.spongepowered 35 | spongeapi 36 | 7.1.0 37 | provided 38 | 39 | 40 | 41 | 42 | 43 | 44 | maven-compiler-plugin 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /sponge/src/main/java/net/byteflux/libby/SpongeLibraryManager.java: -------------------------------------------------------------------------------- 1 | package net.byteflux.libby; 2 | 3 | import com.google.inject.Inject; 4 | import net.byteflux.libby.classloader.URLClassLoaderHelper; 5 | import net.byteflux.libby.logging.adapters.SLF4JLogAdapter; 6 | import org.slf4j.Logger; 7 | import org.spongepowered.api.config.ConfigDir; 8 | 9 | import java.net.URLClassLoader; 10 | import java.nio.file.Path; 11 | 12 | import static java.util.Objects.requireNonNull; 13 | 14 | /** 15 | * A runtime dependency manager for Sponge plugins. 16 | */ 17 | public class SpongeLibraryManager extends LibraryManager { 18 | /** 19 | * Plugin classpath helper 20 | */ 21 | private final URLClassLoaderHelper classLoader; 22 | 23 | /** 24 | * Creates a new Sponge library manager. 25 | * 26 | * @param logger the plugin logger 27 | * @param dataDirectory plugin's data directory 28 | * @param plugin the plugin to manage 29 | */ 30 | @Inject 31 | private SpongeLibraryManager(Logger logger, @ConfigDir(sharedRoot = false) Path dataDirectory, T plugin) { 32 | super(new SLF4JLogAdapter(logger), dataDirectory); 33 | classLoader = new URLClassLoaderHelper((URLClassLoader) requireNonNull(plugin, "plugin").getClass().getClassLoader()); 34 | } 35 | 36 | /** 37 | * Adds a file to the Sponge plugin's classpath. 38 | * 39 | * @param file the file to add 40 | */ 41 | @Override 42 | protected void addToClasspath(Path file) { 43 | classLoader.addToClasspath(file); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /velocity/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | net.byteflux 9 | libby 10 | 0.0.2-SNAPSHOT 11 | 12 | 13 | libby-velocity 14 | 15 | 16 | 17 | velocity 18 | https://repo.velocitypowered.com/snapshots/ 19 | 20 | 21 | 22 | 23 | 24 | net.byteflux 25 | libby-core 26 | 0.0.2-SNAPSHOT 27 | 28 | 29 | net.byteflux 30 | libby-slf4j 31 | 0.0.2-SNAPSHOT 32 | 33 | 34 | com.velocitypowered 35 | velocity-api 36 | 1.0.0-SNAPSHOT 37 | provided 38 | 39 | 40 | 41 | 42 | 43 | 44 | maven-compiler-plugin 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /velocity/src/main/java/net/byteflux/libby/VelocityLibraryManager.java: -------------------------------------------------------------------------------- 1 | package net.byteflux.libby; 2 | 3 | import com.google.inject.Inject; 4 | import com.velocitypowered.api.plugin.PluginManager; 5 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 6 | import net.byteflux.libby.logging.adapters.SLF4JLogAdapter; 7 | import org.slf4j.Logger; 8 | 9 | import java.nio.file.Path; 10 | 11 | import static java.util.Objects.requireNonNull; 12 | 13 | /** 14 | * A runtime dependency manager for Velocity plugins. 15 | */ 16 | public class VelocityLibraryManager extends LibraryManager { 17 | /** 18 | * Velocity plugin manager used for adding files to the plugin's classpath 19 | */ 20 | private final PluginManager pluginManager; 21 | 22 | /** 23 | * The plugin instance required by the plugin manager to add files to the 24 | * plugin's classpath 25 | */ 26 | private final T plugin; 27 | 28 | /** 29 | * Creates a new Velocity library manager. 30 | * 31 | * @param logger the plugin logger 32 | * @param dataDirectory plugin's data directory 33 | * @param pluginManager Velocity plugin manager 34 | * @param plugin the plugin to manage 35 | */ 36 | @Inject 37 | private VelocityLibraryManager(Logger logger, 38 | @DataDirectory Path dataDirectory, 39 | PluginManager pluginManager, 40 | T plugin) { 41 | 42 | super(new SLF4JLogAdapter(logger), dataDirectory); 43 | this.pluginManager = requireNonNull(pluginManager, "pluginManager"); 44 | this.plugin = requireNonNull(plugin, "plugin"); 45 | } 46 | 47 | /** 48 | * Adds a file to the Velocity plugin's classpath. 49 | * 50 | * @param file the file to add 51 | */ 52 | @Override 53 | protected void addToClasspath(Path file) { 54 | pluginManager.addToClasspath(plugin, file); 55 | } 56 | } 57 | --------------------------------------------------------------------------------