001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.plugins; 003 004import java.net.URL; 005import java.net.URLClassLoader; 006import java.util.ArrayList; 007import java.util.Arrays; 008import java.util.Collection; 009import java.util.Objects; 010 011import org.openstreetmap.josm.tools.Logging; 012 013/** 014 * Class loader for JOSM plugins. 015 * <p> 016 * In addition to the classes in the plugin jar file, it loads classes of required 017 * plugins. The JOSM core classes should be provided by the parent class loader. 018 * @since 12322 019 */ 020public class PluginClassLoader extends URLClassLoader { 021 022 private final Collection<PluginClassLoader> dependencies; 023 024 static { 025 ClassLoader.registerAsParallelCapable(); 026 } 027 028 /** 029 * Create a new PluginClassLoader. 030 * @param urls URLs of the plugin jar file (and extra libraries) 031 * @param parent the parent class loader (for JOSM core classes) 032 * @param dependencies class loaders of required plugin; can be null 033 */ 034 public PluginClassLoader(URL[] urls, ClassLoader parent, Collection<PluginClassLoader> dependencies) { 035 super(urls, parent); 036 this.dependencies = dependencies == null ? new ArrayList<>() : new ArrayList<>(dependencies); 037 } 038 039 /** 040 * Add class loader of a required plugin. 041 * This plugin will have access to the classes of the dependent plugin 042 * @param dependency the class loader of the required plugin 043 * @return {@code true} if the collection of dependencies changed as a result of the call 044 * @since 12867 045 */ 046 public boolean addDependency(PluginClassLoader dependency) { 047 // Add dependency only if not already present (directly or transitively through another one) 048 boolean result = !dependencies.contains(Objects.requireNonNull(dependency, "dependency")) 049 && !dependencies.stream().anyMatch(pcl -> pcl.dependencies.contains(dependency)) 050 && dependencies.add(dependency); 051 if (result) { 052 // Now, remove top-level single dependencies, which would be children of the added one 053 dependencies.removeIf(pcl -> pcl.dependencies.isEmpty() && dependency.dependencies.contains(pcl)); 054 } 055 return result; 056 } 057 058 @Override 059 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 060 Class<?> result = findLoadedClass(name); 061 if (result == null) { 062 for (PluginClassLoader dep : dependencies) { 063 try { 064 result = dep.loadClass(name, resolve); 065 if (result != null) { 066 return result; 067 } 068 } catch (ClassNotFoundException e) { 069 // do nothing 070 Logging.trace("Plugin class not found in {0}: {1}", dep, e.getMessage()); 071 Logging.trace(e); 072 } 073 } 074 result = super.loadClass(name, resolve); 075 } 076 if (result != null) { 077 return result; 078 } 079 throw new ClassNotFoundException(name); 080 } 081 082 @Override 083 public String toString() { 084 return "PluginClassLoader [urls=" + Arrays.toString(getURLs()) + 085 (dependencies.isEmpty() ? "" : ", dependencies=" + dependencies) + ']'; 086 } 087}