/*
* Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.config;
import static com.sun.max.lang.Classes.*;
import java.lang.reflect.*;
import java.util.*;
import com.sun.max.annotate.*;
import com.sun.max.lang.*;
import com.sun.max.program.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.thread.*;
/**
* Describes a package in the Maxine VM boot image, providing programmatic package information manipulation, which is
* lacking in {@link java.lang.Package}.
* <p>
* Any {@link BootImagePackage} subclass describing one or more packages that
* declare {@link Word} subclasses must override {@link #wordSubclasses()}.
* <p>
* Any {@link BootImagePackage} subclass describing one or more packages whose
* classes instantiate one or more {@link VmThreadLocal}s must reference those thread
* local objects in its constructor. This ensures all {@link VmThreadLocal}s
* are registered before the {@linkplain VmThreadLocal#tlaSize() size} of the
* thread locals area is computed. The only exception to this requirement are
* the thread locals declared in the {@link VmThreadLocal} class itself.
*/
@HOSTED_ONLY
public class BootImagePackage implements Comparable<BootImagePackage>, Cloneable {
/**
* The name of the package.
*/
private String name;
private Map<String, BootImagePackage> others = Collections.emptyMap();
/**
* Specifies if this package also denotes all its sub-packages.
*/
public boolean recursive;
/**
* Exact set of classes to be returned by {@link #listClasses(Classpath)}.
* If this value is {@code null}, then a class path {@linkplain ClassSearch search}
* is performed to find the classes in this package.
*/
private Set<String> classes;
private static final Class[] NO_CLASSES = {};
/**
* Creates an object denoting the package whose name is {@code this.getClass().getPackage().getName()}.
*/
protected BootImagePackage() {
this.name = getPackageName(getClass());
}
/**
* Creates an object denoting the package whose name is {@code this.getClass().getPackage().getName()}
* and, if {@code recursive == true}, all its sub-packages.
*/
protected BootImagePackage(String pkgName, boolean recursive) {
this.name = pkgName == null ? getClass().getPackage().getName() : pkgName;
this.recursive = recursive;
}
/**
* Creates an object denoting the package whose name is {@code this.getClass().getPackage().getName()} as well as
* the packages specified by {@code packageSpecs}.
*
* A package specification is one of the following:
* <ol>
* <li>A string ending with {@code ".*"} (e.g. {@code "com.sun.max.unsafe.*"}). This denotes a single package.</li>
* <li>A string ending with {@code ".**"} (e.g. {@code "com.sun.max.asm.**"}). This denotes a package and all its
* sub-packages.</li>
* <li>A name of a class available on the class path via {@link Class#forName(String)}.</li>
* </ol>
*
* @param packageSpecs a set of package specifications
*/
protected BootImagePackage(String... packageSpecs) {
this.name = getClass().getPackage().getName();
this.recursive = false;
Map<String, BootImagePackage> pkgs = new TreeMap<String, BootImagePackage>();
for (String spec : packageSpecs) {
String pkgName;
String className = null;
boolean recursive = false;
if (spec.endsWith(".**")) {
recursive = true;
pkgName = spec.substring(0, spec.length() - 3);
} else if (spec.endsWith(".*")) {
pkgName = spec.substring(0, spec.length() - 2);
} else {
className = spec;
try {
Class.forName(className);
} catch (ClassNotFoundException e) {
throw ProgramError.unexpected("Package specification does not end with \".*\" or \".**\" or name a class on the class path: " + spec);
}
pkgName = getPackageName(className);
}
// Honor existing Package.java classes, but watch for recursion when we are explicitly
// including classes in this package
BootImagePackage pkg = null;
if (className != null && pkgName.equals(name)) {
pkg = this;
} else {
pkg = fromName(pkgName);
if (pkg == null) {
pkg = pkgs.get(pkgName);
if (pkg == null) {
pkg = cloneAs(pkgName);
}
pkg.recursive = recursive;
} else {
assert !pkg.recursive : "Package created via reflection should not be recursive";
pkg.recursive = recursive;
}
BootImagePackage oldPkg = pkgs.put(pkgName, pkg);
assert oldPkg == null || oldPkg == pkg;
}
if (className != null) {
pkg.initClasses();
pkg.classes.add(className);
}
}
this.others = pkgs;
}
/**
* Determines if this package is part of the boot image under the given configuration.
*
* @param vmConfig
*/
public boolean isPartOfMaxineVM(VMConfiguration vmConfig) {
return true;
}
/**
* Determines if this package contains any classes annotated with {@link METHOD_SUBSTITUTIONS}.
*/
public boolean containsMethodSubstitutions() {
return false;
}
private void initClasses() {
if (classes == null) {
classes = new HashSet<String>();
}
}
/**
* Lists the classes included from this package.
* If this package object was created based on at least one explicit
* class name, then only the set of explicit class names is returned.
* Otherwise the names of all the class files on the given class path
* whose package name matches this package is returned.
*
* Note that in the former case, there is no check as to whether the
* class files really exist.
*
* @return the class names
*/
public String[] listClasses(Classpath classpath) {
final HashSet<String> classNames = new HashSet<String>();
if (classes != null) {
assert !classes.isEmpty();
return classes.toArray(new String[classes.size()]);
}
final ClassSearch classSearch = new ClassSearch() {
@Override
protected boolean visitClass(boolean isArchiveEntry, String className) {
if (!className.endsWith("package-info") && !classNames.contains(className) && includesClass(className)) {
classNames.add(className);
}
return true;
}
};
classSearch.run(classpath, name.replace('.', '/'));
return classNames.toArray(new String[classNames.size()]);
}
/**
* Determines if this package includes a given class.
* This can be overridden to implement class exclusion.
*/
protected boolean includesClass(String className) {
return name.equals(getPackageName(className));
}
/**
* Check whether the given class should be in the boot image.
*/
public boolean isBootImageClass(String name) {
if (classes == null) {
return true;
}
return classes.contains(name);
}
/**
* Last chance for special setup for this instance.
* Call just before loading any classes.
*/
public void loading() {
}
/**
* Gets an instance of the class named "Package" in a named package.
*
* @param packageName denotes the name of a package which may contain a subclass of {@link BootImagePackage} named
* "Package"
* @return an instance of the class name "Package" in {@code packageName}. If such a class does not exist or there
* was an error {@linkplain Class#newInstance() instantiating} it, then {@code null} is returned
*/
public static BootImagePackage fromName(String packageName) {
final String name = packageName + "." + Package.class.getSimpleName();
if (name.equals(java.lang.Package.class.getName())) {
// Special case!
return null;
}
try {
final Class packageClass = Class.forName(name);
return (BootImagePackage) packageClass.newInstance();
} catch (ClassNotFoundException e) {
} catch (NoClassDefFoundError e) {
} catch (InstantiationException e) {
} catch (IllegalAccessException e) {
}
return null;
}
public static BootImagePackage fromClass(Class javaClass) {
final java.lang.Package javaPackage = javaClass.getPackage();
if (javaPackage == null) {
return null;
}
return fromName(javaPackage.getName());
}
public String name() {
return name;
}
public BootImagePackage superPackage() {
final int end = name().lastIndexOf('.');
if (end < 0) {
return null;
}
return fromName(name().substring(0, end));
}
/**
* Gets the subclasses of {@link Word} in this package.
* The returned array must not include boxed (see com.sun.max.unsafe.Boxed)
* word types as they are derived from the name of the unboxed types.
*/
public Class[] wordSubclasses() {
return NO_CLASSES;
}
protected static void registerThreadLocal(VmThreadLocal tl) {
}
protected static void registerThreadLocal(Class c, String fieldName) {
registerThreadLocal(c.getName(), fieldName);
}
protected static void registerThreadLocal(String className, String fieldName) {
try {
Class c = Class.forName(className);
Field f = c.getDeclaredField(fieldName);
f.setAccessible(true);
Object value = f.get(null);
FatalError.check(value instanceof VmThreadLocal, f + " is not an instance of " + VmThreadLocal.class);
} catch (Exception e) {
throw FatalError.unexpected("Error looking up thread local " + className + "." + fieldName, e);
}
}
public boolean isSubPackageOf(BootImagePackage superPackage) {
return name().startsWith(superPackage.name());
}
static class RootPackageInfo {
final BootImagePackage root;
final Set<String> pkgNames = new TreeSet<String>();
RootPackageInfo(Classpath classpath, BootImagePackage root) {
this.root = root;
//long start = System.currentTimeMillis();
final String classNamePrefix = root.name() + ".";
new ClassSearch() {
@Override
protected boolean visitClass(String className) {
if (className.startsWith(classNamePrefix)) {
pkgNames.add(Classes.getPackageName(className));
}
return true;
}
}.run(classpath, root.name().replace('.', '/'));
//System.out.println("scan: " + root + " [pkgs=" + pkgNames.size() + ", time=" + (System.currentTimeMillis() - start) + "ms]");
}
@Override
public String toString() {
return root.toString();
}
}
private BootImagePackage cloneAs(String pkgName) {
assert fromName(pkgName) == null;
try {
BootImagePackage pkg = (BootImagePackage) clone();
pkg.name = pkgName;
return pkg;
} catch (CloneNotSupportedException ex) {
throw ProgramError.unexpected("BootImagePackage failed to clone " + this);
}
}
/**
* Add a new package or merge previous state in if it exists already.
* @param pkg the (presumed) new package
* @param pkgMap the global map of all packages discovered so far
*/
private static void add(BootImagePackage pkg, Map<String, BootImagePackage> pkgMap) {
BootImagePackage oldPkg = pkgMap.get(pkg.name());
if (oldPkg == pkg) {
// if this identical then we must have added it to the list previously
} else {
if (oldPkg == null) {
// new
pkgMap.put(pkg.name(), pkg);
} else {
// merge into oldPkg
oldPkg.others.putAll(pkg.others);
if (pkg.classes != null) {
oldPkg.initClasses();
oldPkg.classes.addAll(pkg.classes);
}
}
}
}
/**
* Finds all sub-packages in the class path for a given set of root packages.
*
* We maintain a global map of all the packages discovered. Duplicates may arise in the processing from different
* subsystems referencing the same package, possibly including different subsets of the classes in the package. The
* first occurrence of a package is the canonical representative and duplicates have their relevant state merged
* into it.
*
* @param classpath the class path to search for packages
* @param roots array of subclass of {@code BootImagePackage} that define search start and match
* @return list of packages in the closure denoted by {@code roots}
*/
public static List<BootImagePackage> getTransitiveSubPackages(Classpath classpath, final BootImagePackage... roots) {
final Map<String, BootImagePackage> pkgMap = new TreeMap<String, BootImagePackage>();
final ArrayList<RootPackageInfo> rootInfos = new ArrayList<RootPackageInfo>(roots.length);
for (BootImagePackage root : roots) {
rootInfos.add(new RootPackageInfo(classpath, root));
}
int rootIndex = 0;
while (rootIndex < rootInfos.size()) {
RootPackageInfo info = rootInfos.get(rootIndex++);
for (String pkgName : info.pkgNames) {
BootImagePackage pkg = BootImagePackage.fromName(pkgName);
if (pkg == null) {
// No explicit Package class provided, so we will create one.
// Locate the closest parent in the tree that has a Package instance.
// N.B. One is guaranteed to exist, if only at the root.
// First check in case we are the root!
pkg = pkgMap.get(pkgName);
if (pkg == null) {
String parentPkgName = getPackageName(pkgName);
while (parentPkgName.length() != 0) {
BootImagePackage parent = pkgMap.get(parentPkgName);
if (parent != null) {
pkg = parent.cloneAs(pkgName);
break;
}
parentPkgName = getPackageName(parentPkgName);
}
}
assert pkg != null : pkgName;
}
add(pkg, pkgMap);
for (final BootImagePackage otherPkg : pkg.others.values()) {
// Add any redirects and, for recursive packages outside this tree, add them as a new root.
add(otherPkg, pkgMap);
if (!otherPkg.name().startsWith(info.root.name()) && otherPkg.recursive) {
rootInfos.add(new RootPackageInfo(classpath, otherPkg));
}
}
}
}
return Arrays.asList(pkgMap.values().toArray(new BootImagePackage[pkgMap.size()]));
}
private Map<Class<? extends VMScheme>, Class<? extends VMScheme>> schemeTypeToImplementation;
/**
* Registers a class in this package that implements a given scheme type.
*
* @param schemeType a scheme type
* @param schemeImplementation a class that implements {@code schemType}
*/
public synchronized <S extends VMScheme> void registerScheme(Class<S> schemeType, Class<? extends S> schemeImplementation) {
assert schemeType.isInterface() || Modifier.isAbstract(schemeType.getModifiers());
assert schemeImplementation.getPackage().getName().equals(name()) : "cannot register implmentation class from another package: " + schemeImplementation;
if (schemeTypeToImplementation == null) {
schemeTypeToImplementation = new IdentityHashMap<Class<? extends VMScheme>, Class<? extends VMScheme>>();
}
Class<? extends VMScheme> oldValue = schemeTypeToImplementation.put(schemeType, schemeImplementation);
assert oldValue == null;
}
/**
* Gets the class within this package implementing a given scheme type (represented as an abstract class or interface).
*
* @return the class directly within this package that implements {@code scheme} or null if no such class
* exists
*/
public synchronized <S extends VMScheme> Class<? extends S> schemeTypeToImplementation(Class<S> schemeType) {
if (schemeTypeToImplementation == null) {
return null;
}
final Class< ? extends VMScheme> implementation = schemeTypeToImplementation.get(schemeType);
if (implementation == null) {
return null;
}
return implementation.asSubclass(schemeType);
}
@Override
public boolean equals(Object other) {
if (other instanceof BootImagePackage) {
return name.equals(((BootImagePackage) other).name);
}
return false;
}
@Override
public int hashCode() {
return name().hashCode();
}
public int compareTo(BootImagePackage other) {
return name.compareTo(other.name);
}
public synchronized <S extends VMScheme> Class<? extends S> loadSchemeImplementation(Class<S> schemeType) {
final Class<? extends S> schemeImplementation = schemeTypeToImplementation(schemeType);
if (schemeImplementation == null) {
throw ProgramError.unexpected("could not find subclass of " + schemeType + " in " + this);
} else {
final Class<?> loadedImplementation = Classes.load(schemeType.getClassLoader(), schemeImplementation.getName());
return loadedImplementation.asSubclass(schemeType);
}
}
/**
* Instantiates the scheme implementation class in this package implementing a given scheme type.
*
* @param schemeType the interface or abstract class defining a scheme type
* @return a new instance of the scheme implementation class
*/
public synchronized <S extends VMScheme> S loadAndInstantiateScheme(Class<S> schemeType) {
final Class<? extends S> schemeImplementation = loadSchemeImplementation(schemeType);
try {
return schemeImplementation.newInstance();
} catch (Throwable throwable) {
throw ProgramError.unexpected("could not instantiate class: " + schemeImplementation.getName(), throwable);
}
}
@Override
public String toString() {
return name();
}
}