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