ClassWorld.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.codehaus.plexus.classworlds;
/*
* Copyright 2001-2006 Codehaus Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
import org.codehaus.plexus.classworlds.realm.FilteredClassRealm;
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
/**
* A collection of <code>ClassRealm</code>s, indexed by id.
*
* @author <a href="mailto:bob@eng.werken.com">bob mcwhirter</a>
*/
public class ClassWorld implements Closeable {
private static final System.Logger LOGGER = System.getLogger(ClassWorld.class.getName());
private Map<String, ClassRealm> realms;
private final List<ClassWorldListener> listeners = new ArrayList<>();
private ModuleLayer moduleLayer;
private ModuleLayer.Controller moduleLayerController;
public ClassWorld(String realmId, ClassLoader classLoader) {
this();
try {
newRealm(realmId, classLoader);
} catch (DuplicateRealmException e) {
// Will never happen as we are just creating the world.
}
}
public ClassWorld() {
this.realms = new LinkedHashMap<>();
}
public ClassRealm newRealm(String id) throws DuplicateRealmException {
return newRealm(id, getClass().getClassLoader());
}
public ClassRealm newRealm(String id, ClassLoader classLoader) throws DuplicateRealmException {
return newRealm(id, classLoader, null);
}
/**
* Adds a class realm with filtering.
* Only resources/classes whose name matches a given predicate are exposed.
* @param id The identifier for this realm, must not be <code>null</code>.
* @param classLoader The base class loader for this realm, may be <code>null</code> to use the bootstrap class
* loader.
* @param filter a predicate to apply to each resource name to determine if it should be loaded through this class loader
* @return the created class realm
* @throws DuplicateRealmException in case a realm with the given id does already exist
* @since 2.7.0
* @see FilteredClassRealm
*/
public synchronized ClassRealm newRealm(String id, ClassLoader classLoader, Predicate<String> filter)
throws DuplicateRealmException {
if (realms.containsKey(id)) {
throw new DuplicateRealmException(this, id);
}
ClassRealm realm;
if (filter == null) {
realm = new ClassRealm(this, id, classLoader);
} else {
realm = new FilteredClassRealm(filter, this, id, classLoader);
}
realms.put(id, realm);
for (ClassWorldListener listener : listeners) {
listener.realmCreated(realm);
}
return realm;
}
/**
* Closes all contained class realms.
* @since 2.7.0
*/
@Override
public synchronized void close() throws IOException {
realms.values().forEach(this::disposeRealm);
realms.clear();
}
public synchronized void disposeRealm(String id) throws NoSuchRealmException {
ClassRealm realm = realms.remove(id);
if (realm != null) {
disposeRealm(realm);
} else {
throw new NoSuchRealmException(this, id);
}
}
private void disposeRealm(ClassRealm realm) {
try {
realm.close();
} catch (IOException ignore) {
}
for (ClassWorldListener listener : listeners) {
listener.realmDisposed(realm);
}
}
public synchronized ClassRealm getRealm(String id) throws NoSuchRealmException {
if (realms.containsKey(id)) {
return realms.get(id);
}
throw new NoSuchRealmException(this, id);
}
public synchronized Collection<ClassRealm> getRealms() {
return Collections.unmodifiableList(new ArrayList<>(realms.values()));
}
public synchronized void setModuleLayer(ModuleLayer moduleLayer, ModuleLayer.Controller controller) {
this.moduleLayer = moduleLayer;
this.moduleLayerController = controller;
}
public ModuleLayer getModuleLayer() {
return moduleLayer;
}
public ModuleLayer.Controller getModuleLayerController() {
return moduleLayerController;
}
/**
* Exports a package from a named module to the given target module.
* Looks up the source module in the runtime layer first, then the boot layer.
* Only runtime layer modules can be modified via the Controller; boot layer
* modules require {@code --add-exports} in the launcher script.
*
* @param moduleName the source module name
* @param packageName the package to export
* @param target the target module to export to
*/
public synchronized void addExports(String moduleName, String packageName, Module target) {
applyModuleAccess(moduleName, packageName, target, false);
}
/**
* Opens a package from a named module to the given target module for deep reflection.
* Looks up the source module in the runtime layer first, then the boot layer.
* Only runtime layer modules can be modified via the Controller; boot layer
* modules require {@code --add-opens} in the launcher script.
*
* @param moduleName the source module name
* @param packageName the package to open
* @param target the target module to open to
*/
public synchronized void addOpens(String moduleName, String packageName, Module target) {
applyModuleAccess(moduleName, packageName, target, true);
}
/**
* Adds a reads edge from the named source module to the given target module.
* Only runtime layer modules can be modified via the Controller.
*
* @param sourceModuleName the source module name
* @param target the target module to read
*/
public synchronized void addReads(String sourceModuleName, Module target) {
Module source = findModule(sourceModuleName);
if (source == null) {
LOGGER.log(
System.Logger.Level.WARNING,
"module-access: module ''{0}'' not found, ignoring add-reads",
sourceModuleName);
return;
}
if (moduleLayerController == null) {
LOGGER.log(
System.Logger.Level.WARNING,
"module-access: no ModuleLayer controller available, cannot add-reads {0}",
sourceModuleName);
return;
}
if (isBootLayerModule(source)) {
LOGGER.log(
System.Logger.Level.WARNING,
"module-access: module ''{0}'' is in the boot layer and cannot be modified at runtime;"
+ " use --add-reads {0}=ALL-UNNAMED on the JVM command line instead",
sourceModuleName);
return;
}
moduleLayerController.addReads(source, target);
}
private void applyModuleAccess(String moduleName, String packageName, Module target, boolean open) {
String directive = open ? "add-opens" : "add-exports";
Module source = findModule(moduleName);
if (source == null) {
LOGGER.log(
System.Logger.Level.WARNING,
"module-access: module ''{0}'' not found, ignoring {1} {0}/{2}",
moduleName,
directive,
packageName);
return;
}
if (moduleLayerController == null) {
LOGGER.log(
System.Logger.Level.WARNING,
"module-access: no ModuleLayer controller available, cannot {0} {1}/{2}",
directive,
moduleName,
packageName);
return;
}
if (isBootLayerModule(source)) {
LOGGER.log(
System.Logger.Level.WARNING,
"module-access: module ''{0}'' is in the boot layer and cannot be modified at runtime;"
+ " use --{1} {0}/{2}=ALL-UNNAMED on the JVM command line instead",
moduleName,
directive,
packageName);
return;
}
if (open) {
moduleLayerController.addOpens(source, packageName, target);
} else {
moduleLayerController.addExports(source, packageName, target);
}
}
private Module findModule(String moduleName) {
if (moduleLayer != null) {
Module m = moduleLayer.findModule(moduleName).orElse(null);
if (m != null) {
return m;
}
}
return ModuleLayer.boot().findModule(moduleName).orElse(null);
}
private boolean isBootLayerModule(Module module) {
return module.getLayer() == ModuleLayer.boot();
}
// from exports branch
public synchronized ClassRealm getClassRealm(String id) {
if (realms.containsKey(id)) {
return realms.get(id);
}
return null;
}
public synchronized void addListener(ClassWorldListener listener) {
// TODO ideally, use object identity, not equals
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
public synchronized void removeListener(ClassWorldListener listener) {
listeners.remove(listener);
}
}