/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * 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 org.spongepowered.asm.util.launchwrapper; import java.lang.reflect.Field; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import net.minecraft.launchwrapper.LaunchClassLoader; /** * Utility class for reflecting into {@link LaunchClassLoader}. We <b>do not * write</b> anything of the classloader fields, but we need to be able to read * them to perform some validation tasks, and insert entries for mixin "classes" * into the invalid classes set. */ public final class LaunchClassLoaderUtil { /** * ClassLoader -> util mapping */ private static final Map<LaunchClassLoader, LaunchClassLoaderUtil> utils = new HashMap<LaunchClassLoader, LaunchClassLoaderUtil>(); /** * ClassLoader for this util */ private final LaunchClassLoader classLoader; // Reflected fields private Map<String, Class<?>> cachedClasses; private final Set<String> invalidClasses; private final Set<String> classLoaderExceptions; private final Set<String> transformerExceptions; /** * Singleton, use factory to get an instance * * @param classLoader class loader */ private LaunchClassLoaderUtil(LaunchClassLoader classLoader) { this.classLoader = classLoader; this.cachedClasses = LaunchClassLoaderUtil.<Map<String, Class<?>>>getField(classLoader, "cachedClasses"); this.invalidClasses = LaunchClassLoaderUtil.<Set<String>>getField(classLoader, "invalidClasses"); this.classLoaderExceptions = LaunchClassLoaderUtil.<Set<String>>getField(classLoader, "classLoaderExceptions"); this.transformerExceptions = LaunchClassLoaderUtil.<Set<String>>getField(classLoader, "transformerExceptions"); } /** * Get the classloader */ public LaunchClassLoader getClassLoader() { return this.classLoader; } /** * Get all loaded class names from the cache */ public Set<String> getLoadedClasses() { return this.getLoadedClasses(null); } /** * Get the names of loaded classes from the cache, filter using the supplied * filter string * * @param filter filter string or null * @return set of class names */ public Set<String> getLoadedClasses(String filter) { Set<String> loadedClasses = new HashSet<String>(); for (String className : this.cachedClasses.keySet()) { if (filter == null || className.startsWith(filter)) { loadedClasses.add(className); } } return loadedClasses; } /** * Get whether a class name exists in the cache (indicating it was loaded * via the inner loader * * @param name class name * @return true if the class name exists in the cache */ public boolean isClassLoaded(String name) { if (this.cachedClasses != null && this.cachedClasses.containsKey(name)) { return true; } return false; } /** * Get whether the specified name or transformedName exist in either of the * exclusion lists * * @param name class name * @param transformedName transformed class name * @return true if either exclusion list contains either of the names */ public boolean isClassExcluded(String name, String transformedName) { for (final String exception : this.getClassLoaderExceptions()) { if (transformedName.startsWith(exception) || name.startsWith(exception)) { return true; } } for (final String exception : this.getTransformerExceptions()) { if (transformedName.startsWith(exception) || name.startsWith(exception)) { return true; } } return false; } /** * Stuff a class name directly into the invalidClasses set, this prevents * the loader from classloading the named class. This is used by the mixin * processor to prevent classloading of mixin classes * * @param name class name */ public void registerInvalidClass(String name) { if (this.invalidClasses != null) { this.invalidClasses.add(name); } } /** * Get the classloader exclusions from the target classloader */ public Set<String> getClassLoaderExceptions() { if (this.classLoaderExceptions != null) { return this.classLoaderExceptions; } return Collections.<String>emptySet(); } /** * Get the transformer exclusions from the target classloader */ public Set<String> getTransformerExceptions() { if (this.transformerExceptions != null) { return this.transformerExceptions; } return Collections.<String>emptySet(); } @SuppressWarnings("unchecked") private static <T> T getField(LaunchClassLoader classLoader, String fieldName) { try { Field field = LaunchClassLoader.class.getDeclaredField(fieldName); field.setAccessible(true); return (T)field.get(classLoader); } catch (Exception ex) { ex.printStackTrace(); } return null; } /** * Get the utility class for the supplied classloader * * @param classLoader classLoader to fetch utility wrapper for * @return utility wrapper */ public static LaunchClassLoaderUtil forClassLoader(LaunchClassLoader classLoader) { LaunchClassLoaderUtil util = LaunchClassLoaderUtil.utils.get(classLoader); if (util == null) { util = new LaunchClassLoaderUtil(classLoader); LaunchClassLoaderUtil.utils.put(classLoader, util); } return util; } }