/*
* 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 java.lang;
import java.lang.ref.SoftReference;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.net.URL;
import java.net.JarURLConnection;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import org.jikesrvm.classloader.BootstrapClassLoader;
import org.jikesrvm.classloader.RVMClass;
/**
* This class must be implemented by the VM vendor.
*
* An instance of class Package contains information about a Java package. This
* includes implementation and specification versions. Typically this
* information is retrieved from the manifest.
* <p>
* Packages are managed by class loaders. All classes loaded by the same loader
* from the same package share a Package instance.
*
*
* @see ClassLoader
* @since 1.0
*/
public class Package implements AnnotatedElement {
/**
* The defining loader.
*/
private final ClassLoader loader;
/**
* A map of {url<String>, attrs<Manifest>} pairs for caching
* attributes of bootsrap jars.
*/
private static SoftReference<Map<String, Manifest>> jarCache;
/**
* An url of a source jar, for deffered attributes initialization.
* After the initialization, if any, is reset to null.
*/
private String jar;
private String implTitle;
private String implVendor;
private String implVersion;
private final String name;
private URL sealBase;
private String specTitle;
private String specVendor;
private String specVersion;
/**
* Name must not be null.
*/
Package(ClassLoader ld, String packageName, String sTitle, String sVersion, String sVendor,
String iTitle, String iVersion, String iVendor, URL base) {
loader = ld;
name = packageName.toString();
specTitle = sTitle;
specVersion = sVersion;
specVendor = sVendor;
implTitle = iTitle;
implVersion = iVersion;
implVendor = iVendor;
sealBase = base;
}
/**
* Prevent this class from being instantiated
*/
private Package(){
loader = null;
name = null;
}
/**
* Gets the annotation associated with the given annotation type and this
* package.
*
* @return An instance of {@link Annotation} or <code>null</code>.
* @since 1.5
* @see java.lang.reflect.AnnotatedElement#getAnnotation(java.lang.Class)
*/
public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
if(annotationType == null) {
throw new NullPointerException();
}
Annotation aa[] = getAnnotations();
for (int i = 0; i < aa.length; i++) {
if(aa[i].annotationType().equals(annotationType)) {
return (T) aa[i];
}
}
return null;
}
/**
* Gets all of the annotations associated with this package.
*
* @return An array of {@link Annotation} instances, which may be empty.
* @since 1.5
* @see java.lang.reflect.AnnotatedElement#getAnnotations()
*/
public Annotation[] getAnnotations() {
return getDeclaredAnnotations();
}
/**
* Gets all of the annotations directly declared on this element.
*
* @return An array of {@link Annotation} instances, which may be empty.
* @since 1.5
* @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotations()
*/
public Annotation[] getDeclaredAnnotations() {
Class pkgInfo;
try
{
pkgInfo = Class.forName(name + ".package-info", false, loader);
}
catch (ClassNotFoundException _)
{
return new Annotation[0];
}
return pkgInfo.getDeclaredAnnotations();
}
/**
* Indicates whether or not the given annotation is present.
*
* @return A value of <code>true</code> if the annotation is present,
* otherwise <code>false</code>.
* @since 1.5
* @see java.lang.reflect.AnnotatedElement#isAnnotationPresent(java.lang.Class)
*/
public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
return getAnnotation(annotationType) != null;
}
/**
* Return the title of the implementation of this package, or null if this
* is unknown. The format of this string is unspecified.
*
* @return The implementation title, or null
*/
public String getImplementationTitle() {
if (jar != null) {
init();
}
return implTitle;
}
/**
* Return the name of the vendor or organization that provided this
* implementation of the package, or null if this is unknown. The format of
* this string is unspecified.
*
* @return The implementation vendor name, or null
*/
public String getImplementationVendor() {
if (jar != null) {
init();
}
return implVendor;
}
/**
* Return the version of the implementation of this package, or null if this
* is unknown. The format of this string is unspecified.
*
* @return The implementation version, or null
*/
public String getImplementationVersion() {
if (jar != null) {
init();
}
return implVersion;
}
/**
* Return the name of this package in the standard dot notation; for
* example: "java.lang".
*
* @return The name of this package
*/
public String getName() {
return name;
}
/**
* Attempt to locate the requested package in the caller's class loader. If
* no package information can be located, null is returned.
*
* @param packageName The name of the package to find
* @return The package requested, or null
*
* @see ClassLoader#getPackage
*/
public static Package getPackage(String packageName) {
ClassLoader callerLoader = RVMClass.getClassLoaderFromStackFrame(1);
return callerLoader == null ?
BootstrapClassLoader.getBootstrapClassLoader().getPackage(packageName) :
callerLoader.getPackage(packageName);
}
/**
* Return all the packages known to the caller's class loader.
*
* @return All the packages known to the caller's class loader
*
* @see ClassLoader#getPackages
*/
public static Package[] getPackages() {
ClassLoader callerLoader = RVMClass.getClassLoaderFromStackFrame(1);
if (callerLoader == null) {
return BootstrapClassLoader.getBootstrapClassLoader().getPackages();
}
return callerLoader.getPackages();
}
/**
* Return the title of the specification this package implements, or null if
* this is unknown.
*
* @return The specification title, or null
*/
public String getSpecificationTitle() {
if (jar != null) {
init();
}
return specTitle;
}
/**
* Return the name of the vendor or organization that owns and maintains the
* specification this package implements, or null if this is unknown.
*
* @return The specification vendor name, or null
*/
public String getSpecificationVendor() {
if (jar != null) {
init();
}
return specVendor;
}
/**
* Return the version of the specification this package implements, or null
* if this is unknown. The version string is a sequence of non-negative
* integers separated by dots; for example: "1.2.3".
*
* @return The specification version string, or null
*/
public String getSpecificationVersion() {
if (jar != null) {
init();
}
return specVersion;
}
/**
* Answers an integer hash code for the receiver. Any two objects which
* answer <code>true</code> when passed to <code>equals</code> must
* answer the same value for this method.
*
* @return the receiver's hash
*/
@Override
public int hashCode() {
return name.hashCode();
}
/**
* Return true if this package's specification version is compatible with
* the specified version string. Version strings are compared by comparing
* each dot separated part of the version as an integer.
*
* @param desiredVersion The version string to compare against
* @return true if the package versions are compatible, false otherwise
*
* @throws NumberFormatException if the package's version string or the one
* provided is not in the correct format
*/
public boolean isCompatibleWith(String desiredVersion) throws NumberFormatException {
if (jar != null) {
init();
}
if (specVersion == null || specVersion.length() == 0) {
throw new NumberFormatException(
"No specification version defined for the package");
}
if (!specVersion.matches("[\\p{javaDigit}]+(.[\\p{javaDigit}]+)*")) {
throw new NumberFormatException(
"Package specification version is not of the correct dotted form : "
+ specVersion);
}
if (desiredVersion == null || desiredVersion.length() == 0) {
throw new NumberFormatException("Empty version to check");
}
if (!desiredVersion.matches("[\\p{javaDigit}]+(.[\\p{javaDigit}]+)*")) {
throw new NumberFormatException(
"Desired version is not of the correct dotted form : "
+ desiredVersion);
}
StringTokenizer specVersionTokens = new StringTokenizer(specVersion,
".");
StringTokenizer desiredVersionTokens = new StringTokenizer(
desiredVersion, ".");
try {
while (specVersionTokens.hasMoreElements()) {
int desiredVer = Integer.parseInt(desiredVersionTokens
.nextToken());
int specVer = Integer.parseInt(specVersionTokens.nextToken());
if (specVer != desiredVer) {
return specVer > desiredVer;
}
}
} catch (NoSuchElementException e) {
/*
* run out of tokens for desiredVersion
*/
}
/*
* now, if desired is longer than spec, and they have been
* equal so far (ex. 1.4 <-> 1.4.0.0) then the remainder
* better be zeros
*/
while (desiredVersionTokens.hasMoreTokens()) {
if (0 != Integer.parseInt(desiredVersionTokens.nextToken())) {
return false;
}
}
return true;
}
/**
* Return true if this package is sealed, false otherwise.
*
* @return true if this package is sealed, false otherwise
*/
public boolean isSealed() {
if (jar != null) {
init();
}
return sealBase != null;
}
/**
* Return true if this package is sealed with respect to the specified URL,
* false otherwise.
*
* @param url the URL to test
* @return true if this package is sealed, false otherwise
*/
public boolean isSealed(URL url) {
if (jar != null) {
init();
}
return url.equals(sealBase);
}
/**
* Answers a string containing a concise, human-readable description of the
* receiver.
*
* @return a printable representation for the receiver.
*/
@Override
public String toString() {
if (jar != null) {
init();
}
return "package " + name + (specTitle != null ? " " + specTitle : "")
+ (specVersion != null ? " " + specVersion : "");
}
/**
* Performs initialization of optional attributes, if the source jar location
* was specified in the lazy constructor.
*/
private void init() {
try {
Map<String, Manifest> map = null;
Manifest manifest = null;
URL sealURL = null;
if (jarCache != null && (map = jarCache.get()) != null) {
manifest = map.get(jar);
}
if (manifest == null) {
final URL url = sealURL = new URL(jar);
manifest = AccessController.doPrivileged(
new PrivilegedAction<Manifest>() {
public Manifest run()
{
try {
return ((JarURLConnection)url
.openConnection()).getManifest();
} catch (Exception e) {
return new Manifest();
}
}
});
if (map == null) {
map = new Hashtable<String, Manifest>();
if (jarCache == null) {
jarCache = new SoftReference<Map<String, Manifest>>(map);
}
}
map.put(jar, manifest);
}
Attributes mainAttrs = manifest.getMainAttributes();
Attributes pkgAttrs = manifest.getAttributes(name.replace('.','/')+"/");
specTitle = pkgAttrs == null || (specTitle = pkgAttrs
.getValue(Attributes.Name.SPECIFICATION_TITLE)) == null
? mainAttrs.getValue(Attributes.Name.SPECIFICATION_TITLE)
: specTitle;
specVersion = pkgAttrs == null || (specVersion = pkgAttrs
.getValue(Attributes.Name.SPECIFICATION_VERSION)) == null
? mainAttrs.getValue(Attributes.Name.SPECIFICATION_VERSION)
: specVersion;
specVendor = pkgAttrs == null || (specVendor = pkgAttrs
.getValue(Attributes.Name.SPECIFICATION_VENDOR)) == null
? mainAttrs.getValue(Attributes.Name.SPECIFICATION_VENDOR)
: specVendor;
implTitle = pkgAttrs == null || (implTitle = pkgAttrs
.getValue(Attributes.Name.IMPLEMENTATION_TITLE)) == null
? mainAttrs.getValue(Attributes.Name.IMPLEMENTATION_TITLE)
: implTitle;
implVersion = pkgAttrs == null || (implVersion = pkgAttrs
.getValue(Attributes.Name.IMPLEMENTATION_VERSION)) == null
? mainAttrs
.getValue(Attributes.Name.IMPLEMENTATION_VERSION)
: implVersion;
implVendor = pkgAttrs == null || (implVendor = pkgAttrs
.getValue(Attributes.Name.IMPLEMENTATION_VENDOR)) == null
? mainAttrs.getValue(Attributes.Name.IMPLEMENTATION_VENDOR)
: implVendor;
String sealed = pkgAttrs == null || (sealed = pkgAttrs
.getValue(Attributes.Name.SEALED)) == null ? mainAttrs
.getValue(Attributes.Name.SEALED) : sealed;
if (Boolean.valueOf(sealed).booleanValue()) {
sealBase = sealURL != null ? sealURL : new URL(jar);
}
} catch (Exception e) {}
jar = null;
}
}