Skip to content

Commit

Permalink
feat: Add isolating classloader for sharing across projects
Browse files Browse the repository at this point in the history
Closes GH-55
  • Loading branch information
zml2008 committed Aug 24, 2024
1 parent 7768ac7 commit 49c86e6
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 0 deletions.
86 changes: 86 additions & 0 deletions src/main/java/net/kyori/mammoth/IsolatingClassLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* This file is part of mammoth, licensed under the MIT License.
*
* Copyright (c) 2024 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.kyori.mammoth;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Iterator;
import java.util.Set;
import org.gradle.api.file.FileCollection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* A factory for classloaders that will load classes from themselves rather than its parent where possible.
*
* <p>This can be used to create isolated environments for working with Gradle plugins, or providing an alternative to workers.</p>
*
* <p>The returned loader from any of these factory methods will be registered as parallel capable.</p>
*
* @since 1.4.0
*/
public final class IsolatingClassLoader {
private IsolatingClassLoader() {
}

/**
* Create a new loader based on a provided set of URLs.
*
* @param parent the parent loader
* @param urls the urls
* @return the newly created loader
* @since 1.4.0
*/
public static @NotNull URLClassLoader isolatingClassLoader(final @Nullable ClassLoader parent, final @NotNull URL @NotNull... urls) {
return new IsolatingClassLoaderImpl(urls, parent);
}

/**
* Create a new loader based on a provided file collection.
*
* @param parent the parent loader
* @param files the file collection
* @return the newly created loader
* @since 1.4.0
*/
public static @NotNull URLClassLoader isolatingClassLoader(final @Nullable ClassLoader parent, final @NotNull FileCollection files) {
final Set<File> unwrapped = files.getFiles();
final URL[] urls = new URL[unwrapped.size()];
final Iterator<File> it = files.iterator();
int idx = 0;
File file;
while (it.hasNext()) {
file = it.next();
try {
urls[idx++] = file.toURI().toURL();
} catch (final MalformedURLException ex) {
throw new IllegalArgumentException("Unable to include file " + file + " in classpath");
}
}

return new IsolatingClassLoaderImpl(urls, parent);
}
}
114 changes: 114 additions & 0 deletions src/main/java/net/kyori/mammoth/IsolatingClassLoaderImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* This file is part of mammoth, licensed under the MIT License.
*
* Copyright (c) 2024 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.kyori.mammoth;

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

final class IsolatingClassLoaderImpl extends URLClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}

private final ClassLoader parent;

// todo: maybe add transformer function (UnaryOperator<byte[]>)? just for fun
// todo: add a filter
IsolatingClassLoaderImpl(final URL[] urls, final ClassLoader parent) {
super(urls, parent);
this.parent = parent;
}

@Override
protected @Nullable Class<?> loadClass(final @NotNull String name, final boolean resolve) throws ClassNotFoundException {
synchronized (this.getClassLoadingLock(name)) {
Class<?> result = this.findLoadedClass(name);
if (result == null) {
try {
result = this.findClass(name);
} catch (final ClassNotFoundException ex) {
// ignore, delegate to parent
}
}

if (result == null) {
return super.loadClass(name, resolve);
}

if (resolve) {
this.resolveClass(result);
}
return result;
}
}

@Override
public @Nullable URL getResource(final String name) {
@Nullable URL result = this.findResource(name);
if (result == null) {
result = super.getResource(name);
}
return result;
}

@Override
public @NotNull Enumeration<URL> getResources(final String name) throws IOException {
return new Enumeration<URL>() {
@Nullable Enumeration<URL> active = IsolatingClassLoaderImpl.this.findResources(name);
@Nullable Enumeration<URL> staged = IsolatingClassLoaderImpl.this.parent == null
? ClassLoader.getSystemClassLoader().getResources(name)
: IsolatingClassLoaderImpl.this.parent.getResources(name);

private @Nullable Enumeration<URL> nextComponent() {
if (this.active == null) {
return null;
} else if (!this.active.hasMoreElements()) {
this.active = this.staged;
this.staged = null;
}
return this.active;
}

@Override
public boolean hasMoreElements() {
final @Nullable Enumeration<URL> component = this.nextComponent();
return component != null && component.hasMoreElements();
}

@Override
public @NotNull URL nextElement() {
final @Nullable Enumeration<URL> component = this.nextComponent();
if (component == null) {
throw new NoSuchElementException();
}
return component.nextElement();
}
};
}
}

0 comments on commit 49c86e6

Please sign in to comment.