Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 218 additions & 0 deletions src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,21 @@

import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -438,6 +445,217 @@ public Enumeration<URL> loadResourcesFromParent(String name) {
return null;
}

// ---------------------------------------------------------------------------------------------
// Packages are visible exactly where classes in them are: own class path, imports, parent
// ---------------------------------------------------------------------------------------------

/**
* Realms whose package lookup is running on the current thread. Imports and parents may form cycles, which class
* loading never walks for a missing package but {@link Package#getPackage(String)} does.
*/
private static final ThreadLocal<Set<ClassRealm>> PACKAGE_LOOKUPS = new ThreadLocal<>();

@Override
@SuppressWarnings("deprecation")
protected Package getPackage(String name) {
if (!enterPackageLookup()) {
return null;
}
try {
Package pkg = super.getPackage(name);

for (String className : getClassNamesIn(name, foreignImports)) {
if (pkg != null) {
break;
}
ClassLoader importClassLoader = getImportClassLoader(className);
if (importClassLoader != null) {
pkg = getPackage(importClassLoader, name);
}
}

ClassLoader parent = getParentClassLoader();
if (pkg == null && parent != null && isPackageImportedFromParent(name)) {
pkg = getPackage(parent, name);
}

return pkg;
} finally {
exitPackageLookup();
}
}

@Override
protected Package[] getPackages() {
if (!enterPackageLookup()) {
return new Package[0];
}
try {
Map<String, Package> packages = new LinkedHashMap<>();

for (Package pkg : super.getPackages()) {
packages.putIfAbsent(pkg.getName(), pkg);
}

for (ClassLoader importClassLoader : getImportClassLoaders()) {
for (Package pkg : getPackages(importClassLoader)) {
if (isPackageImportedFrom(pkg.getName(), importClassLoader)) {
packages.putIfAbsent(pkg.getName(), pkg);
}
}
}

ClassLoader parent = getParentClassLoader();
if (parent != null) {
for (Package pkg : getPackages(parent)) {
if (isPackageImportedFromParent(pkg.getName())) {
packages.putIfAbsent(pkg.getName(), pkg);
}
}
}

return packages.values().toArray(new Package[0]);
} finally {
exitPackageLookup();
}
}

/**
* Class names standing for every way a class in the package can be routed by the given imports: one no class can
* have, which only package imports match, plus each imported name that is a class directly inside the package.
*/
private static Collection<String> getClassNamesIn(String packageName, Collection<Entry> imports) {
Collection<String> classNames = new ArrayList<>();
classNames.add(packageName + ".-");
if (imports != null) {
for (Entry entry : imports) {
String importName = entry.getPackageName();
int index = importName.lastIndexOf('.');
// an import without a dot names a class in the unnamed package, or a package import
String importPackage = index < 0 ? "" : importName.substring(0, index);
if (index != 0
&& !importName.isEmpty()
&& !importName.endsWith("*")
&& importPackage.equals(packageName)) {
classNames.add(importName);
}
}
}
return classNames;
}

private boolean isPackageImportedFrom(String packageName, ClassLoader importClassLoader) {
for (String className : getClassNamesIn(packageName, foreignImports)) {
if (getImportClassLoader(className) == importClassLoader) {
return true;
}
}
return false;
}

private boolean isPackageImportedFromParent(String packageName) {
for (String className : getClassNamesIn(packageName, parentImports)) {
if (isImportedFromParent(className)) {
return true;
}
}
return false;
}

private Collection<ClassLoader> getImportClassLoaders() {
Collection<ClassLoader> classLoaders = new LinkedHashSet<>();
for (Entry entry : foreignImports) {
if (entry.getClassLoader() != null) {
classLoaders.add(entry.getClassLoader());
}
}
return classLoaders;
}

private boolean enterPackageLookup() {
Set<ClassRealm> lookups = PACKAGE_LOOKUPS.get();
if (lookups == null) {
lookups = Collections.newSetFromMap(new IdentityHashMap<>());
PACKAGE_LOOKUPS.set(lookups);
}
return lookups.add(this);
}

private void exitPackageLookup() {
Set<ClassRealm> lookups = PACKAGE_LOOKUPS.get();
lookups.remove(this);
if (lookups.isEmpty()) {
PACKAGE_LOOKUPS.remove();
}
}

// ClassLoader.getDefinedPackage(s) are public since Java 9; on Java 8 only the protected getPackage(s) exist
private static final Method GET_DEFINED_PACKAGE = findMethod("getDefinedPackage", String.class);

private static final Method GET_DEFINED_PACKAGES = findMethod("getDefinedPackages");

private static final Method GET_PACKAGE =
GET_DEFINED_PACKAGE == null ? findMethod("getPackage", String.class) : null;

private static final Method GET_PACKAGES = GET_DEFINED_PACKAGES == null ? findMethod("getPackages") : null;

private static Method findMethod(String name, Class<?>... parameterTypes) {
try {
Method method = ClassLoader.class.getDeclaredMethod(name, parameterTypes);
if (!Modifier.isPublic(method.getModifiers())) {
method.setAccessible(true);
}
return method;
} catch (NoSuchMethodException | RuntimeException e) {
return null;
}
}

private static Object invoke(Method method, ClassLoader classLoader, Object... args) {
if (method == null) {
return null;
}
try {
return method.invoke(classLoader, args);
} catch (ReflectiveOperationException | RuntimeException e) {
return null;
}
}

private static Package getPackage(ClassLoader classLoader, String name) {
if (classLoader instanceof ClassRealm) {
return ((ClassRealm) classLoader).getPackage(name);
}
if (GET_DEFINED_PACKAGE == null) {
return (Package) invoke(GET_PACKAGE, classLoader, name);
}
for (ClassLoader loader = classLoader; loader != null; loader = loader.getParent()) {
Package pkg = (Package) invoke(GET_DEFINED_PACKAGE, loader, name);
if (pkg != null) {
return pkg;
}
}
return null;
}

private static Package[] getPackages(ClassLoader classLoader) {
if (classLoader instanceof ClassRealm) {
return ((ClassRealm) classLoader).getPackages();
}
if (GET_DEFINED_PACKAGES == null) {
Package[] packages = (Package[]) invoke(GET_PACKAGES, classLoader);
return packages != null ? packages : new Package[0];
}
Collection<Package> packages = new ArrayList<>();
for (ClassLoader loader = classLoader; loader != null; loader = loader.getParent()) {
Package[] defined = (Package[]) invoke(GET_DEFINED_PACKAGES, loader);
if (defined != null) {
Collections.addAll(packages, defined);
}
}
return packages.toArray(new Package[0]);
}

static {
registerAsParallelCapable();
}
Expand Down
21 changes: 21 additions & 0 deletions src/test/java/DefaultPackageClass.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.
*/

/** A class in the unnamed package, loaded by realms in the package lookup tests. */
public class DefaultPackageClass {}
Loading
Loading