diff options
Diffstat (limited to 'libdvm')
28 files changed, 10546 insertions, 0 deletions
diff --git a/libdvm/src/main/java/dalvik/system/BaseDexClassLoader.java b/libdvm/src/main/java/dalvik/system/BaseDexClassLoader.java new file mode 100644 index 0000000..d3ec95a --- /dev/null +++ b/libdvm/src/main/java/dalvik/system/BaseDexClassLoader.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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. + */ + +package dalvik.system; + +import java.io.File; +import java.net.URL; +import java.util.Enumeration; + +/** + * Base class for common functionality between various dex-based + * {@link ClassLoader} implementations. + */ +public class BaseDexClassLoader extends ClassLoader { + private final DexPathList pathList; + + /** + * Constructs an instance. + * + * @param dexPath the list of jar/apk files containing classes and + * resources, delimited by {@code File.pathSeparator}, which + * defaults to {@code ":"} on Android + * @param optimizedDirectory directory where optimized dex files + * should be written; may be {@code null} + * @param libraryPath the list of directories containing native + * libraries, delimited by {@code File.pathSeparator}; may be + * {@code null} + * @param parent the parent class loader + */ + public BaseDexClassLoader(String dexPath, File optimizedDirectory, + String libraryPath, ClassLoader parent) { + super(parent); + this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); + } + + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + Class c = pathList.findClass(name); + if (c == null) { + throw new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); + } + return c; + } + + @Override + protected URL findResource(String name) { + return pathList.findResource(name); + } + + @Override + protected Enumeration<URL> findResources(String name) { + return pathList.findResources(name); + } + + @Override + public String findLibrary(String name) { + return pathList.findLibrary(name); + } + + /** + * Returns package information for the given package. + * Unfortunately, instances of this class don't really have this + * information, and as a non-secure {@code ClassLoader}, it isn't + * even required to, according to the spec. Yet, we want to + * provide it, in order to make all those hopeful callers of + * {@code myClass.getPackage().getName()} happy. Thus we construct + * a {@code Package} object the first time it is being requested + * and fill most of the fields with dummy values. The {@code + * Package} object is then put into the {@code ClassLoader}'s + * package cache, so we see the same one next time. We don't + * create {@code Package} objects for {@code null} arguments or + * for the default package. + * + * <p>There is a limited chance that we end up with multiple + * {@code Package} objects representing the same package: It can + * happen when when a package is scattered across different JAR + * files which were loaded by different {@code ClassLoader} + * instances. This is rather unlikely, and given that this whole + * thing is more or less a workaround, probably not worth the + * effort to address. + * + * @param name the name of the class + * @return the package information for the class, or {@code null} + * if there is no package information available for it + */ + @Override + protected synchronized Package getPackage(String name) { + if (name != null && !name.isEmpty()) { + Package pack = super.getPackage(name); + + if (pack == null) { + pack = definePackage(name, "Unknown", "0.0", "Unknown", + "Unknown", "0.0", "Unknown", null); + } + + return pack; + } + + return null; + } + + /** + * @hide + */ + public String getLdLibraryPath() { + StringBuilder result = new StringBuilder(); + for (File directory : pathList.getNativeLibraryDirectories()) { + if (result.length() > 0) { + result.append(':'); + } + result.append(directory); + } + return result.toString(); + } + + @Override public String toString() { + return getClass().getName() + "[" + pathList + "]"; + } +} diff --git a/libdvm/src/main/java/dalvik/system/DexFile.java b/libdvm/src/main/java/dalvik/system/DexFile.java new file mode 100644 index 0000000..18b730b --- /dev/null +++ b/libdvm/src/main/java/dalvik/system/DexFile.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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. + */ + +package dalvik.system; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Enumeration; +import libcore.io.ErrnoException; +import libcore.io.Libcore; +import libcore.io.StructStat; + +/** + * Manipulates DEX files. The class is similar in principle to + * {@link java.util.zip.ZipFile}. It is used primarily by class loaders. + * <p> + * Note we don't directly open and read the DEX file here. They're memory-mapped + * read-only by the VM. + */ +public final class DexFile { + private int mCookie; + private final String mFileName; + private final CloseGuard guard = CloseGuard.get(); + + /** + * Opens a DEX file from a given File object. This will usually be a ZIP/JAR + * file with a "classes.dex" inside. + * + * The VM will generate the name of the corresponding file in + * /data/dalvik-cache and open it, possibly creating or updating + * it first if system permissions allow. Don't pass in the name of + * a file in /data/dalvik-cache, as the named file is expected to be + * in its original (pre-dexopt) state. + * + * @param file + * the File object referencing the actual DEX file + * + * @throws IOException + * if an I/O error occurs, such as the file not being found or + * access rights missing for opening it + */ + public DexFile(File file) throws IOException { + this(file.getPath()); + } + + /** + * Opens a DEX file from a given filename. This will usually be a ZIP/JAR + * file with a "classes.dex" inside. + * + * The VM will generate the name of the corresponding file in + * /data/dalvik-cache and open it, possibly creating or updating + * it first if system permissions allow. Don't pass in the name of + * a file in /data/dalvik-cache, as the named file is expected to be + * in its original (pre-dexopt) state. + * + * @param fileName + * the filename of the DEX file + * + * @throws IOException + * if an I/O error occurs, such as the file not being found or + * access rights missing for opening it + */ + public DexFile(String fileName) throws IOException { + mCookie = openDexFile(fileName, null, 0); + mFileName = fileName; + guard.open("close"); + //System.out.println("DEX FILE cookie is " + mCookie); + } + + /** + * Opens a DEX file from a given filename, using a specified file + * to hold the optimized data. + * + * @param sourceName + * Jar or APK file with "classes.dex". + * @param outputName + * File that will hold the optimized form of the DEX data. + * @param flags + * Enable optional features. + */ + private DexFile(String sourceName, String outputName, int flags) throws IOException { + if (outputName != null) { + try { + String parent = new File(outputName).getParent(); + if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) { + throw new IllegalArgumentException("Optimized data directory " + parent + + " is not owned by the current user. Shared storage cannot protect" + + " your application from code injection attacks."); + } + } catch (ErrnoException ignored) { + // assume we'll fail with a more contextual error later + } + } + + mCookie = openDexFile(sourceName, outputName, flags); + mFileName = sourceName; + guard.open("close"); + //System.out.println("DEX FILE cookie is " + mCookie); + } + + /** + * Open a DEX file, specifying the file in which the optimized DEX + * data should be written. If the optimized form exists and appears + * to be current, it will be used; if not, the VM will attempt to + * regenerate it. + * + * This is intended for use by applications that wish to download + * and execute DEX files outside the usual application installation + * mechanism. This function should not be called directly by an + * application; instead, use a class loader such as + * dalvik.system.DexClassLoader. + * + * @param sourcePathName + * Jar or APK file with "classes.dex". (May expand this to include + * "raw DEX" in the future.) + * @param outputPathName + * File that will hold the optimized form of the DEX data. + * @param flags + * Enable optional features. (Currently none defined.) + * @return + * A new or previously-opened DexFile. + * @throws IOException + * If unable to open the source or output file. + */ + static public DexFile loadDex(String sourcePathName, String outputPathName, + int flags) throws IOException { + + /* + * TODO: we may want to cache previously-opened DexFile objects. + * The cache would be synchronized with close(). This would help + * us avoid mapping the same DEX more than once when an app + * decided to open it multiple times. In practice this may not + * be a real issue. + */ + return new DexFile(sourcePathName, outputPathName, flags); + } + + /** + * Gets the name of the (already opened) DEX file. + * + * @return the file name + */ + public String getName() { + return mFileName; + } + + /** + * Closes the DEX file. + * <p> + * This may not be able to release any resources. If classes from this + * DEX file are still resident, the DEX file can't be unmapped. + * + * @throws IOException + * if an I/O error occurs during closing the file, which + * normally should not happen + */ + public void close() throws IOException { + guard.close(); + closeDexFile(mCookie); + mCookie = 0; + } + + /** + * Loads a class. Returns the class on success, or a {@code null} reference + * on failure. + * <p> + * If you are not calling this from a class loader, this is most likely not + * going to do what you want. Use {@link Class#forName(String)} instead. + * <p> + * The method does not throw {@link ClassNotFoundException} if the class + * isn't found because it isn't reasonable to throw exceptions wildly every + * time a class is not found in the first DEX file we look at. + * + * @param name + * the class name, which should look like "java/lang/String" + * + * @param loader + * the class loader that tries to load the class (in most cases + * the caller of the method + * + * @return the {@link Class} object representing the class, or {@code null} + * if the class cannot be loaded + */ + public Class loadClass(String name, ClassLoader loader) { + String slashName = name.replace('.', '/'); + return loadClassBinaryName(slashName, loader); + } + + /** + * See {@link #loadClass(String, ClassLoader)}. + * + * This takes a "binary" class name to better match ClassLoader semantics. + * + * @hide + */ + public Class loadClassBinaryName(String name, ClassLoader loader) { + return defineClass(name, loader, mCookie); + } + + private native static Class defineClass(String name, ClassLoader loader, int cookie); + + /** + * Enumerate the names of the classes in this DEX file. + * + * @return an enumeration of names of classes contained in the DEX file, in + * the usual internal form (like "java/lang/String"). + */ + public Enumeration<String> entries() { + return new DFEnum(this); + } + + /* + * Helper class. + */ + private class DFEnum implements Enumeration<String> { + private int mIndex; + private String[] mNameList; + + DFEnum(DexFile df) { + mIndex = 0; + mNameList = getClassNameList(mCookie); + } + + public boolean hasMoreElements() { + return (mIndex < mNameList.length); + } + + public String nextElement() { + return mNameList[mIndex++]; + } + } + + /* return a String array with class names */ + native private static String[] getClassNameList(int cookie); + + /** + * Called when the class is finalized. Makes sure the DEX file is closed. + * + * @throws IOException + * if an I/O error occurs during closing the file, which + * normally should not happen + */ + @Override protected void finalize() throws Throwable { + try { + if (guard != null) { + guard.warnIfOpen(); + } + close(); + } finally { + super.finalize(); + } + } + + /* + * Open a DEX file. The value returned is a magic VM cookie. On + * failure, an IOException is thrown. + */ + native private static int openDexFile(String sourceName, String outputName, + int flags) throws IOException; + + /* + * Close DEX file. + */ + native private static void closeDexFile(int cookie); + + /** + * Returns true if the VM believes that the apk/jar file is out of date + * and should be passed through "dexopt" again. + * + * @param fileName the absolute path to the apk/jar file to examine. + * @return true if dexopt should be called on the file, false otherwise. + * @throws java.io.FileNotFoundException if fileName is not readable, + * not a file, or not present. + * @throws java.io.IOException if fileName is not a valid apk/jar file or + * if problems occur while parsing it. + * @throws java.lang.NullPointerException if fileName is null. + * @throws dalvik.system.StaleDexCacheError if the optimized dex file + * is stale but exists on a read-only partition. + */ + native public static boolean isDexOptNeeded(String fileName) + throws FileNotFoundException, IOException; +} diff --git a/libdvm/src/main/java/dalvik/system/DexPathList.java b/libdvm/src/main/java/dalvik/system/DexPathList.java new file mode 100644 index 0000000..3d9ee3e --- /dev/null +++ b/libdvm/src/main/java/dalvik/system/DexPathList.java @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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. + */ + +package dalvik.system; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.regex.Pattern; +import java.util.zip.ZipFile; +import libcore.io.ErrnoException; +import libcore.io.IoUtils; +import libcore.io.Libcore; +import libcore.io.StructStat; +import static libcore.io.OsConstants.*; + +/** + * A pair of lists of entries, associated with a {@code ClassLoader}. + * One of the lists is a dex/resource path — typically referred + * to as a "class path" — list, and the other names directories + * containing native code libraries. Class path entries may be any of: + * a {@code .jar} or {@code .zip} file containing an optional + * top-level {@code classes.dex} file as well as arbitrary resources, + * or a plain {@code .dex} file (with no possibility of associated + * resources). + * + * <p>This class also contains methods to use these lists to look up + * classes and resources.</p> + */ +/*package*/ final class DexPathList { + private static final String DEX_SUFFIX = ".dex"; + private static final String JAR_SUFFIX = ".jar"; + private static final String ZIP_SUFFIX = ".zip"; + private static final String APK_SUFFIX = ".apk"; + + /** class definition context */ + private final ClassLoader definingContext; + + /** + * List of dex/resource (class path) elements. + * Should be called pathElements, but the Facebook app uses reflection + * to modify 'dexElements' (http://b/7726934). + */ + private final Element[] dexElements; + + /** List of native library directories. */ + private final File[] nativeLibraryDirectories; + + /** + * Constructs an instance. + * + * @param definingContext the context in which any as-yet unresolved + * classes should be defined + * @param dexPath list of dex/resource path elements, separated by + * {@code File.pathSeparator} + * @param libraryPath list of native library directory path elements, + * separated by {@code File.pathSeparator} + * @param optimizedDirectory directory where optimized {@code .dex} files + * should be found and written to, or {@code null} to use the default + * system directory for same + */ + public DexPathList(ClassLoader definingContext, String dexPath, + String libraryPath, File optimizedDirectory) { + if (definingContext == null) { + throw new NullPointerException("definingContext == null"); + } + + if (dexPath == null) { + throw new NullPointerException("dexPath == null"); + } + + if (optimizedDirectory != null) { + if (!optimizedDirectory.exists()) { + throw new IllegalArgumentException( + "optimizedDirectory doesn't exist: " + + optimizedDirectory); + } + + if (!(optimizedDirectory.canRead() + && optimizedDirectory.canWrite())) { + throw new IllegalArgumentException( + "optimizedDirectory not readable/writable: " + + optimizedDirectory); + } + } + + this.definingContext = definingContext; + this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory); + this.nativeLibraryDirectories = splitLibraryPath(libraryPath); + } + + @Override public String toString() { + return "DexPathList[" + Arrays.toString(dexElements) + + ",nativeLibraryDirectories=" + Arrays.toString(nativeLibraryDirectories) + "]"; + } + + /** + * For BaseDexClassLoader.getLdLibraryPath. + */ + public File[] getNativeLibraryDirectories() { + return nativeLibraryDirectories; + } + + /** + * Splits the given dex path string into elements using the path + * separator, pruning out any elements that do not refer to existing + * and readable files. (That is, directories are not included in the + * result.) + */ + private static ArrayList<File> splitDexPath(String path) { + return splitPaths(path, null, false); + } + + /** + * Splits the given library directory path string into elements + * using the path separator ({@code File.pathSeparator}, which + * defaults to {@code ":"} on Android, appending on the elements + * from the system library path, and pruning out any elements that + * do not refer to existing and readable directories. + */ + private static File[] splitLibraryPath(String path) { + /* + * Native libraries may exist in both the system and + * application library paths, and we use this search order: + * + * 1. this class loader's library path for application + * libraries + * 2. the VM's library path from the system + * property for system libraries + * + * This order was reversed prior to Gingerbread; see http://b/2933456. + */ + ArrayList<File> result = splitPaths( + path, System.getProperty("java.library.path", "."), true); + return result.toArray(new File[result.size()]); + } + + /** + * Splits the given path strings into file elements using the path + * separator, combining the results and filtering out elements + * that don't exist, aren't readable, or aren't either a regular + * file or a directory (as specified). Either string may be empty + * or {@code null}, in which case it is ignored. If both strings + * are empty or {@code null}, or all elements get pruned out, then + * this returns a zero-element list. + */ + private static ArrayList<File> splitPaths(String path1, String path2, + boolean wantDirectories) { + ArrayList<File> result = new ArrayList<File>(); + + splitAndAdd(path1, wantDirectories, result); + splitAndAdd(path2, wantDirectories, result); + return result; + } + + /** + * Helper for {@link #splitPaths}, which does the actual splitting + * and filtering and adding to a result. + */ + private static void splitAndAdd(String searchPath, boolean directoriesOnly, + ArrayList<File> resultList) { + if (searchPath == null) { + return; + } + for (String path : searchPath.split(":")) { + try { + StructStat sb = Libcore.os.stat(path); + if (!directoriesOnly || S_ISDIR(sb.st_mode)) { + resultList.add(new File(path)); + } + } catch (ErrnoException ignored) { + } + } + } + + /** + * Makes an array of dex/resource path elements, one per element of + * the given array. + */ + private static Element[] makeDexElements(ArrayList<File> files, + File optimizedDirectory) { + ArrayList<Element> elements = new ArrayList<Element>(); + + /* + * Open all files and load the (direct or contained) dex files + * up front. + */ + for (File file : files) { + File zip = null; + DexFile dex = null; + String name = file.getName(); + + if (name.endsWith(DEX_SUFFIX)) { + // Raw dex file (not inside a zip/jar). + try { + dex = loadDexFile(file, optimizedDirectory); + } catch (IOException ex) { + System.logE("Unable to load dex file: " + file, ex); + } + } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX) + || name.endsWith(ZIP_SUFFIX)) { + zip = file; + + try { + dex = loadDexFile(file, optimizedDirectory); + } catch (IOException ignored) { + /* + * IOException might get thrown "legitimately" by + * the DexFile constructor if the zip file turns + * out to be resource-only (that is, no + * classes.dex file in it). Safe to just ignore + * the exception here, and let dex == null. + */ + } + } else if (file.isDirectory()) { + // We support directories for looking up resources. + // This is only useful for running libcore tests. + elements.add(new Element(file, true, null, null)); + } else { + System.logW("Unknown file type for: " + file); + } + + if ((zip != null) || (dex != null)) { + elements.add(new Element(file, false, zip, dex)); + } + } + + return elements.toArray(new Element[elements.size()]); + } + + /** + * Constructs a {@code DexFile} instance, as appropriate depending + * on whether {@code optimizedDirectory} is {@code null}. + */ + private static DexFile loadDexFile(File file, File optimizedDirectory) + throws IOException { + if (optimizedDirectory == null) { + return new DexFile(file); + } else { + String optimizedPath = optimizedPathFor(file, optimizedDirectory); + return DexFile.loadDex(file.getPath(), optimizedPath, 0); + } + } + + /** + * Converts a dex/jar file path and an output directory to an + * output file path for an associated optimized dex file. + */ + private static String optimizedPathFor(File path, + File optimizedDirectory) { + /* + * Get the filename component of the path, and replace the + * suffix with ".dex" if that's not already the suffix. + * + * We don't want to use ".odex", because the build system uses + * that for files that are paired with resource-only jar + * files. If the VM can assume that there's no classes.dex in + * the matching jar, it doesn't need to open the jar to check + * for updated dependencies, providing a slight performance + * boost at startup. The use of ".dex" here matches the use on + * files in /data/dalvik-cache. + */ + String fileName = path.getName(); + if (!fileName.endsWith(DEX_SUFFIX)) { + int lastDot = fileName.lastIndexOf("."); + if (lastDot < 0) { + fileName += DEX_SUFFIX; + } else { + StringBuilder sb = new StringBuilder(lastDot + 4); + sb.append(fileName, 0, lastDot); + sb.append(DEX_SUFFIX); + fileName = sb.toString(); + } + } + + File result = new File(optimizedDirectory, fileName); + return result.getPath(); + } + + /** + * Finds the named class in one of the dex files pointed at by + * this instance. This will find the one in the earliest listed + * path element. If the class is found but has not yet been + * defined, then this method will define it in the defining + * context that this instance was constructed with. + * + * @return the named class or {@code null} if the class is not + * found in any of the dex files + */ + public Class findClass(String name) { + for (Element element : dexElements) { + DexFile dex = element.dexFile; + + if (dex != null) { + Class clazz = dex.loadClassBinaryName(name, definingContext); + if (clazz != null) { + return clazz; + } + } + } + + return null; + } + + /** + * Finds the named resource in one of the zip/jar files pointed at + * by this instance. This will find the one in the earliest listed + * path element. + * + * @return a URL to the named resource or {@code null} if the + * resource is not found in any of the zip/jar files + */ + public URL findResource(String name) { + for (Element element : dexElements) { + URL url = element.findResource(name); + if (url != null) { + return url; + } + } + + return null; + } + + /** + * Finds all the resources with the given name, returning an + * enumeration of them. If there are no resources with the given + * name, then this method returns an empty enumeration. + */ + public Enumeration<URL> findResources(String name) { + ArrayList<URL> result = new ArrayList<URL>(); + + for (Element element : dexElements) { + URL url = element.findResource(name); + if (url != null) { + result.add(url); + } + } + + return Collections.enumeration(result); + } + + /** + * Finds the named native code library on any of the library + * directories pointed at by this instance. This will find the + * one in the earliest listed directory, ignoring any that are not + * readable regular files. + * + * @return the complete path to the library or {@code null} if no + * library was found + */ + public String findLibrary(String libraryName) { + String fileName = System.mapLibraryName(libraryName); + for (File directory : nativeLibraryDirectories) { + String path = new File(directory, fileName).getPath(); + if (IoUtils.canOpenReadOnly(path)) { + return path; + } + } + return null; + } + + /** + * Element of the dex/resource file path + */ + /*package*/ static class Element { + private final File file; + private final boolean isDirectory; + private final File zip; + private final DexFile dexFile; + + private ZipFile zipFile; + private boolean initialized; + + public Element(File file, boolean isDirectory, File zip, DexFile dexFile) { + this.file = file; + this.isDirectory = isDirectory; + this.zip = zip; + this.dexFile = dexFile; + } + + @Override public String toString() { + if (isDirectory) { + return "directory \"" + file + "\""; + } else if (zip != null) { + return "zip file \"" + zip + "\""; + } else { + return "dex file \"" + dexFile + "\""; + } + } + + public synchronized void maybeInit() { + if (initialized) { + return; + } + + initialized = true; + + if (isDirectory || zip == null) { + return; + } + + try { + zipFile = new ZipFile(zip); + } catch (IOException ioe) { + /* + * Note: ZipException (a subclass of IOException) + * might get thrown by the ZipFile constructor + * (e.g. if the file isn't actually a zip/jar + * file). + */ + System.logE("Unable to open zip file: " + file, ioe); + zipFile = null; + } + } + + public URL findResource(String name) { + maybeInit(); + + // We support directories so we can run tests and/or legacy code + // that uses Class.getResource. + if (isDirectory) { + File resourceFile = new File(file, name); + if (resourceFile.exists()) { + try { + return resourceFile.toURI().toURL(); + } catch (MalformedURLException ex) { + throw new RuntimeException(ex); + } + } + } + + if (zipFile == null || zipFile.getEntry(name) == null) { + /* + * Either this element has no zip/jar file (first + * clause), or the zip/jar file doesn't have an entry + * for the given name (second clause). + */ + return null; + } + + try { + /* + * File.toURL() is compliant with RFC 1738 in + * always creating absolute path names. If we + * construct the URL by concatenating strings, we + * might end up with illegal URLs for relative + * names. + */ + return new URL("jar:" + file.toURL() + "!/" + name); + } catch (MalformedURLException ex) { + throw new RuntimeException(ex); + } + } + } +} diff --git a/libdvm/src/main/java/java/lang/Class.java b/libdvm/src/main/java/java/lang/Class.java new file mode 100644 index 0000000..f618b66 --- /dev/null +++ b/libdvm/src/main/java/java/lang/Class.java @@ -0,0 +1,1218 @@ +/* + * 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. + */ +/* + * Copyright (C) 2006-2007 The Android Open Source Project + * + * 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. + */ + +package java.lang; + +import dalvik.system.VMStack; +import java.io.InputStream; +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.net.URL; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import libcore.util.CollectionUtils; +import libcore.util.EmptyArray; +import org.apache.harmony.kernel.vm.StringUtils; +import org.apache.harmony.luni.lang.reflect.GenericSignatureParser; +import org.apache.harmony.luni.lang.reflect.Types; + +/** + * The in-memory representation of a Java class. This representation serves as + * the starting point for querying class-related information, a process usually + * called "reflection". There are basically three types of {@code Class} + * instances: those representing real classes and interfaces, those representing + * primitive types, and those representing array classes. + * + * <h4>Class instances representing object types (classes or interfaces)</h4> + * <p> + * These represent an ordinary class or interface as found in the class + * hierarchy. The name associated with these {@code Class} instances is simply + * the fully qualified class name of the class or interface that it represents. + * In addition to this human-readable name, each class is also associated by a + * so-called <em>signature</em>, which is the letter "L", followed by the + * class name and a semicolon (";"). The signature is what the runtime system + * uses internally for identifying the class (for example in a DEX file). + * </p> + * <h4>Classes representing primitive types</h4> + * <p> + * These represent the standard Java primitive types and hence share their + * names (for example "int" for the {@code int} primitive type). Although it is + * not possible to create new instances based on these {@code Class} instances, + * they are still useful for providing reflection information, and as the + * component type of array classes. There is one {@code Class} instance for each + * primitive type, and their signatures are: + * </p> + * <ul> + * <li>{@code B} representing the {@code byte} primitive type</li> + * <li>{@code S} representing the {@code short} primitive type</li> + * <li>{@code I} representing the {@code int} primitive type</li> + * <li>{@code J} representing the {@code long} primitive type</li> + * <li>{@code F} representing the {@code float} primitive type</li> + * <li>{@code D} representing the {@code double} primitive type</li> + * <li>{@code C} representing the {@code char} primitive type</li> + * <li>{@code Z} representing the {@code boolean} primitive type</li> + * <li>{@code V} representing void function return values</li> + * </ul> + * <p> + * <h4>Classes representing array classes</h4> + * <p> + * These represent the classes of Java arrays. There is one such {@code Class} + * instance per combination of array leaf component type and arity (number of + * dimensions). In this case, the name associated with the {@code Class} + * consists of one or more left square brackets (one per dimension in the array) + * followed by the signature of the class representing the leaf component type, + * which can be either an object type or a primitive type. The signature of a + * {@code Class} representing an array type is the same as its name. Examples + * of array class signatures are: + * </p> + * <ul> + * <li>{@code [I} representing the {@code int[]} type</li> + * <li>{@code [Ljava/lang/String;} representing the {@code String[]} type</li> + * <li>{@code [[[C} representing the {@code char[][][]} type (three dimensions!)</li> + * </ul> + */ +public final class Class<T> implements Serializable, AnnotatedElement, GenericDeclaration, Type { + + private static final long serialVersionUID = 3206093459760846163L; + + /** + * Lazily computed name of this class; always prefer calling getName(). + */ + private transient String name; + + private Class() { + // Prevent this class to be instantiated, instance + // should be created by JVM only + } + + /** + * Get the Signature attribute for this class. Returns null if not found. + */ + private String getSignatureAttribute() { + Object[] annotation = getSignatureAnnotation(); + + if (annotation == null) { + return null; + } + + return StringUtils.combineStrings(annotation); + } + + /** + * Get the Signature annotation for this class. Returns null if not found. + */ + native private Object[] getSignatureAnnotation(); + + /** + * Returns a {@code Class} object which represents the class with the + * given name. The name should be the name of a non-primitive class, as described in + * the {@link Class class definition}. + * Primitive types can not be found using this method; use {@code int.class} or {@code Integer.TYPE} instead. + * + * <p>If the class has not yet been loaded, it is loaded and initialized + * first. This is done through either the class loader of the calling class + * or one of its parent class loaders. It is possible that a static initializer is run as + * a result of this call. + * + * @throws ClassNotFoundException + * if the requested class can not be found. + * @throws LinkageError + * if an error occurs during linkage + * @throws ExceptionInInitializerError + * if an exception occurs during static initialization of a + * class. + */ + public static Class<?> forName(String className) throws ClassNotFoundException { + return forName(className, true, VMStack.getCallingClassLoader()); + } + + /** + * Returns a {@code Class} object which represents the class with the + * given name. The name should be the name of a non-primitive class, as described in + * the {@link Class class definition}. + * Primitive types can not be found using this method; use {@code int.class} or {@code Integer.TYPE} instead. + * + * <p>If the class has not yet been loaded, it is loaded first, using the given class loader. + * If the class has not yet been initialized and {@code shouldInitialize} is true, + * the class will be initialized. + * + * @throws ClassNotFoundException + * if the requested class can not be found. + * @throws LinkageError + * if an error occurs during linkage + * @throws ExceptionInInitializerError + * if an exception occurs during static initialization of a + * class. + */ + public static Class<?> forName(String className, boolean shouldInitialize, + ClassLoader classLoader) throws ClassNotFoundException { + + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); + } + // Catch an Exception thrown by the underlying native code. It wraps + // up everything inside a ClassNotFoundException, even if e.g. an + // Error occurred during initialization. This as a workaround for + // an ExceptionInInitializerError that's also wrapped. It is actually + // expected to be thrown. Maybe the same goes for other errors. + // Not wrapping up all the errors will break android though. + Class<?> result; + try { + result = classForName(className, shouldInitialize, + classLoader); + } catch (ClassNotFoundException e) { + Throwable cause = e.getCause(); + if (cause instanceof ExceptionInInitializerError) { + throw (ExceptionInInitializerError) cause; + } + throw e; + } + return result; + } + + private static native Class<?> classForName(String className, boolean shouldInitialize, + ClassLoader classLoader) throws ClassNotFoundException; + + /** + * Returns an array containing {@code Class} objects for all public classes + * and interfaces that are members of this class. This includes public + * members inherited from super classes and interfaces. If there are no such + * class members or if this object represents a primitive type then an array + * of length 0 is returned. + */ + public Class<?>[] getClasses() { + Class<?>[] result = getDeclaredClasses(this, true); + // Traverse all superclasses. + for (Class<?> c = this.getSuperclass(); c != null; c = c.getSuperclass()) { + Class<?>[] temp = getDeclaredClasses(c, true); + if (temp.length != 0) { + result = arraycopy(new Class[result.length + temp.length], result, temp); + } + } + return result; + } + + @Override public <A extends Annotation> A getAnnotation(Class<A> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + + A annotation = getDeclaredAnnotation(annotationType); + if (annotation != null) { + return annotation; + } + + if (annotationType.isAnnotationPresent(Inherited.class)) { + for (Class<?> sup = getSuperclass(); sup != null; sup = sup.getSuperclass()) { + annotation = sup.getDeclaredAnnotation(annotationType); + if (annotation != null) { + return annotation; + } + } + } + + return null; + } + + /** + * Returns an array containing all the annotations of this class. If there are no annotations + * then an empty array is returned. + * + * @see #getDeclaredAnnotations() + */ + public Annotation[] getAnnotations() { + /* + * We need to get the annotations declared on this class, plus the + * annotations from superclasses that have the "@Inherited" annotation + * set. We create a temporary map to use while we accumulate the + * annotations and convert it to an array at the end. + * + * It's possible to have duplicates when annotations are inherited. + * We use a Map to filter those out. + * + * HashMap might be overkill here. + */ + HashMap<Class, Annotation> map = new HashMap<Class, Annotation>(); + Annotation[] declaredAnnotations = getDeclaredAnnotations(); + + for (int i = declaredAnnotations.length-1; i >= 0; --i) { + map.put(declaredAnnotations[i].annotationType(), declaredAnnotations[i]); + } + for (Class<?> sup = getSuperclass(); sup != null; sup = sup.getSuperclass()) { + declaredAnnotations = sup.getDeclaredAnnotations(); + for (int i = declaredAnnotations.length-1; i >= 0; --i) { + Class<?> clazz = declaredAnnotations[i].annotationType(); + if (!map.containsKey(clazz) && clazz.isAnnotationPresent(Inherited.class)) { + map.put(clazz, declaredAnnotations[i]); + } + } + } + + /* convert annotation values from HashMap to array */ + Collection<Annotation> coll = map.values(); + return coll.toArray(new Annotation[coll.size()]); + } + + /** + * Returns the canonical name of this class. If this class does not have a + * canonical name as defined in the Java Language Specification, then the + * method returns {@code null}. + */ + public String getCanonicalName() { + if (isLocalClass() || isAnonymousClass()) + return null; + + if (isArray()) { + /* + * The canonical name of an array type depends on the (existence of) + * the component type's canonical name. + */ + String name = getComponentType().getCanonicalName(); + if (name != null) { + return name + "[]"; + } + } else if (isMemberClass()) { + /* + * The canonical name of an inner class depends on the (existence + * of) the declaring class' canonical name. + */ + String name = getDeclaringClass().getCanonicalName(); + if (name != null) { + return name + "." + getSimpleName(); + } + } else { + /* + * The canonical name of a top-level class or primitive type is + * equal to the fully qualified name. + */ + return getName(); + } + + /* + * Other classes don't have a canonical name. + */ + return null; + } + + /** + * Returns the class loader which was used to load the class represented by + * this {@code Class}. Implementations are free to return {@code null} for + * classes that were loaded by the bootstrap class loader. The Android + * reference implementation, though, always returns a reference to an actual + * class loader. + */ + public ClassLoader getClassLoader() { + if (this.isPrimitive()) { + return null; + } + + ClassLoader loader = getClassLoaderImpl(); + if (loader == null) { + loader = BootClassLoader.getInstance(); + } + return loader; + } + + /** + * This must be provided by the VM vendor, as it is used by other provided + * class implementations in this package. Outside of this class, it is used + * by SecurityManager.classLoaderDepth(), + * currentClassLoader() and currentLoadedClass(). Return the ClassLoader for + * this Class without doing any security checks. The bootstrap ClassLoader + * is returned, unlike getClassLoader() which returns null in place of the + * bootstrap ClassLoader. + */ + ClassLoader getClassLoaderImpl() { + ClassLoader loader = getClassLoader(this); + return loader == null ? BootClassLoader.getInstance() : loader; + } + + /* + * Returns the defining class loader for the given class. + */ + private static native ClassLoader getClassLoader(Class<?> c); + + /** + * Returns a {@code Class} object which represents the component type if + * this class represents an array type. Returns {@code null} if this class + * does not represent an array type. The component type of an array type is + * the type of the elements of the array. + */ + public native Class<?> getComponentType(); + + /** + * Returns a {@code Constructor} object which represents the public + * constructor matching the given parameter types. + * {@code (Class[]) null} is equivalent to the empty array. + * + * @throws NoSuchMethodException + * if the constructor can not be found. + * @see #getDeclaredConstructor(Class[]) + */ + @SuppressWarnings("unchecked") + public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException { + return (Constructor) getConstructorOrMethod("<init>", false, true, parameterTypes); + } + + /** + * Returns a constructor or method with the given name. Use "<init>" to return a constructor. + */ + private Member getConstructorOrMethod(String name, boolean searchSuperTypes, + boolean publicOnly, Class<?>[] parameterTypes) throws NoSuchMethodException { + if (searchSuperTypes && !publicOnly) { + throw new AssertionError(); // can't lookup non-public members recursively + } + if (name == null) { + throw new NullPointerException("name == null"); + } + if (parameterTypes == null) { + parameterTypes = EmptyArray.CLASS; + } + for (Class<?> c : parameterTypes) { + if (c == null) { + throw new NoSuchMethodException("parameter type is null"); + } + } + Member result = searchSuperTypes + ? getPublicConstructorOrMethodRecursive(name, parameterTypes) + : Class.getDeclaredConstructorOrMethod(this, name, parameterTypes); + if (result == null || publicOnly && (result.getModifiers() & Modifier.PUBLIC) == 0) { + throw new NoSuchMethodException(name + " " + Arrays.toString(parameterTypes)); + } + return result; + } + + private Member getPublicConstructorOrMethodRecursive(String name, Class<?>[] parameterTypes) { + // search superclasses + for (Class<?> c = this; c != null; c = c.getSuperclass()) { + Member result = Class.getDeclaredConstructorOrMethod(c, name, parameterTypes); + if (result != null && (result.getModifiers() & Modifier.PUBLIC) != 0) { + return result; + } + } + + // search implemented interfaces + for (Class<?> c = this; c != null; c = c.getSuperclass()) { + for (Class<?> ifc : c.getInterfaces()) { + Member result = ifc.getPublicConstructorOrMethodRecursive(name, parameterTypes); + if (result != null && (result.getModifiers() & Modifier.PUBLIC) != 0) { + return result; + } + } + } + + return null; + } + + /** + * Returns an array containing {@code Constructor} objects for all public + * constructors for this {@code Class}. If there + * are no public constructors or if this {@code Class} represents an array + * class, a primitive type or void then an empty array is returned. + * + * @see #getDeclaredConstructors() + */ + public Constructor<?>[] getConstructors() { + return getDeclaredConstructors(this, true); + } + + /** + * Returns the annotations that are directly defined on the class + * represented by this {@code Class}. Annotations that are inherited are not + * included in the result. If there are no annotations at all, an empty + * array is returned. + * + * @see #getAnnotations() + */ + public native Annotation[] getDeclaredAnnotations(); + + /** + * Returns the annotation if it exists. + */ + native private <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass); + + /** + * Returns true if the annotation exists. + */ + native private boolean isDeclaredAnnotationPresent(Class<? extends Annotation> annotationClass); + + /** + * Returns an array containing {@code Class} objects for all classes and + * interfaces that are declared as members of the class which this {@code + * Class} represents. If there are no classes or interfaces declared or if + * this class represents an array class, a primitive type or void, then an + * empty array is returned. + */ + public Class<?>[] getDeclaredClasses() { + return getDeclaredClasses(this, false); + } + + /* + * Returns the list of member classes of the given class. + * If no members exist, an empty array is returned. + */ + private static native Class<?>[] getDeclaredClasses(Class<?> c, boolean publicOnly); + + /** + * Returns a {@code Constructor} object which represents the constructor + * matching the given parameter types that is declared by the class + * represented by this {@code Class}. + * {@code (Class[]) null} is equivalent to the empty array. + * + * @throws NoSuchMethodException + * if the requested constructor can not be found. + * @see #getConstructor(Class[]) + */ + @SuppressWarnings("unchecked") + public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) + throws NoSuchMethodException { + return (Constructor) getConstructorOrMethod("<init>", false, false, parameterTypes); + } + + /** + * Returns an array containing {@code Constructor} objects for all + * constructors declared in the class represented by this {@code Class}. If + * there are no constructors or if this {@code Class} represents an array + * class, a primitive type, or void then an empty array is returned. + * + * @see #getConstructors() + */ + public Constructor<?>[] getDeclaredConstructors() { + return getDeclaredConstructors(this, false); + } + + /* + * Returns the list of constructors. If no constructors exist, an empty array is returned. + */ + private static native <T> Constructor<T>[] getDeclaredConstructors(Class<T> c, + boolean publicOnly); + + /** + * Returns a {@code Field} object for the field with the given name + * which is declared in the class represented by this {@code Class}. + * + * @throws NoSuchFieldException if the requested field can not be found. + * @see #getField(String) + */ + public Field getDeclaredField(String name) throws NoSuchFieldException { + if (name == null) { + throw new NullPointerException("name == null"); + } + Field result = getDeclaredField(this, name); + if (result == null) { + throw new NoSuchFieldException(name); + } + return result; + } + + /** + * Returns an array containing {@code Field} objects for all fields declared + * in the class represented by this {@code Class}. If there are no fields or + * if this {@code Class} represents an array class, a primitive type or void + * then an empty array is returned. + * + * @see #getFields() + */ + public Field[] getDeclaredFields() { + return getDeclaredFields(this, false); + } + + /* + * Returns the list of fields without performing any security checks + * first. If no fields exist at all, an empty array is returned. + */ + static native Field[] getDeclaredFields(Class<?> c, boolean publicOnly); + + /** + * Returns the field if it is defined by {@code c}; null otherwise. This + * may return a non-public member. + */ + static native Field getDeclaredField(Class<?> c, String name); + + /** + * Returns a {@code Method} object which represents the method matching the + * given name and parameter types that is declared by the class + * represented by this {@code Class}. + * {@code (Class[]) null} is equivalent to the empty array. + * + * @throws NoSuchMethodException + * if the requested method can not be found. + * @throws NullPointerException + * if {@code name} is {@code null}. + * @see #getMethod(String, Class[]) + */ + public Method getDeclaredMethod(String name, Class<?>... parameterTypes) + throws NoSuchMethodException { + Member member = getConstructorOrMethod(name, false, false, parameterTypes); + if (member instanceof Constructor) { + throw new NoSuchMethodException(name); + } + return (Method) member; + } + + /** + * Returns an array containing {@code Method} objects for all methods + * declared in the class represented by this {@code Class}. If there are no + * methods or if this {@code Class} represents an array class, a primitive + * type or void then an empty array is returned. + * + * @see #getMethods() + */ + public Method[] getDeclaredMethods() { + return getDeclaredMethods(this, false); + } + + /** + * Returns the list of methods. If no methods exist, an empty array is returned. + */ + static native Method[] getDeclaredMethods(Class<?> c, boolean publicOnly); + + /** + * Returns the constructor or method if it is defined by {@code c}; null + * otherwise. This may return a non-public member. Use "<init>" to get a constructor. + */ + static native Member getDeclaredConstructorOrMethod(Class c, String name, Class[] args); + + /** + * Returns the declaring {@code Class} of this {@code Class}. Returns + * {@code null} if the class is not a member of another class or if this + * {@code Class} represents an array class, a primitive type, or void. + */ + public native Class<?> getDeclaringClass(); + + /** + * Returns the enclosing {@code Class} of this {@code Class}. If there is no + * enclosing class the method returns {@code null}. + */ + public native Class<?> getEnclosingClass(); + + /** + * Returns the enclosing {@code Constructor} of this {@code Class}, if it is an + * anonymous or local/automatic class; otherwise {@code null}. + */ + public native Constructor<?> getEnclosingConstructor(); + + /** + * Returns the enclosing {@code Method} of this {@code Class}, if it is an + * anonymous or local/automatic class; otherwise {@code null}. + */ + public native Method getEnclosingMethod(); + + /** + * Returns the {@code enum} constants associated with this {@code Class}. + * Returns {@code null} if this {@code Class} does not represent an {@code + * enum} type. + */ + @SuppressWarnings("unchecked") // we only cast after confirming that this class is an enum + public T[] getEnumConstants() { + if (!isEnum()) { + return null; + } + return (T[]) Enum.getSharedConstants((Class) this).clone(); + } + + /** + * Returns a {@code Field} object which represents the public field with the + * given name. This method first searches the class C represented by + * this {@code Class}, then the interfaces implemented by C and finally the + * superclasses of C. + * + * @throws NoSuchFieldException + * if the field can not be found. + * @see #getDeclaredField(String) + */ + public Field getField(String name) throws NoSuchFieldException { + if (name == null) { + throw new NullPointerException("name == null"); + } + Field result = getPublicFieldRecursive(name); + if (result == null) { + throw new NoSuchFieldException(name); + } + return result; + } + + private Field getPublicFieldRecursive(String name) { + // search superclasses + for (Class<?> c = this; c != null; c = c.getSuperclass()) { + Field result = Class.getDeclaredField(c, name); + if (result != null && (result.getModifiers() & Modifier.PUBLIC) != 0) { + return result; + } + } + + // search implemented interfaces + for (Class<?> c = this; c != null; c = c.getSuperclass()) { + for (Class<?> ifc : c.getInterfaces()) { + Field result = ifc.getPublicFieldRecursive(name); + if (result != null && (result.getModifiers() & Modifier.PUBLIC) != 0) { + return result; + } + } + } + + return null; + } + + /** + * Returns an array containing {@code Field} objects for all public fields + * for the class C represented by this {@code Class}. Fields may be declared + * in C, the interfaces it implements or in the superclasses of C. The + * elements in the returned array are in no particular order. + * + * <p>If there are no public fields or if this class represents an array class, + * a primitive type or {@code void} then an empty array is returned. + * + * @see #getDeclaredFields() + */ + public Field[] getFields() { + List<Field> fields = new ArrayList<Field>(); + getPublicFieldsRecursive(fields); + + /* + * The result may include duplicates when this class implements an interface + * through multiple paths. Remove those duplicates. + */ + CollectionUtils.removeDuplicates(fields, Field.ORDER_BY_NAME_AND_DECLARING_CLASS); + return fields.toArray(new Field[fields.size()]); + } + + /** + * Populates {@code result} with public fields defined by this class, its + * superclasses, and all implemented interfaces. + */ + private void getPublicFieldsRecursive(List<Field> result) { + // search superclasses + for (Class<?> c = this; c != null; c = c.getSuperclass()) { + for (Field field : Class.getDeclaredFields(c, true)) { + result.add(field); + } + } + + // search implemented interfaces + for (Class<?> c = this; c != null; c = c.getSuperclass()) { + for (Class<?> ifc : c.getInterfaces()) { + ifc.getPublicFieldsRecursive(result); + } + } + } + + /** + * Returns the {@link Type}s of the interfaces that this {@code Class} directly + * implements. If the {@code Class} represents a primitive type or {@code + * void} then an empty array is returned. + */ + public Type[] getGenericInterfaces() { + GenericSignatureParser parser = new GenericSignatureParser(getClassLoader()); + parser.parseForClass(this, getSignatureAttribute()); + return Types.getClonedTypeArray(parser.interfaceTypes); + } + + /** + * Returns the {@code Type} that represents the superclass of this {@code + * class}. + */ + public Type getGenericSuperclass() { + GenericSignatureParser parser = new GenericSignatureParser(getClassLoader()); + parser.parseForClass(this, getSignatureAttribute()); + return Types.getType(parser.superclassType); + } + + /** + * Returns an array of {@code Class} objects that match the interfaces + * in the {@code implements} declaration of the class represented + * by this {@code Class}. The order of the elements in the array is + * identical to the order in the original class declaration. If the class + * does not implement any interfaces, an empty array is returned. + */ + public native Class<?>[] getInterfaces(); + + /** + * Returns a {@code Method} object which represents the public method with + * the given name and parameter types. + * {@code (Class[]) null} is equivalent to the empty array. + * This method first searches the + * class C represented by this {@code Class}, then the superclasses of C and + * finally the interfaces implemented by C and finally the superclasses of C + * for a method with matching name. + * + * @throws NoSuchMethodException + * if the method can not be found. + * @see #getDeclaredMethod(String, Class[]) + */ + public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException { + Member member = getConstructorOrMethod(name, true, true, parameterTypes); + if (member instanceof Constructor) { + throw new NoSuchMethodException(name); + } + return (Method) member; + } + + /** + * Returns an array containing {@code Method} objects for all public methods + * for the class C represented by this {@code Class}. Methods may be + * declared in C, the interfaces it implements or in the superclasses of C. + * The elements in the returned array are in no particular order. + * + * <p>If there are no public methods or if this {@code Class} represents a + * primitive type or {@code void} then an empty array is returned. + * + * @see #getDeclaredMethods() + */ + public Method[] getMethods() { + List<Method> methods = new ArrayList<Method>(); + getPublicMethodsRecursive(methods); + + /* + * Remove methods defined by multiple types, preferring to keep methods + * declared by derived types. + */ + CollectionUtils.removeDuplicates(methods, Method.ORDER_BY_SIGNATURE); + return methods.toArray(new Method[methods.size()]); + } + + /** + * Populates {@code result} with public methods defined by this class, its + * superclasses, and all implemented interfaces, including overridden methods. + */ + private void getPublicMethodsRecursive(List<Method> result) { + // search superclasses + for (Class<?> c = this; c != null; c = c.getSuperclass()) { + for (Method method : Class.getDeclaredMethods(c, true)) { + result.add(method); + } + } + + // search implemented interfaces + for (Class<?> c = this; c != null; c = c.getSuperclass()) { + for (Class<?> ifc : c.getInterfaces()) { + ifc.getPublicMethodsRecursive(result); + } + } + } + + /** + * Returns an integer that represents the modifiers of the class represented + * by this {@code Class}. The returned value is a combination of bits + * defined by constants in the {@link Modifier} class. + */ + public int getModifiers() { + return getModifiers(this, false); + } + + /* + * Returns the modifiers for the given class. + * + * {@code ignoreInnerClassesAttrib} determines whether we look for and use the + * flags from an "inner class" attribute + */ + private static native int getModifiers(Class<?> clazz, boolean ignoreInnerClassesAttrib); + + /** + * Returns the name of the class represented by this {@code Class}. For a + * description of the format which is used, see the class definition of + * {@link Class}. + */ + public String getName() { + String result = name; + return (result == null) ? (name = getNameNative()) : result; + } + + private native String getNameNative(); + + /** + * Returns the simple name of the class represented by this {@code Class} as + * defined in the source code. If there is no name (that is, the class is + * anonymous) then an empty string is returned. If the receiver is an array + * then the name of the underlying type with square braces appended (for + * example {@code "Integer[]"}) is returned. + * + * @return the simple name of the class represented by this {@code Class}. + */ + public String getSimpleName() { + if (isArray()) { + return getComponentType().getSimpleName() + "[]"; + } + + String name = getName(); + + if (isAnonymousClass()) { + return ""; + } + + if (isMemberClass() || isLocalClass()) { + return getInnerClassName(); + } + + int dot = name.lastIndexOf('.'); + if (dot != -1) { + return name.substring(dot + 1); + } + + return name; + } + + /* + * Returns the simple name of a member or local class, or null otherwise. + */ + private native String getInnerClassName(); + + /** + * Returns null. + */ + public ProtectionDomain getProtectionDomain() { + return null; + } + + /** + * Returns the URL of the given resource, or null if the resource is not found. + * The mapping between the resource name and the URL is managed by the class' class loader. + * + * @see ClassLoader + */ + public URL getResource(String resourceName) { + // Get absolute resource name, but without the leading slash + if (resourceName.startsWith("/")) { + resourceName = resourceName.substring(1); + } else { + String pkg = getName(); + int dot = pkg.lastIndexOf('.'); + if (dot != -1) { + pkg = pkg.substring(0, dot).replace('.', '/'); + } else { + pkg = ""; + } + + resourceName = pkg + "/" + resourceName; + } + + // Delegate to proper class loader + ClassLoader loader = getClassLoader(); + if (loader != null) { + return loader.getResource(resourceName); + } else { + return ClassLoader.getSystemResource(resourceName); + } + } + + /** + * Returns a read-only stream for the contents of the given resource, or null if the resource + * is not found. + * The mapping between the resource name and the stream is managed by the class' class loader. + * + * @see ClassLoader + */ + public InputStream getResourceAsStream(String resourceName) { + // Get absolute resource name, but without the leading slash + if (resourceName.startsWith("/")) { + resourceName = resourceName.substring(1); + } else { + String pkg = getName(); + int dot = pkg.lastIndexOf('.'); + if (dot != -1) { + pkg = pkg.substring(0, dot).replace('.', '/'); + } else { + pkg = ""; + } + + resourceName = pkg + "/" + resourceName; + } + + // Delegate to proper class loader + ClassLoader loader = getClassLoader(); + if (loader != null) { + return loader.getResourceAsStream(resourceName); + } else { + return ClassLoader.getSystemResourceAsStream(resourceName); + } + } + + /** + * Returns null. (On Android, a {@code ClassLoader} can load classes from multiple dex files. + * All classes from any given dex file will have the same signers, but different dex + * files may have different signers. This does not fit well with the original + * {@code ClassLoader}-based model of {@code getSigners}.) + */ + public Object[] getSigners() { + // See http://code.google.com/p/android/issues/detail?id=1766. + return null; + } + + /** + * Returns the {@code Class} object which represents the superclass of the + * class represented by this {@code Class}. If this {@code Class} represents + * the {@code Object} class, a primitive type, an interface or void then the + * method returns {@code null}. If this {@code Class} represents an array + * class then the {@code Object} class is returned. + */ + public native Class<? super T> getSuperclass(); + + /** + * Returns an array containing {@code TypeVariable} objects for type + * variables declared by the generic class represented by this {@code + * Class}. Returns an empty array if the class is not generic. + */ + @SuppressWarnings("unchecked") + public synchronized TypeVariable<Class<T>>[] getTypeParameters() { + GenericSignatureParser parser = new GenericSignatureParser(getClassLoader()); + parser.parseForClass(this, getSignatureAttribute()); + return parser.formalTypeParameters.clone(); + } + + /** + * Tests whether this {@code Class} represents an annotation class. + */ + public boolean isAnnotation() { + final int ACC_ANNOTATION = 0x2000; // not public in reflect.Modifiers + int mod = getModifiers(this, true); + return (mod & ACC_ANNOTATION) != 0; + } + + @Override public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + + if (isDeclaredAnnotationPresent(annotationType)) { + return true; + } + + if (annotationType.isDeclaredAnnotationPresent(Inherited.class)) { + for (Class<?> sup = getSuperclass(); sup != null; sup = sup.getSuperclass()) { + if (sup.isDeclaredAnnotationPresent(annotationType)) { + return true; + } + } + } + + return false; + } + + /** + * Tests whether the class represented by this {@code Class} is + * anonymous. + */ + native public boolean isAnonymousClass(); + + /** + * Tests whether the class represented by this {@code Class} is an array class. + */ + public boolean isArray() { + return getComponentType() != null; + } + + /** + * Tests whether the given class type can be converted to the class + * represented by this {@code Class}. Conversion may be done via an identity + * conversion or a widening reference conversion (if either the receiver or + * the argument represent primitive types, only the identity conversion + * applies). + * + * @throws NullPointerException + * if {@code c} is {@code null}. + */ + public native boolean isAssignableFrom(Class<?> c); + + /** + * Tests whether the class represented by this {@code Class} is an + * {@code enum}. + */ + public boolean isEnum() { + return ((getModifiers() & 0x4000) != 0) && (getSuperclass() == Enum.class); + } + + /** + * Tests whether the given object can be cast to the class + * represented by this {@code Class}. This is the runtime version of the + * {@code instanceof} operator. + * + * @return {@code true} if {@code object} can be cast to the type + * represented by this {@code Class}; {@code false} if {@code + * object} is {@code null} or cannot be cast. + */ + public native boolean isInstance(Object object); + + /** + * Tests whether this {@code Class} represents an interface. + */ + public native boolean isInterface(); + + /** + * Tests whether the class represented by this {@code Class} is defined + * locally. + */ + public boolean isLocalClass() { + boolean enclosed = (getEnclosingMethod() != null || + getEnclosingConstructor() != null); + return enclosed && !isAnonymousClass(); + } + + /** + * Tests whether the class represented by this {@code Class} is a member + * class. + */ + public boolean isMemberClass() { + return getDeclaringClass() != null; + } + + /** + * Tests whether this {@code Class} represents a primitive type. + */ + public native boolean isPrimitive(); + + /** + * Tests whether this {@code Class} represents a synthetic type. + */ + public boolean isSynthetic() { + final int ACC_SYNTHETIC = 0x1000; // not public in reflect.Modifiers + int mod = getModifiers(this, true); + return (mod & ACC_SYNTHETIC) != 0; + } + + /** + * Returns a new instance of the class represented by this {@code Class}, + * created by invoking the default (that is, zero-argument) constructor. If + * there is no such constructor, or if the creation fails (either because of + * a lack of available memory or because an exception is thrown by the + * constructor), an {@code InstantiationException} is thrown. If the default + * constructor exists but is not accessible from the context where this + * method is invoked, an {@code IllegalAccessException} is thrown. + * + * @throws IllegalAccessException + * if the default constructor is not visible. + * @throws InstantiationException + * if the instance can not be created. + */ + public T newInstance() throws InstantiationException, IllegalAccessException { + return newInstanceImpl(); + } + + private native T newInstanceImpl() throws IllegalAccessException, InstantiationException; + + @Override + public String toString() { + if (isPrimitive()) { + return getSimpleName(); + } else { + return (isInterface() ? "interface " : "class ") + getName(); + } + } + + /** + * Returns the {@code Package} of which the class represented by this + * {@code Class} is a member. Returns {@code null} if no {@code Package} + * object was created by the class loader of the class. + */ + public Package getPackage() { + // TODO This might be a hack, but the VM doesn't have the necessary info. + ClassLoader loader = getClassLoader(); + if (loader != null) { + String name = getName(); + int dot = name.lastIndexOf('.'); + return (dot != -1 ? loader.getPackage(name.substring(0, dot)) : null); + } + return null; + } + + /** + * Returns the assertion status for the class represented by this {@code + * Class}. Assertion is enabled / disabled based on the class loader, + * package or class default at runtime. + */ + public native boolean desiredAssertionStatus(); + + /** + * Casts this {@code Class} to represent a subclass of the given class. + * If successful, this {@code Class} is returned; otherwise a {@code + * ClassCastException} is thrown. + * + * @throws ClassCastException + * if this {@code Class} cannot be cast to the given type. + */ + @SuppressWarnings("unchecked") + public <U> Class<? extends U> asSubclass(Class<U> c) { + if (c.isAssignableFrom(this)) { + return (Class<? extends U>)this; + } + String actualClassName = this.getName(); + String desiredClassName = c.getName(); + throw new ClassCastException(actualClassName + " cannot be cast to " + desiredClassName); + } + + /** + * Casts the given object to the type represented by this {@code Class}. + * If the object is {@code null} then the result is also {@code null}. + * + * @throws ClassCastException + * if the object cannot be cast to the given type. + */ + @SuppressWarnings("unchecked") + public T cast(Object obj) { + if (obj == null) { + return null; + } else if (this.isInstance(obj)) { + return (T)obj; + } + String actualClassName = obj.getClass().getName(); + String desiredClassName = this.getName(); + throw new ClassCastException(actualClassName + " cannot be cast to " + desiredClassName); + } + + /** + * Copies two arrays into one. Assumes that the destination array is large + * enough. + * + * @param result the destination array + * @param head the first source array + * @param tail the second source array + * @return the destination array, that is, result + */ + private static <T extends Object> T[] arraycopy(T[] result, T[] head, T[] tail) { + System.arraycopy(head, 0, result, 0, head.length); + System.arraycopy(tail, 0, result, head.length, tail.length); + return result; + } +} diff --git a/libdvm/src/main/java/java/lang/ClassLoader.java b/libdvm/src/main/java/java/lang/ClassLoader.java new file mode 100644 index 0000000..dbad137 --- /dev/null +++ b/libdvm/src/main/java/java/lang/ClassLoader.java @@ -0,0 +1,836 @@ +/* + * 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. + */ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package java.lang; + +import dalvik.system.PathClassLoader; +import dalvik.system.VMStack; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.ByteBuffer; +import java.security.ProtectionDomain; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * Loads classes and resources from a repository. One or more class loaders are + * installed at runtime. These are consulted whenever the runtime system needs a + * specific class that is not yet available in-memory. Typically, class loaders + * are grouped into a tree where child class loaders delegate all requests to + * parent class loaders. Only if the parent class loader cannot satisfy the + * request, the child class loader itself tries to handle it. + * <p> + * {@code ClassLoader} is an abstract class that implements the common + * infrastructure required by all class loaders. Android provides several + * concrete implementations of the class, with + * {@link dalvik.system.PathClassLoader} being the one typically used. Other + * applications may implement subclasses of {@code ClassLoader} to provide + * special ways for loading classes. + * </p> + * @see Class + */ +public abstract class ClassLoader { + + /** + * The 'System' ClassLoader - the one that is responsible for loading + * classes from the classpath. It is not equal to the bootstrap class loader - + * that one handles the built-in classes. + * + * Because of a potential class initialization race between ClassLoader and + * java.lang.System, reproducible when using JDWP with "suspend=y", we defer + * creation of the system class loader until first use. We use a static + * inner class to get synchronization at init time without having to sync on + * every access. + * + * @see #getSystemClassLoader() + */ + static private class SystemClassLoader { + public static ClassLoader loader = ClassLoader.createSystemClassLoader(); + } + + /** + * The parent ClassLoader. + */ + private ClassLoader parent; + + /** + * The packages known to the class loader. + */ + private Map<String, Package> packages = new HashMap<String, Package>(); + + /** + * Create the system class loader. Note this is NOT the bootstrap class + * loader (which is managed by the VM). We use a null value for the parent + * to indicate that the bootstrap loader is our parent. + */ + private static ClassLoader createSystemClassLoader() { + String classPath = System.getProperty("java.class.path", "."); + + // String[] paths = classPath.split(":"); + // URL[] urls = new URL[paths.length]; + // for (int i = 0; i < paths.length; i++) { + // try { + // urls[i] = new URL("file://" + paths[i]); + // } + // catch (Exception ex) { + // ex.printStackTrace(); + // } + // } + // + // return new java.net.URLClassLoader(urls, null); + + // TODO Make this a java.net.URLClassLoader once we have those? + return new PathClassLoader(classPath, BootClassLoader.getInstance()); + } + + /** + * Returns the system class loader. This is the parent for new + * {@code ClassLoader} instances and is typically the class loader used to + * start the application. + */ + public static ClassLoader getSystemClassLoader() { + return SystemClassLoader.loader; + } + + /** + * Finds the URL of the resource with the specified name. The system class + * loader's resource lookup algorithm is used to find the resource. + * + * @return the {@code URL} object for the requested resource or {@code null} + * if the resource can not be found. + * @param resName + * the name of the resource to find. + * @see Class#getResource + */ + public static URL getSystemResource(String resName) { + return SystemClassLoader.loader.getResource(resName); + } + + /** + * Returns an enumeration of URLs for the resource with the specified name. + * The system class loader's resource lookup algorithm is used to find the + * resource. + * + * @return an enumeration of {@code URL} objects containing the requested + * resources. + * @param resName + * the name of the resource to find. + * @throws IOException + * if an I/O error occurs. + */ + public static Enumeration<URL> getSystemResources(String resName) throws IOException { + return SystemClassLoader.loader.getResources(resName); + } + + /** + * Returns a stream for the resource with the specified name. The system + * class loader's resource lookup algorithm is used to find the resource. + * Basically, the contents of the java.class.path are searched in order, + * looking for a path which matches the specified resource. + * + * @return a stream for the resource or {@code null}. + * @param resName + * the name of the resource to find. + * @see Class#getResourceAsStream + */ + public static InputStream getSystemResourceAsStream(String resName) { + return SystemClassLoader.loader.getResourceAsStream(resName); + } + + /** + * Constructs a new instance of this class with the system class loader as + * its parent. + */ + protected ClassLoader() { + this(getSystemClassLoader(), false); + } + + /** + * Constructs a new instance of this class with the specified class loader + * as its parent. + * + * @param parentLoader + * The {@code ClassLoader} to use as the new class loader's + * parent. + */ + protected ClassLoader(ClassLoader parentLoader) { + this(parentLoader, false); + } + + /* + * constructor for the BootClassLoader which needs parent to be null. + */ + ClassLoader(ClassLoader parentLoader, boolean nullAllowed) { + if (parentLoader == null && !nullAllowed) { + throw new NullPointerException("parentLoader == null && !nullAllowed"); + } + parent = parentLoader; + } + + /** + * Constructs a new class from an array of bytes containing a class + * definition in class file format. + * + * @param classRep + * the memory image of a class file. + * @param offset + * the offset into {@code classRep}. + * @param length + * the length of the class file. + * @return the {@code Class} object created from the specified subset of + * data in {@code classRep}. + * @throws ClassFormatError + * if {@code classRep} does not contain a valid class. + * @throws IndexOutOfBoundsException + * if {@code offset < 0}, {@code length < 0} or if + * {@code offset + length} is greater than the length of + * {@code classRep}. + * @deprecated Use {@link #defineClass(String, byte[], int, int)} + */ + @Deprecated + protected final Class<?> defineClass(byte[] classRep, int offset, int length) + throws ClassFormatError { + throw new UnsupportedOperationException("can't load this type of class file"); + } + + /** + * Constructs a new class from an array of bytes containing a class + * definition in class file format. + * + * @param className + * the expected name of the new class, may be {@code null} if not + * known. + * @param classRep + * the memory image of a class file. + * @param offset + * the offset into {@code classRep}. + * @param length + * the length of the class file. + * @return the {@code Class} object created from the specified subset of + * data in {@code classRep}. + * @throws ClassFormatError + * if {@code classRep} does not contain a valid class. + * @throws IndexOutOfBoundsException + * if {@code offset < 0}, {@code length < 0} or if + * {@code offset + length} is greater than the length of + * {@code classRep}. + */ + protected final Class<?> defineClass(String className, byte[] classRep, int offset, int length) + throws ClassFormatError { + throw new UnsupportedOperationException("can't load this type of class file"); + } + + /** + * Constructs a new class from an array of bytes containing a class + * definition in class file format and assigns the specified protection + * domain to the new class. If the provided protection domain is + * {@code null} then a default protection domain is assigned to the class. + * + * @param className + * the expected name of the new class, may be {@code null} if not + * known. + * @param classRep + * the memory image of a class file. + * @param offset + * the offset into {@code classRep}. + * @param length + * the length of the class file. + * @param protectionDomain + * the protection domain to assign to the loaded class, may be + * {@code null}. + * @return the {@code Class} object created from the specified subset of + * data in {@code classRep}. + * @throws ClassFormatError + * if {@code classRep} does not contain a valid class. + * @throws IndexOutOfBoundsException + * if {@code offset < 0}, {@code length < 0} or if + * {@code offset + length} is greater than the length of + * {@code classRep}. + * @throws NoClassDefFoundError + * if {@code className} is not equal to the name of the class + * contained in {@code classRep}. + */ + protected final Class<?> defineClass(String className, byte[] classRep, int offset, int length, + ProtectionDomain protectionDomain) throws java.lang.ClassFormatError { + throw new UnsupportedOperationException("can't load this type of class file"); + } + + /** + * Defines a new class with the specified name, byte code from the byte + * buffer and the optional protection domain. If the provided protection + * domain is {@code null} then a default protection domain is assigned to + * the class. + * + * @param name + * the expected name of the new class, may be {@code null} if not + * known. + * @param b + * the byte buffer containing the byte code of the new class. + * @param protectionDomain + * the protection domain to assign to the loaded class, may be + * {@code null}. + * @return the {@code Class} object created from the data in {@code b}. + * @throws ClassFormatError + * if {@code b} does not contain a valid class. + * @throws NoClassDefFoundError + * if {@code className} is not equal to the name of the class + * contained in {@code b}. + */ + protected final Class<?> defineClass(String name, ByteBuffer b, + ProtectionDomain protectionDomain) throws ClassFormatError { + + byte[] temp = new byte[b.remaining()]; + b.get(temp); + return defineClass(name, temp, 0, temp.length, protectionDomain); + } + + /** + * Overridden by subclasses, throws a {@code ClassNotFoundException} by + * default. This method is called by {@code loadClass} after the parent + * {@code ClassLoader} has failed to find a loaded class of the same name. + * + * @param className + * the name of the class to look for. + * @return the {@code Class} object that is found. + * @throws ClassNotFoundException + * if the class cannot be found. + */ + protected Class<?> findClass(String className) throws ClassNotFoundException { + throw new ClassNotFoundException(className); + } + + /** + * Returns the class with the specified name if it has already been loaded + * by the VM or {@code null} if it has not yet been loaded. + * + * @param className + * the name of the class to look for. + * @return the {@code Class} object or {@code null} if the requested class + * has not been loaded. + */ + protected final Class<?> findLoadedClass(String className) { + ClassLoader loader; + if (this == BootClassLoader.getInstance()) + loader = null; + else + loader = this; + return VMClassLoader.findLoadedClass(loader, className); + } + + /** + * Finds the class with the specified name, loading it using the system + * class loader if necessary. + * + * @param className + * the name of the class to look for. + * @return the {@code Class} object with the requested {@code className}. + * @throws ClassNotFoundException + * if the class can not be found. + */ + protected final Class<?> findSystemClass(String className) throws ClassNotFoundException { + return Class.forName(className, false, getSystemClassLoader()); + } + + /** + * Returns this class loader's parent. + * + * @return this class loader's parent or {@code null}. + */ + public final ClassLoader getParent() { + return parent; + } + + /** + * Returns the URL of the resource with the specified name. This + * implementation first tries to use the parent class loader to find the + * resource; if this fails then {@link #findResource(String)} is called to + * find the requested resource. + * + * @param resName + * the name of the resource to find. + * @return the {@code URL} object for the requested resource or {@code null} + * if the resource can not be found + * @see Class#getResource + */ + public URL getResource(String resName) { + URL resource = parent.getResource(resName); + if (resource == null) { + resource = findResource(resName); + } + return resource; + } + + /** + * Returns an enumeration of URLs for the resource with the specified name. + * This implementation first uses this class loader's parent to find the + * resource, then it calls {@link #findResources(String)} to get additional + * URLs. The returned enumeration contains the {@code URL} objects of both + * find operations. + * + * @return an enumeration of {@code URL} objects for the requested resource. + * @param resName + * the name of the resource to find. + * @throws IOException + * if an I/O error occurs. + */ + @SuppressWarnings("unchecked") + public Enumeration<URL> getResources(String resName) throws IOException { + + Enumeration first = parent.getResources(resName); + Enumeration second = findResources(resName); + + return new TwoEnumerationsInOne(first, second); + } + + /** + * Returns a stream for the resource with the specified name. See + * {@link #getResource(String)} for a description of the lookup algorithm + * used to find the resource. + * + * @return a stream for the resource or {@code null} if the resource can not be found + * @param resName + * the name of the resource to find. + * @see Class#getResourceAsStream + */ + public InputStream getResourceAsStream(String resName) { + try { + URL url = getResource(resName); + if (url != null) { + return url.openStream(); + } + } catch (IOException ex) { + // Don't want to see the exception. + } + + return null; + } + + /** + * Loads the class with the specified name. Invoking this method is + * equivalent to calling {@code loadClass(className, false)}. + * <p> + * <strong>Note:</strong> In the Android reference implementation, the + * second parameter of {@link #loadClass(String, boolean)} is ignored + * anyway. + * </p> + * + * @return the {@code Class} object. + * @param className + * the name of the class to look for. + * @throws ClassNotFoundException + * if the class can not be found. + */ + public Class<?> loadClass(String className) throws ClassNotFoundException { + return loadClass(className, false); + } + + /** + * Loads the class with the specified name, optionally linking it after + * loading. The following steps are performed: + * <ol> + * <li> Call {@link #findLoadedClass(String)} to determine if the requested + * class has already been loaded.</li> + * <li>If the class has not yet been loaded: Invoke this method on the + * parent class loader.</li> + * <li>If the class has still not been loaded: Call + * {@link #findClass(String)} to find the class.</li> + * </ol> + * <p> + * <strong>Note:</strong> In the Android reference implementation, the + * {@code resolve} parameter is ignored; classes are never linked. + * </p> + * + * @return the {@code Class} object. + * @param className + * the name of the class to look for. + * @param resolve + * Indicates if the class should be resolved after loading. This + * parameter is ignored on the Android reference implementation; + * classes are not resolved. + * @throws ClassNotFoundException + * if the class can not be found. + */ + protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { + Class<?> clazz = findLoadedClass(className); + + if (clazz == null) { + try { + clazz = parent.loadClass(className, false); + } catch (ClassNotFoundException e) { + // Don't want to see this. + } + + if (clazz == null) { + clazz = findClass(className); + } + } + + return clazz; + } + + /** + * Forces a class to be linked (initialized). If the class has already been + * linked this operation has no effect. + * <p> + * <strong>Note:</strong> In the Android reference implementation, this + * method has no effect. + * </p> + * + * @param clazz + * the class to link. + */ + protected final void resolveClass(Class<?> clazz) { + // no-op, doesn't make sense on android. + } + + /** + * Finds the URL of the resource with the specified name. This + * implementation just returns {@code null}; it should be overridden in + * subclasses. + * + * @param resName + * the name of the resource to find. + * @return the {@code URL} object for the requested resource. + */ + protected URL findResource(String resName) { + return null; + } + + /** + * Finds an enumeration of URLs for the resource with the specified name. + * This implementation just returns an empty {@code Enumeration}; it should + * be overridden in subclasses. + * + * @param resName + * the name of the resource to find. + * @return an enumeration of {@code URL} objects for the requested resource. + * @throws IOException + * if an I/O error occurs. + */ + @SuppressWarnings( { + "unchecked", "unused" + }) + protected Enumeration<URL> findResources(String resName) throws IOException { + return Collections.emptyEnumeration(); + } + + /** + * Returns the absolute path of the native library with the specified name, + * or {@code null}. If this method returns {@code null} then the virtual + * machine searches the directories specified by the system property + * "java.library.path". + * <p> + * This implementation always returns {@code null}. + * </p> + * + * @param libName + * the name of the library to find. + * @return the absolute path of the library. + */ + protected String findLibrary(String libName) { + return null; + } + + /** + * Returns the package with the specified name. Package information is + * searched in this class loader. + * + * @param name + * the name of the package to find. + * @return the package with the requested name; {@code null} if the package + * can not be found. + */ + protected Package getPackage(String name) { + synchronized (packages) { + return packages.get(name); + } + } + + /** + * Returns all the packages known to this class loader. + * + * @return an array with all packages known to this class loader. + */ + protected Package[] getPackages() { + synchronized (packages) { + Collection<Package> col = packages.values(); + Package[] result = new Package[col.size()]; + col.toArray(result); + return result; + } + } + + /** + * Defines and returns a new {@code Package} using the specified + * information. If {@code sealBase} is {@code null}, the package is left + * unsealed. Otherwise, the package is sealed using this URL. + * + * @param name + * the name of the package. + * @param specTitle + * the title of the specification. + * @param specVersion + * the version of the specification. + * @param specVendor + * the vendor of the specification. + * @param implTitle + * the implementation title. + * @param implVersion + * the implementation version. + * @param implVendor + * the specification vendor. + * @param sealBase + * the URL used to seal this package or {@code null} to leave the + * package unsealed. + * @return the {@code Package} object that has been created. + * @throws IllegalArgumentException + * if a package with the specified name already exists. + */ + protected Package definePackage(String name, String specTitle, String specVersion, + String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase) + throws IllegalArgumentException { + + synchronized (packages) { + if (packages.containsKey(name)) { + throw new IllegalArgumentException("Package " + name + " already defined"); + } + + Package newPackage = new Package(name, specTitle, specVersion, specVendor, implTitle, + implVersion, implVendor, sealBase); + + packages.put(name, newPackage); + + return newPackage; + } + } + + /** + * Sets the signers of the specified class. This implementation does + * nothing. + * + * @param c + * the {@code Class} object for which to set the signers. + * @param signers + * the signers for {@code c}. + */ + protected final void setSigners(Class<?> c, Object[] signers) { + } + + /** + * Sets the assertion status of the class with the specified name. + * <p> + * <strong>Note: </strong>This method does nothing in the Android reference + * implementation. + * </p> + * + * @param cname + * the name of the class for which to set the assertion status. + * @param enable + * the new assertion status. + */ + public void setClassAssertionStatus(String cname, boolean enable) { + } + + /** + * Sets the assertion status of the package with the specified name. + * <p> + * <strong>Note: </strong>This method does nothing in the Android reference + * implementation. + * </p> + * + * @param pname + * the name of the package for which to set the assertion status. + * @param enable + * the new assertion status. + */ + public void setPackageAssertionStatus(String pname, boolean enable) { + } + + /** + * Sets the default assertion status for this class loader. + * <p> + * <strong>Note: </strong>This method does nothing in the Android reference + * implementation. + * </p> + * + * @param enable + * the new assertion status. + */ + public void setDefaultAssertionStatus(boolean enable) { + } + + /** + * Sets the default assertion status for this class loader to {@code false} + * and removes any package default and class assertion status settings. + * <p> + * <strong>Note:</strong> This method does nothing in the Android reference + * implementation. + * </p> + */ + public void clearAssertionStatus() { + } +} + +/* + * Provides a helper class that combines two existing URL enumerations into one. + * It is required for the getResources() methods. Items are fetched from the + * first enumeration until it's empty, then from the second one. + */ +class TwoEnumerationsInOne implements Enumeration<URL> { + + private Enumeration<URL> first; + + private Enumeration<URL> second; + + public TwoEnumerationsInOne(Enumeration<URL> first, Enumeration<URL> second) { + this.first = first; + this.second = second; + } + + public boolean hasMoreElements() { + return first.hasMoreElements() || second.hasMoreElements(); + } + + public URL nextElement() { + if (first.hasMoreElements()) { + return first.nextElement(); + } else { + return second.nextElement(); + } + } + +} + +/** + * Provides an explicit representation of the boot class loader. It sits at the + * head of the class loader chain and delegates requests to the VM's internal + * class loading mechanism. + */ +class BootClassLoader extends ClassLoader { + + private static BootClassLoader instance; + + @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED") + public static synchronized BootClassLoader getInstance() { + if (instance == null) { + instance = new BootClassLoader(); + } + + return instance; + } + + public BootClassLoader() { + super(null, true); + } + + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + return VMClassLoader.loadClass(name, false); + } + + @Override + protected URL findResource(String name) { + return VMClassLoader.getResource(name); + } + + @SuppressWarnings("unused") + @Override + protected Enumeration<URL> findResources(String resName) throws IOException { + return Collections.enumeration(VMClassLoader.getResources(resName)); + } + + /** + * Returns package information for the given package. Unfortunately, the + * Android BootClassLoader doesn't really have this information, and as a + * non-secure ClassLoader, it isn't even required to, according to the spec. + * Yet, we want to provide it, in order to make all those hopeful callers of + * {@code myClass.getPackage().getName()} happy. Thus we construct a Package + * object the first time it is being requested and fill most of the fields + * with dummy values. The Package object is then put into the ClassLoader's + * Package cache, so we see the same one next time. We don't create Package + * objects for null arguments or for the default package. + * <p> + * There a limited chance that we end up with multiple Package objects + * representing the same package: It can happen when when a package is + * scattered across different JAR files being loaded by different + * ClassLoaders. Rather unlikely, and given that this whole thing is more or + * less a workaround, probably not worth the effort. + */ + @Override + protected Package getPackage(String name) { + if (name != null && !name.isEmpty()) { + synchronized (this) { + Package pack = super.getPackage(name); + + if (pack == null) { + pack = definePackage(name, "Unknown", "0.0", "Unknown", "Unknown", "0.0", + "Unknown", null); + } + + return pack; + } + } + + return null; + } + + @Override + public URL getResource(String resName) { + return findResource(resName); + } + + @Override + protected Class<?> loadClass(String className, boolean resolve) + throws ClassNotFoundException { + Class<?> clazz = findLoadedClass(className); + + if (clazz == null) { + clazz = findClass(className); + } + + return clazz; + } + + @Override + public Enumeration<URL> getResources(String resName) throws IOException { + return findResources(resName); + } +} + +/** + * TODO Open issues - Missing / empty methods - Signer stuff - Protection + * domains - Assertions + */ diff --git a/libdvm/src/main/java/java/lang/Daemons.java b/libdvm/src/main/java/java/lang/Daemons.java new file mode 100644 index 0000000..78a4152 --- /dev/null +++ b/libdvm/src/main/java/java/lang/Daemons.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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. + */ + +package java.lang; + +import dalvik.system.VMRuntime; +import java.lang.ref.FinalizerReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.concurrent.TimeoutException; +import libcore.util.EmptyArray; + +/** + * Calls Object.finalize() on objects in the finalizer reference queue. The VM + * will abort if any finalize() call takes more than the maximum finalize time + * to complete. + * + * @hide + */ +public final class Daemons { + private static final int NANOS_PER_MILLI = 1000 * 1000; + private static final int NANOS_PER_SECOND = NANOS_PER_MILLI * 1000; + private static final long MAX_FINALIZE_NANOS = 10L * NANOS_PER_SECOND; + + public static void start() { + ReferenceQueueDaemon.INSTANCE.start(); + FinalizerDaemon.INSTANCE.start(); + FinalizerWatchdogDaemon.INSTANCE.start(); + } + + public static void stop() { + ReferenceQueueDaemon.INSTANCE.stop(); + FinalizerDaemon.INSTANCE.stop(); + FinalizerWatchdogDaemon.INSTANCE.stop(); + } + + /** + * A background task that provides runtime support to the application. + * Daemons can be stopped and started, but only so that the zygote can be a + * single-threaded process when it forks. + */ + private static abstract class Daemon implements Runnable { + private Thread thread; + + public synchronized void start() { + if (thread != null) { + throw new IllegalStateException("already running"); + } + thread = new Thread(ThreadGroup.mSystem, this, + getClass().getSimpleName()); + thread.setDaemon(true); + thread.start(); + } + + public abstract void run(); + + /** + * Returns true while the current thread should continue to run; false + * when it should return. + */ + protected synchronized boolean isRunning() { + return thread != null; + } + + public synchronized void interrupt() { + if (thread == null) { + throw new IllegalStateException("not running"); + } + thread.interrupt(); + } + + /** + * Waits for the runtime thread to stop. This interrupts the thread + * currently running the runnable and then waits for it to exit. + */ + public void stop() { + Thread threadToStop; + synchronized (this) { + threadToStop = thread; + thread = null; + } + if (threadToStop == null) { + throw new IllegalStateException("not running"); + } + threadToStop.interrupt(); + while (true) { + try { + threadToStop.join(); + return; + } catch (InterruptedException ignored) { + } + } + } + + /** + * Returns the current stack trace of the thread, or an empty stack trace + * if the thread is not currently running. + */ + public synchronized StackTraceElement[] getStackTrace() { + return thread != null ? thread.getStackTrace() : EmptyArray.STACK_TRACE_ELEMENT; + } + } + + /** + * This heap management thread moves elements from the garbage collector's + * pending list to the managed reference queue. + */ + private static class ReferenceQueueDaemon extends Daemon { + private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon(); + + @Override public void run() { + while (isRunning()) { + Reference<?> list; + try { + synchronized (ReferenceQueue.class) { + while (ReferenceQueue.unenqueued == null) { + ReferenceQueue.class.wait(); + } + list = ReferenceQueue.unenqueued; + ReferenceQueue.unenqueued = null; + } + } catch (InterruptedException e) { + continue; + } + enqueue(list); + } + } + + private void enqueue(Reference<?> list) { + while (list != null) { + Reference<?> reference; + // pendingNext is owned by the GC so no synchronization is required + if (list == list.pendingNext) { + reference = list; + reference.pendingNext = null; + list = null; + } else { + reference = list.pendingNext; + list.pendingNext = reference.pendingNext; + reference.pendingNext = null; + } + reference.enqueueInternal(); + } + } + } + + private static class FinalizerDaemon extends Daemon { + private static final FinalizerDaemon INSTANCE = new FinalizerDaemon(); + private final ReferenceQueue<Object> queue = FinalizerReference.queue; + private volatile Object finalizingObject; + private volatile long finalizingStartedNanos; + + @Override public void run() { + while (isRunning()) { + // Take a reference, blocking until one is ready or the thread should stop + try { + doFinalize((FinalizerReference<?>) queue.remove()); + } catch (InterruptedException ignored) { + } + } + } + + @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION") + private void doFinalize(FinalizerReference<?> reference) { + FinalizerReference.remove(reference); + Object object = reference.get(); + reference.clear(); + try { + finalizingStartedNanos = System.nanoTime(); + finalizingObject = object; + synchronized (FinalizerWatchdogDaemon.INSTANCE) { + FinalizerWatchdogDaemon.INSTANCE.notify(); + } + object.finalize(); + } catch (Throwable ex) { + // The RI silently swallows these, but Android has always logged. + System.logE("Uncaught exception thrown by finalizer", ex); + } finally { + finalizingObject = null; + } + } + } + + /** + * The watchdog exits the VM if the finalizer ever gets stuck. We consider + * the finalizer to be stuck if it spends more than MAX_FINALIZATION_MILLIS + * on one instance. + */ + private static class FinalizerWatchdogDaemon extends Daemon { + private static final FinalizerWatchdogDaemon INSTANCE = new FinalizerWatchdogDaemon(); + + @Override public void run() { + while (isRunning()) { + Object object = waitForObject(); + if (object == null) { + // We have been interrupted, need to see if this daemon has been stopped. + continue; + } + boolean finalized = waitForFinalization(object); + if (!finalized && !VMRuntime.getRuntime().isDebuggerActive()) { + finalizerTimedOut(object); + break; + } + } + } + + private Object waitForObject() { + while (true) { + Object object = FinalizerDaemon.INSTANCE.finalizingObject; + if (object != null) { + return object; + } + synchronized (this) { + // wait until something is ready to be finalized + // http://code.google.com/p/android/issues/detail?id=22778 + try { + wait(); + } catch (InterruptedException e) { + // Daemon.stop may have interrupted us. + return null; + } + } + } + } + + private void sleepFor(long startNanos, long durationNanos) { + while (true) { + long elapsedNanos = System.nanoTime() - startNanos; + long sleepNanos = durationNanos - elapsedNanos; + long sleepMills = sleepNanos / NANOS_PER_MILLI; + if (sleepMills <= 0) { + return; + } + try { + Thread.sleep(sleepMills); + } catch (InterruptedException e) { + if (!isRunning()) { + return; + } + } + } + } + + private boolean waitForFinalization(Object object) { + sleepFor(FinalizerDaemon.INSTANCE.finalizingStartedNanos, MAX_FINALIZE_NANOS); + return object != FinalizerDaemon.INSTANCE.finalizingObject; + } + + private static void finalizerTimedOut(Object object) { + // The current object has exceeded the finalization deadline; abort! + String message = object.getClass().getName() + ".finalize() timed out after " + + (MAX_FINALIZE_NANOS / NANOS_PER_SECOND) + " seconds"; + Exception syntheticException = new TimeoutException(message); + // We use the stack from where finalize() was running to show where it was stuck. + syntheticException.setStackTrace(FinalizerDaemon.INSTANCE.getStackTrace()); + Thread.UncaughtExceptionHandler h = Thread.getDefaultUncaughtExceptionHandler(); + if (h == null) { + // If we have no handler, log and exit. + System.logE(message, syntheticException); + System.exit(2); + } + // Otherwise call the handler to do crash reporting. + // We don't just throw because we're not the thread that + // timed out; we're the thread that detected it. + h.uncaughtException(Thread.currentThread(), syntheticException); + } + } +} diff --git a/libdvm/src/main/java/java/lang/Object.java b/libdvm/src/main/java/java/lang/Object.java new file mode 100644 index 0000000..4bca034 --- /dev/null +++ b/libdvm/src/main/java/java/lang/Object.java @@ -0,0 +1,442 @@ +/* + * 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. + */ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package java.lang; + +/** + * The root class of the Java class hierarchy. All non-primitive types + * (including arrays) inherit either directly or indirectly from this class. + * + * <a name="writing_equals"><h4>Writing a correct {@code equals} method</h4></a> + * <p>Follow this style to write a canonical {@code equals} method: + * <pre> + * // Use @Override to avoid accidental overloading. + * @Override public boolean equals(Object o) { + * // Return true if the objects are identical. + * // (This is just an optimization, not required for correctness.) + * if (this == o) { + * return true; + * } + * + * // Return false if the other object has the wrong type. + * // This type may be an interface depending on the interface's specification. + * if (!(o instanceof MyType)) { + * return false; + * } + * + * // Cast to the appropriate type. + * // This will succeed because of the instanceof, and lets us access private fields. + * MyType lhs = (MyType) o; + * + * // Check each field. Primitive fields, reference fields, and nullable reference + * // fields are all treated differently. + * return primitiveField == lhs.primitiveField && + * referenceField.equals(lhs.referenceField) && + * (nullableField == null ? lhs.nullableField == null + * : nullableField.equals(lhs.nullableField)); + * } + * </pre> + * <p>If you override {@code equals}, you should also override {@code hashCode}: equal + * instances must have equal hash codes. + * + * <p>See <i>Effective Java</i> item 8 for much more detail and clarification. + * + * <a name="writing_hashCode"><h4>Writing a correct {@code hashCode} method</h4></a> + * <p>Follow this style to write a canonical {@code hashCode} method: + * <pre> + * @Override public int hashCode() { + * // Start with a non-zero constant. + * int result = 17; + * + * // Include a hash for each field. + * result = 31 * result + (booleanField ? 1 : 0); + * + * result = 31 * result + byteField; + * result = 31 * result + charField; + * result = 31 * result + shortField; + * result = 31 * result + intField; + * + * result = 31 * result + (int) (longField ^ (longField >>> 32)); + * + * result = 31 * result + Float.floatToIntBits(floatField); + * + * long doubleFieldBits = Double.doubleToLongBits(doubleField); + * result = 31 * result + (int) (doubleFieldBits ^ (doubleFieldBits >>> 32)); + * + * result = 31 * result + Arrays.hashCode(arrayField); + * + * result = 31 * result + referenceField.hashCode(); + * result = 31 * result + + * (nullableReferenceField == null ? 0 + * : nullableReferenceField.hashCode()); + * + * return result; + * } + * </pre> + * + * <p>If you don't intend your type to be used as a hash key, don't simply rely on the default + * {@code hashCode} implementation, because that silently and non-obviously breaks any future + * code that does use your type as a hash key. You should throw instead: + * <pre> + * @Override public int hashCode() { + * throw new UnsupportedOperationException(); + * } + * </pre> + * + * <p>See <i>Effective Java</i> item 9 for much more detail and clarification. + * + * <a name="writing_toString"><h4>Writing a useful {@code toString} method</h4></a> + * <p>For debugging convenience, it's common to override {@code toString} in this style: + * <pre> + * @Override public String toString() { + * return getClass().getName() + "[" + + * "primitiveField=" + primitiveField + ", " + + * "referenceField=" + referenceField + ", " + + * "arrayField=" + Arrays.toString(arrayField) + "]"; + * } + * </pre> + * <p>The set of fields to include is generally the same as those that would be tested + * in your {@code equals} implementation. + * <p>See <i>Effective Java</i> item 10 for much more detail and clarification. + */ +public class Object { + /** + * Constructs a new instance of {@code Object}. + */ + public Object() { + } + + /** + * Creates and returns a copy of this {@code Object}. The default + * implementation returns a so-called "shallow" copy: It creates a new + * instance of the same class and then copies the field values (including + * object references) from this instance to the new instance. A "deep" copy, + * in contrast, would also recursively clone nested objects. A subclass that + * needs to implement this kind of cloning should call {@code super.clone()} + * to create the new instance and then create deep copies of the nested, + * mutable objects. + * + * @return a copy of this object. + * @throws CloneNotSupportedException + * if this object's class does not implement the {@code + * Cloneable} interface. + */ + protected Object clone() throws CloneNotSupportedException { + if (!(this instanceof Cloneable)) { + throw new CloneNotSupportedException("Class doesn't implement Cloneable"); + } + + return internalClone((Cloneable) this); + } + + /* + * Native helper method for cloning. + */ + private native Object internalClone(Cloneable o); + + /** + * Compares this instance with the specified object and indicates if they + * are equal. In order to be equal, {@code o} must represent the same object + * as this instance using a class-specific comparison. The general contract + * is that this comparison should be reflexive, symmetric, and transitive. + * Also, no object reference other than null is equal to null. + * + * <p>The default implementation returns {@code true} only if {@code this == + * o}. See <a href="{@docRoot}reference/java/lang/Object.html#writing_equals">Writing a correct + * {@code equals} method</a> + * if you intend implementing your own {@code equals} method. + * + * <p>The general contract for the {@code equals} and {@link + * #hashCode()} methods is that if {@code equals} returns {@code true} for + * any two objects, then {@code hashCode()} must return the same value for + * these objects. This means that subclasses of {@code Object} usually + * override either both methods or neither of them. + * + * @param o + * the object to compare this instance with. + * @return {@code true} if the specified object is equal to this {@code + * Object}; {@code false} otherwise. + * @see #hashCode + */ + public boolean equals(Object o) { + return this == o; + } + + /** + * Invoked when the garbage collector has detected that this instance is no longer reachable. + * The default implementation does nothing, but this method can be overridden to free resources. + * + * <p>Note that objects that override {@code finalize} are significantly more expensive than + * objects that don't. Finalizers may be run a long time after the object is no longer + * reachable, depending on memory pressure, so it's a bad idea to rely on them for cleanup. + * Note also that finalizers are run on a single VM-wide finalizer thread, + * so doing blocking work in a finalizer is a bad idea. A finalizer is usually only necessary + * for a class that has a native peer and needs to call a native method to destroy that peer. + * Even then, it's better to provide an explicit {@code close} method (and implement + * {@link java.io.Closeable}), and insist that callers manually dispose of instances. This + * works well for something like files, but less well for something like a {@code BigInteger} + * where typical calling code would have to deal with lots of temporaries. Unfortunately, + * code that creates lots of temporaries is the worst kind of code from the point of view of + * the single finalizer thread. + * + * <p>If you <i>must</i> use finalizers, consider at least providing your own + * {@link java.lang.ref.ReferenceQueue} and having your own thread process that queue. + * + * <p>Unlike constructors, finalizers are not automatically chained. You are responsible for + * calling {@code super.finalize()} yourself. + * + * <p>Uncaught exceptions thrown by finalizers are ignored and do not terminate the finalizer + * thread. + * + * See <i>Effective Java</i> Item 7, "Avoid finalizers" for more. + */ + @FindBugsSuppressWarnings("FI_EMPTY") + protected void finalize() throws Throwable { + } + + /** + * Returns the unique instance of {@link Class} that represents this + * object's class. Note that {@code getClass()} is a special case in that it + * actually returns {@code Class<? extends Foo>} where {@code Foo} is the + * erasure of the type of the expression {@code getClass()} was called upon. + * <p> + * As an example, the following code actually compiles, although one might + * think it shouldn't: + * <p> + * <pre>{@code + * List<Integer> l = new ArrayList<Integer>(); + * Class<? extends List> c = l.getClass();}</pre> + * + * @return this object's {@code Class} instance. + */ + public final native Class<?> getClass(); + + /** + * Returns an integer hash code for this object. By contract, any two + * objects for which {@link #equals} returns {@code true} must return + * the same hash code value. This means that subclasses of {@code Object} + * usually override both methods or neither method. + * + * <p>Note that hash values must not change over time unless information used in equals + * comparisons also changes. + * + * <p>See <a href="{@docRoot}reference/java/lang/Object.html#writing_hashCode">Writing a correct + * {@code hashCode} method</a> + * if you intend implementing your own {@code hashCode} method. + * + * @return this object's hash code. + * @see #equals + */ + public native int hashCode(); + + /** + * Causes a thread which is waiting on this object's monitor (by means of + * calling one of the {@code wait()} methods) to be woken up. If more than + * one thread is waiting, one of them is chosen at the discretion of the + * VM. The chosen thread will not run immediately. The thread + * that called {@code notify()} has to release the object's monitor first. + * Also, the chosen thread still has to compete against other threads that + * try to synchronize on the same object. + * <p> + * This method can only be invoked by a thread which owns this object's + * monitor. A thread becomes owner of an object's monitor + * </p> + * <ul> + * <li>by executing a synchronized method of that object;</li> + * <li>by executing the body of a {@code synchronized} statement that + * synchronizes on the object;</li> + * <li>by executing a synchronized static method if the object is of type + * {@code Class}.</li> + * </ul> + * + * @see #notifyAll + * @see #wait() + * @see #wait(long) + * @see #wait(long,int) + * @see java.lang.Thread + */ + public final native void notify(); + + /** + * Causes all threads which are waiting on this object's monitor (by means + * of calling one of the {@code wait()} methods) to be woken up. The threads + * will not run immediately. The thread that called {@code notify()} has to + * release the object's monitor first. Also, the threads still have to + * compete against other threads that try to synchronize on the same object. + * <p> + * This method can only be invoked by a thread which owns this object's + * monitor. A thread becomes owner of an object's monitor + * </p> + * <ul> + * <li>by executing a synchronized method of that object;</li> + * <li>by executing the body of a {@code synchronized} statement that + * synchronizes on the object;</li> + * <li>by executing a synchronized static method if the object is of type + * {@code Class}.</li> + * </ul> + * + * @throws IllegalMonitorStateException + * if the thread calling this method is not the owner of this + * object's monitor. + * @see #notify + * @see #wait() + * @see #wait(long) + * @see #wait(long,int) + * @see java.lang.Thread + */ + public final native void notifyAll(); + + /** + * Returns a string containing a concise, human-readable description of this + * object. Subclasses are encouraged to override this method and provide an + * implementation that takes into account the object's type and data. The + * default implementation is equivalent to the following expression: + * <pre> + * getClass().getName() + '@' + Integer.toHexString(hashCode())</pre> + * <p>See <a href="{@docRoot}reference/java/lang/Object.html#writing_toString">Writing a useful + * {@code toString} method</a> + * if you intend implementing your own {@code toString} method. + * + * @return a printable representation of this object. + */ + public String toString() { + return getClass().getName() + '@' + Integer.toHexString(hashCode()); + } + + /** + * Causes the calling thread to wait until another thread calls the {@code + * notify()} or {@code notifyAll()} method of this object. This method can + * only be invoked by a thread which owns this object's monitor; see + * {@link #notify()} on how a thread can become the owner of a monitor. + * <p> + * A waiting thread can be sent {@code interrupt()} to cause it to + * prematurely stop waiting, so {@code wait} should be called in a loop to + * check that the condition that has been waited for has been met before + * continuing. + * </p> + * <p> + * While the thread waits, it gives up ownership of this object's monitor. + * When it is notified (or interrupted), it re-acquires the monitor before + * it starts running. + * </p> + * + * @throws IllegalMonitorStateException + * if the thread calling this method is not the owner of this + * object's monitor. + * @throws InterruptedException + * if another thread interrupts this thread while it is waiting. + * @see #notify + * @see #notifyAll + * @see #wait(long) + * @see #wait(long,int) + * @see java.lang.Thread + */ + public final void wait() throws InterruptedException { + wait(0, 0); + } + + /** + * Causes the calling thread to wait until another thread calls the {@code + * notify()} or {@code notifyAll()} method of this object or until the + * specified timeout expires. This method can only be invoked by a thread + * which owns this object's monitor; see {@link #notify()} on how a thread + * can become the owner of a monitor. + * <p> + * A waiting thread can be sent {@code interrupt()} to cause it to + * prematurely stop waiting, so {@code wait} should be called in a loop to + * check that the condition that has been waited for has been met before + * continuing. + * </p> + * <p> + * While the thread waits, it gives up ownership of this object's monitor. + * When it is notified (or interrupted), it re-acquires the monitor before + * it starts running. + * </p> + * + * @param millis + * the maximum time to wait in milliseconds. + * @throws IllegalArgumentException + * if {@code millis < 0}. + * @throws IllegalMonitorStateException + * if the thread calling this method is not the owner of this + * object's monitor. + * @throws InterruptedException + * if another thread interrupts this thread while it is waiting. + * @see #notify + * @see #notifyAll + * @see #wait() + * @see #wait(long,int) + * @see java.lang.Thread + */ + public final void wait(long millis) throws InterruptedException { + wait(millis, 0); + } + + /** + * Causes the calling thread to wait until another thread calls the {@code + * notify()} or {@code notifyAll()} method of this object or until the + * specified timeout expires. This method can only be invoked by a thread + * that owns this object's monitor; see {@link #notify()} on how a thread + * can become the owner of a monitor. + * <p> + * A waiting thread can be sent {@code interrupt()} to cause it to + * prematurely stop waiting, so {@code wait} should be called in a loop to + * check that the condition that has been waited for has been met before + * continuing. + * </p> + * <p> + * While the thread waits, it gives up ownership of this object's monitor. + * When it is notified (or interrupted), it re-acquires the monitor before + * it starts running. + * </p> + * + * @param millis + * the maximum time to wait in milliseconds. + * @param nanos + * the fraction of a millisecond to wait, specified in + * nanoseconds. + * @throws IllegalArgumentException + * if {@code millis < 0}, {@code nanos < 0} or {@code nanos > + * 999999}. + * @throws IllegalMonitorStateException + * if the thread calling this method is not the owner of this + * object's monitor. + * @throws InterruptedException + * if another thread interrupts this thread while it is waiting. + * @see #notify + * @see #notifyAll + * @see #wait() + * @see #wait(long,int) + * @see java.lang.Thread + */ + public final native void wait(long millis, int nanos) throws InterruptedException; +} diff --git a/libdvm/src/main/java/java/lang/Thread.java b/libdvm/src/main/java/java/lang/Thread.java new file mode 100644 index 0000000..cc0975f --- /dev/null +++ b/libdvm/src/main/java/java/lang/Thread.java @@ -0,0 +1,1281 @@ +/* + * 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. + */ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package java.lang; + +import dalvik.system.VMStack; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import libcore.util.EmptyArray; + +/** + * A {@code Thread} is a concurrent unit of execution. It has its own call stack + * for methods being invoked, their arguments and local variables. Each application + * has at least one thread running when it is started, the main thread, in the main + * {@link ThreadGroup}. The runtime keeps its own threads in the system thread + * group. + * + * <p>There are two ways to execute code in a new thread. + * You can either subclass {@code Thread} and overriding its {@link #run()} method, + * or construct a new {@code Thread} and pass a {@link Runnable} to the constructor. + * In either case, the {@link #start()} method must be called to actually execute + * the new {@code Thread}. + * + * <p>Each {@code Thread} has an integer priority that affect how the thread is + * scheduled by the OS. A new thread inherits the priority of its parent. + * A thread's priority can be set using the {@link #setPriority(int)} method. + */ +public class Thread implements Runnable { + private static final int NANOS_PER_MILLI = 1000000; + + /** Park states */ + private static class ParkState { + /** park state indicating unparked */ + private static final int UNPARKED = 1; + + /** park state indicating preemptively unparked */ + private static final int PREEMPTIVELY_UNPARKED = 2; + + /** park state indicating parked */ + private static final int PARKED = 3; + } + + /** + * A representation of a thread's state. A given thread may only be in one + * state at a time. + */ + public enum State { + /** + * The thread has been created, but has never been started. + */ + NEW, + /** + * The thread may be run. + */ + RUNNABLE, + /** + * The thread is blocked and waiting for a lock. + */ + BLOCKED, + /** + * The thread is waiting. + */ + WAITING, + /** + * The thread is waiting for a specified amount of time. + */ + TIMED_WAITING, + /** + * The thread has been terminated. + */ + TERMINATED + } + + /** + * The maximum priority value allowed for a thread. + * This corresponds to (but does not have the same value as) + * {@code android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY}. + */ + public static final int MAX_PRIORITY = 10; + + /** + * The minimum priority value allowed for a thread. + * This corresponds to (but does not have the same value as) + * {@code android.os.Process.THREAD_PRIORITY_LOWEST}. + */ + public static final int MIN_PRIORITY = 1; + + /** + * The normal (default) priority value assigned to the main thread. + * This corresponds to (but does not have the same value as) + * {@code android.os.Process.THREAD_PRIORITY_DEFAULT}. + + */ + public static final int NORM_PRIORITY = 5; + + /* some of these are accessed directly by the VM; do not rename them */ + volatile VMThread vmThread; + volatile ThreadGroup group; + volatile boolean daemon; + volatile String name; + volatile int priority; + volatile long stackSize; + Runnable target; + private static int count = 0; + + /** + * Holds the thread's ID. We simply count upwards, so + * each Thread has a unique ID. + */ + private long id; + + /** + * Normal thread local values. + */ + ThreadLocal.Values localValues; + + /** + * Inheritable thread local values. + */ + ThreadLocal.Values inheritableValues; + + /** Callbacks to run on interruption. */ + private final List<Runnable> interruptActions = new ArrayList<Runnable>(); + + /** + * Holds the class loader for this Thread, in case there is one. + */ + private ClassLoader contextClassLoader; + + /** + * Holds the handler for uncaught exceptions in this Thread, + * in case there is one. + */ + private UncaughtExceptionHandler uncaughtHandler; + + /** + * Holds the default handler for uncaught exceptions, in case there is one. + */ + private static UncaughtExceptionHandler defaultUncaughtHandler; + + /** + * Reflects whether this Thread has already been started. A Thread + * can only be started once (no recycling). Also, we need it to deduce + * the proper Thread status. + */ + boolean hasBeenStarted = false; + + /** the park state of the thread */ + private int parkState = ParkState.UNPARKED; + + /** The synchronization object responsible for this thread parking. */ + private Object parkBlocker; + + /** + * Constructs a new {@code Thread} with no {@code Runnable} object and a + * newly generated name. The new {@code Thread} will belong to the same + * {@code ThreadGroup} as the {@code Thread} calling this constructor. + * + * @see java.lang.ThreadGroup + * @see java.lang.Runnable + */ + public Thread() { + create(null, null, null, 0); + } + + /** + * Constructs a new {@code Thread} with a {@code Runnable} object and a + * newly generated name. The new {@code Thread} will belong to the same + * {@code ThreadGroup} as the {@code Thread} calling this constructor. + * + * @param runnable + * a {@code Runnable} whose method <code>run</code> will be + * executed by the new {@code Thread} + * + * @see java.lang.ThreadGroup + * @see java.lang.Runnable + */ + public Thread(Runnable runnable) { + create(null, runnable, null, 0); + } + + /** + * Constructs a new {@code Thread} with a {@code Runnable} object and name + * provided. The new {@code Thread} will belong to the same {@code + * ThreadGroup} as the {@code Thread} calling this constructor. + * + * @param runnable + * a {@code Runnable} whose method <code>run</code> will be + * executed by the new {@code Thread} + * @param threadName + * the name for the {@code Thread} being created + * + * @see java.lang.ThreadGroup + * @see java.lang.Runnable + */ + public Thread(Runnable runnable, String threadName) { + if (threadName == null) { + throw new NullPointerException("threadName == null"); + } + + create(null, runnable, threadName, 0); + } + + /** + * Constructs a new {@code Thread} with no {@code Runnable} object and the + * name provided. The new {@code Thread} will belong to the same {@code + * ThreadGroup} as the {@code Thread} calling this constructor. + * + * @param threadName + * the name for the {@code Thread} being created + * + * @see java.lang.ThreadGroup + * @see java.lang.Runnable + * + */ + public Thread(String threadName) { + if (threadName == null) { + throw new NullPointerException("threadName == null"); + } + + create(null, null, threadName, 0); + } + + /** + * Constructs a new {@code Thread} with a {@code Runnable} object and a + * newly generated name. The new {@code Thread} will belong to the {@code + * ThreadGroup} passed as parameter. + * + * @param group + * {@code ThreadGroup} to which the new {@code Thread} will + * belong + * @param runnable + * a {@code Runnable} whose method <code>run</code> will be + * executed by the new {@code Thread} + * @throws IllegalThreadStateException + * if <code>group.destroy()</code> has already been done + * @see java.lang.ThreadGroup + * @see java.lang.Runnable + */ + public Thread(ThreadGroup group, Runnable runnable) { + create(group, runnable, null, 0); + } + + /** + * Constructs a new {@code Thread} with a {@code Runnable} object, the given + * name and belonging to the {@code ThreadGroup} passed as parameter. + * + * @param group + * ThreadGroup to which the new {@code Thread} will belong + * @param runnable + * a {@code Runnable} whose method <code>run</code> will be + * executed by the new {@code Thread} + * @param threadName + * the name for the {@code Thread} being created + * @throws IllegalThreadStateException + * if <code>group.destroy()</code> has already been done + * @see java.lang.ThreadGroup + * @see java.lang.Runnable + */ + public Thread(ThreadGroup group, Runnable runnable, String threadName) { + if (threadName == null) { + throw new NullPointerException("threadName == null"); + } + + create(group, runnable, threadName, 0); + } + + /** + * Constructs a new {@code Thread} with no {@code Runnable} object, the + * given name and belonging to the {@code ThreadGroup} passed as parameter. + * + * @param group + * {@code ThreadGroup} to which the new {@code Thread} will belong + * @param threadName + * the name for the {@code Thread} being created + * @throws IllegalThreadStateException + * if <code>group.destroy()</code> has already been done + * @see java.lang.ThreadGroup + * @see java.lang.Runnable + */ + public Thread(ThreadGroup group, String threadName) { + if (threadName == null) { + throw new NullPointerException("threadName == null"); + } + + create(group, null, threadName, 0); + } + + /** + * Constructs a new {@code Thread} with a {@code Runnable} object, the given + * name and belonging to the {@code ThreadGroup} passed as parameter. + * + * @param group + * {@code ThreadGroup} to which the new {@code Thread} will + * belong + * @param runnable + * a {@code Runnable} whose method <code>run</code> will be + * executed by the new {@code Thread} + * @param threadName + * the name for the {@code Thread} being created + * @param stackSize + * a stack size for the new {@code Thread}. This has a highly + * platform-dependent interpretation. It may even be ignored + * completely. + * @throws IllegalThreadStateException + * if <code>group.destroy()</code> has already been done + * @see java.lang.ThreadGroup + * @see java.lang.Runnable + */ + public Thread(ThreadGroup group, Runnable runnable, String threadName, long stackSize) { + if (threadName == null) { + throw new NullPointerException("threadName == null"); + } + create(group, runnable, threadName, stackSize); + } + + /** + * Package-scope method invoked by Dalvik VM to create "internal" + * threads or attach threads created externally. + * + * Don't call Thread.currentThread(), since there may not be such + * a thing (e.g. for Main). + */ + Thread(ThreadGroup group, String name, int priority, boolean daemon) { + synchronized (Thread.class) { + id = ++Thread.count; + } + + if (name == null) { + this.name = "Thread-" + id; + } else { + this.name = name; + } + + if (group == null) { + throw new InternalError("group not specified"); + } + + this.group = group; + + this.target = null; + this.stackSize = 0; + this.priority = priority; + this.daemon = daemon; + + /* add ourselves to our ThreadGroup of choice */ + this.group.addThread(this); + } + + /** + * Initializes a new, existing Thread object with a runnable object, + * the given name and belonging to the ThreadGroup passed as parameter. + * This is the method that the several public constructors delegate their + * work to. + * + * @param group ThreadGroup to which the new Thread will belong + * @param runnable a java.lang.Runnable whose method <code>run</code> will + * be executed by the new Thread + * @param threadName Name for the Thread being created + * @param stackSize Platform dependent stack size + * @throws IllegalThreadStateException if <code>group.destroy()</code> has + * already been done + * @see java.lang.ThreadGroup + * @see java.lang.Runnable + */ + private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) { + Thread currentThread = Thread.currentThread(); + if (group == null) { + group = currentThread.getThreadGroup(); + } + + if (group.isDestroyed()) { + throw new IllegalThreadStateException("Group already destroyed"); + } + + this.group = group; + + synchronized (Thread.class) { + id = ++Thread.count; + } + + if (threadName == null) { + this.name = "Thread-" + id; + } else { + this.name = threadName; + } + + this.target = runnable; + this.stackSize = stackSize; + + this.priority = currentThread.getPriority(); + + this.contextClassLoader = currentThread.contextClassLoader; + + // Transfer over InheritableThreadLocals. + if (currentThread.inheritableValues != null) { + inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues); + } + + // add ourselves to our ThreadGroup of choice + this.group.addThread(this); + } + + /** + * Returns the number of active {@code Thread}s in the running {@code + * Thread}'s group and its subgroups. + * + * @return the number of {@code Thread}s + */ + public static int activeCount() { + return currentThread().getThreadGroup().activeCount(); + } + + /** + * Does nothing. + */ + public final void checkAccess() { + } + + /** + * Returns the number of stack frames in this thread. + * + * @return Number of stack frames + * @deprecated The results of this call were never well defined. To make + * things worse, it would depend on whether the Thread was + * suspended or not, and suspend was deprecated too. + */ + @Deprecated + public int countStackFrames() { + return getStackTrace().length; + } + + /** + * Returns the Thread of the caller, that is, the current Thread. + * + * @return the current Thread. + */ + public static Thread currentThread() { + return VMThread.currentThread(); + } + + /** + * Throws {@code UnsupportedOperationException}. + * @deprecated Not implemented. + */ + @Deprecated + public void destroy() { + throw new UnsupportedOperationException(); + } + + /** + * Prints to the standard error stream a text representation of the current + * stack for this Thread. + * + * @see Throwable#printStackTrace() + */ + public static void dumpStack() { + new Throwable("stack dump").printStackTrace(); + } + + /** + * Copies an array with all Threads which are in the same ThreadGroup as the + * receiver - and subgroups - into the array <code>threads</code> passed as + * parameter. If the array passed as parameter is too small no exception is + * thrown - the extra elements are simply not copied. + * + * @param threads + * array into which the Threads will be copied + * @return How many Threads were copied over + */ + public static int enumerate(Thread[] threads) { + Thread thread = Thread.currentThread(); + return thread.getThreadGroup().enumerate(threads); + } + + /** + * Returns a map of all the currently live threads to their stack traces. + */ + public static Map<Thread, StackTraceElement[]> getAllStackTraces() { + Map<Thread, StackTraceElement[]> map = new HashMap<Thread, StackTraceElement[]>(); + + // Find out how many live threads we have. Allocate a bit more + // space than needed, in case new ones are just being created. + int count = ThreadGroup.mSystem.activeCount(); + Thread[] threads = new Thread[count + count / 2]; + + // Enumerate the threads and collect the stacktraces. + count = ThreadGroup.mSystem.enumerate(threads); + for (int i = 0; i < count; i++) { + map.put(threads[i], threads[i].getStackTrace()); + } + + return map; + } + + /** + * Returns the context ClassLoader for this Thread. + * + * @return ClassLoader The context ClassLoader + * @see java.lang.ClassLoader + * @see #getContextClassLoader() + */ + public ClassLoader getContextClassLoader() { + return contextClassLoader; + } + + /** + * Returns the default exception handler that's executed when uncaught + * exception terminates a thread. + * + * @return an {@link UncaughtExceptionHandler} or <code>null</code> if + * none exists. + */ + public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() { + return defaultUncaughtHandler; + } + + /** + * Returns the thread's identifier. The ID is a positive <code>long</code> + * generated on thread creation, is unique to the thread, and doesn't change + * during the lifetime of the thread; the ID may be reused after the thread + * has been terminated. + * + * @return the thread's ID. + */ + public long getId() { + return id; + } + + /** + * Returns the name of the Thread. + */ + public final String getName() { + return name; + } + + /** + * Returns the priority of the Thread. + */ + public final int getPriority() { + return priority; + } + + /** + * Returns an array of {@link StackTraceElement} representing the current thread's stack. + */ + public StackTraceElement[] getStackTrace() { + StackTraceElement ste[] = VMStack.getThreadStackTrace(this); + return ste != null ? ste : EmptyArray.STACK_TRACE_ELEMENT; + } + + /** + * Returns the current state of the Thread. This method is useful for + * monitoring purposes. + * + * @return a {@link State} value. + */ + public State getState() { + // TODO This is ugly and should be implemented better. + VMThread vmt = this.vmThread; + + // Make sure we have a valid reference to an object. If native code + // deletes the reference we won't run into a null reference later. + VMThread thread = vmThread; + if (thread != null) { + // If the Thread Object became invalid or was not yet started, + // getStatus() will return -1. + int state = thread.getStatus(); + if(state != -1) { + return VMThread.STATE_MAP[state]; + } + } + return hasBeenStarted ? Thread.State.TERMINATED : Thread.State.NEW; + } + + /** + * Returns the ThreadGroup to which this Thread belongs. + * + * @return the Thread's ThreadGroup + */ + public final ThreadGroup getThreadGroup() { + // TODO This should actually be done at native termination. + if (getState() == Thread.State.TERMINATED) { + return null; + } else { + return group; + } + } + + /** + * Returns the thread's uncaught exception handler. If not explicitly set, + * then the ThreadGroup's handler is returned. If the thread is terminated, + * then <code>null</code> is returned. + * + * @return an {@link UncaughtExceptionHandler} instance or {@code null}. + */ + public UncaughtExceptionHandler getUncaughtExceptionHandler() { + if (uncaughtHandler != null) + return uncaughtHandler; + else + return group; // ThreadGroup is instance of UEH + } + + /** + * Posts an interrupt request to this {@code Thread}. The behavior depends on + * the state of this {@code Thread}: + * <ul> + * <li> + * {@code Thread}s blocked in one of {@code Object}'s {@code wait()} methods + * or one of {@code Thread}'s {@code join()} or {@code sleep()} methods will + * be woken up, their interrupt status will be cleared, and they receive an + * {@link InterruptedException}. + * <li> + * {@code Thread}s blocked in an I/O operation of an + * {@link java.nio.channels.InterruptibleChannel} will have their interrupt + * status set and receive an + * {@link java.nio.channels.ClosedByInterruptException}. Also, the channel + * will be closed. + * <li> + * {@code Thread}s blocked in a {@link java.nio.channels.Selector} will have + * their interrupt status set and return immediately. They don't receive an + * exception in this case. + * <ul> + * + * @see Thread#interrupted + * @see Thread#isInterrupted + */ + public void interrupt() { + // Interrupt this thread before running actions so that other + // threads that observe the interrupt as a result of an action + // will see that this thread is in the interrupted state. + VMThread vmt = this.vmThread; + if (vmt != null) { + vmt.interrupt(); + } + + synchronized (interruptActions) { + for (int i = interruptActions.size() - 1; i >= 0; i--) { + interruptActions.get(i).run(); + } + } + } + + /** + * Returns a <code>boolean</code> indicating whether the current Thread ( + * <code>currentThread()</code>) has a pending interrupt request (<code> + * true</code>) or not (<code>false</code>). It also has the side-effect of + * clearing the flag. + * + * @return a <code>boolean</code> indicating the interrupt status + * @see Thread#currentThread + * @see Thread#interrupt + * @see Thread#isInterrupted + */ + public static boolean interrupted() { + return VMThread.interrupted(); + } + + /** + * Returns <code>true</code> if the receiver has already been started and + * still runs code (hasn't died yet). Returns <code>false</code> either if + * the receiver hasn't been started yet or if it has already started and run + * to completion and died. + * + * @return a <code>boolean</code> indicating the liveness of the Thread + * @see Thread#start + */ + public final boolean isAlive() { + return (vmThread != null); + } + + /** + * Tests whether this is a daemon thread. + * A daemon thread only runs as long as there are non-daemon threads running. + * When the last non-daemon thread ends, the runtime will exit. This is not + * normally relevant to applications with a UI. + */ + public final boolean isDaemon() { + return daemon; + } + + /** + * Returns a <code>boolean</code> indicating whether the receiver has a + * pending interrupt request (<code>true</code>) or not ( + * <code>false</code>) + * + * @return a <code>boolean</code> indicating the interrupt status + * @see Thread#interrupt + * @see Thread#interrupted + */ + public boolean isInterrupted() { + VMThread vmt = this.vmThread; + if (vmt != null) { + return vmt.isInterrupted(); + } + + return false; + } + + /** + * Blocks the current Thread (<code>Thread.currentThread()</code>) until + * the receiver finishes its execution and dies. + * + * @throws InterruptedException if <code>interrupt()</code> was called for + * the receiver while it was in the <code>join()</code> call + * @see Object#notifyAll + * @see java.lang.ThreadDeath + */ + public final void join() throws InterruptedException { + VMThread t = vmThread; + if (t == null) { + return; + } + + synchronized (t) { + while (isAlive()) { + t.wait(); + } + } + } + + /** + * Blocks the current Thread (<code>Thread.currentThread()</code>) until + * the receiver finishes its execution and dies or the specified timeout + * expires, whatever happens first. + * + * @param millis The maximum time to wait (in milliseconds). + * @throws InterruptedException if <code>interrupt()</code> was called for + * the receiver while it was in the <code>join()</code> call + * @see Object#notifyAll + * @see java.lang.ThreadDeath + */ + public final void join(long millis) throws InterruptedException { + join(millis, 0); + } + + /** + * Blocks the current Thread (<code>Thread.currentThread()</code>) until + * the receiver finishes its execution and dies or the specified timeout + * expires, whatever happens first. + * + * @param millis The maximum time to wait (in milliseconds). + * @param nanos Extra nanosecond precision + * @throws InterruptedException if <code>interrupt()</code> was called for + * the receiver while it was in the <code>join()</code> call + * @see Object#notifyAll + * @see java.lang.ThreadDeath + */ + public final void join(long millis, int nanos) throws InterruptedException { + if (millis < 0 || nanos < 0 || nanos >= NANOS_PER_MILLI) { + throw new IllegalArgumentException("bad timeout: millis=" + millis + ",nanos=" + nanos); + } + + // avoid overflow: if total > 292,277 years, just wait forever + boolean overflow = millis >= (Long.MAX_VALUE - nanos) / NANOS_PER_MILLI; + boolean forever = (millis | nanos) == 0; + if (forever | overflow) { + join(); + return; + } + + VMThread t = vmThread; + if (t == null) { + return; + } + + synchronized (t) { + if (!isAlive()) { + return; + } + + // guaranteed not to overflow + long nanosToWait = millis * NANOS_PER_MILLI + nanos; + + // wait until this thread completes or the timeout has elapsed + long start = System.nanoTime(); + while (true) { + t.wait(millis, nanos); + if (!isAlive()) { + break; + } + long nanosElapsed = System.nanoTime() - start; + long nanosRemaining = nanosToWait - nanosElapsed; + if (nanosRemaining <= 0) { + break; + } + millis = nanosRemaining / NANOS_PER_MILLI; + nanos = (int) (nanosRemaining - millis * NANOS_PER_MILLI); + } + } + } + + /** + * Throws {@code UnsupportedOperationException}. + * @deprecated Only useful in conjunction with deprecated method {@link Thread#suspend}. + */ + @Deprecated + public final void resume() { + throw new UnsupportedOperationException(); + } + + /** + * Calls the <code>run()</code> method of the Runnable object the receiver + * holds. If no Runnable is set, does nothing. + * + * @see Thread#start + */ + public void run() { + if (target != null) { + target.run(); + } + } + + /** + * Set the context ClassLoader for the receiver. + * + * @param cl The context ClassLoader + * @see #getContextClassLoader() + */ + public void setContextClassLoader(ClassLoader cl) { + contextClassLoader = cl; + } + + /** + * Marks this thread as a daemon thread. + * A daemon thread only runs as long as there are non-daemon threads running. + * When the last non-daemon thread ends, the runtime will exit. This is not + * normally relevant to applications with a UI. + * @throws IllegalThreadStateException - if this thread has already started. + */ + public final void setDaemon(boolean isDaemon) { + checkNotStarted(); + if (vmThread == null) { + daemon = isDaemon; + } + } + + private void checkNotStarted() { + if (hasBeenStarted) { + throw new IllegalThreadStateException("Thread already started"); + } + } + + /** + * Sets the default uncaught exception handler. This handler is invoked in + * case any Thread dies due to an unhandled exception. + * + * @param handler + * The handler to set or null. + */ + public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) { + Thread.defaultUncaughtHandler = handler; + } + + /** + * Adds a runnable to be invoked upon interruption. If this thread has + * already been interrupted, the runnable will be invoked immediately. The + * action should be idempotent as it may be invoked multiple times for a + * single interruption. + * + * <p>Each call to this method must be matched with a corresponding call to + * {@link #popInterruptAction$}. + * + * @hide used by NIO + */ + public final void pushInterruptAction$(Runnable interruptAction) { + synchronized (interruptActions) { + interruptActions.add(interruptAction); + } + + if (interruptAction != null && isInterrupted()) { + interruptAction.run(); + } + } + + /** + * Removes {@code interruptAction} so it is not invoked upon interruption. + * + * @param interruptAction the pushed action, used to check that the call + * stack is correctly nested. + * + * @hide used by NIO + */ + public final void popInterruptAction$(Runnable interruptAction) { + synchronized (interruptActions) { + Runnable removed = interruptActions.remove(interruptActions.size() - 1); + if (interruptAction != removed) { + throw new IllegalArgumentException( + "Expected " + interruptAction + " but was " + removed); + } + } + } + + /** + * Sets the name of the Thread. + * + * @param threadName the new name for the Thread + * @see Thread#getName + */ + public final void setName(String threadName) { + if (threadName == null) { + throw new NullPointerException("threadName == null"); + } + + name = threadName; + VMThread vmt = this.vmThread; + if (vmt != null) { + /* notify the VM that the thread name has changed */ + vmt.nameChanged(threadName); + } + } + + /** + * Sets the priority of this thread. If the requested priority is greater than the + * parent thread group's {@link java.lang.ThreadGroup#getMaxPriority}, the group's maximum + * priority will be used instead. + * + * @throws IllegalArgumentException - if the new priority is greater than {@link #MAX_PRIORITY} + * or less than {@link #MIN_PRIORITY} + */ + public final void setPriority(int priority) { + if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) { + throw new IllegalArgumentException("Priority out of range: " + priority); + } + + if (priority > group.getMaxPriority()) { + priority = group.getMaxPriority(); + } + + this.priority = priority; + + VMThread vmt = this.vmThread; + if (vmt != null) { + vmt.setPriority(priority); + } + } + + /** + * <p> + * Sets the uncaught exception handler. This handler is invoked in case this + * Thread dies due to an unhandled exception. + * </p> + * + * @param handler + * The handler to set or <code>null</code>. + */ + public void setUncaughtExceptionHandler(UncaughtExceptionHandler handler) { + uncaughtHandler = handler; + } + + /** + * Causes the thread which sent this message to sleep for the given interval + * of time (given in milliseconds). The precision is not guaranteed - the + * Thread may sleep more or less than requested. + * + * @param time + * The time to sleep in milliseconds. + * @throws InterruptedException + * if <code>interrupt()</code> was called for this Thread while + * it was sleeping + * @see Thread#interrupt() + */ + public static void sleep(long time) throws InterruptedException { + Thread.sleep(time, 0); + } + + /** + * Causes the thread which sent this message to sleep for the given interval + * of time (given in milliseconds and nanoseconds). The precision is not + * guaranteed - the Thread may sleep more or less than requested. + * + * @param millis + * The time to sleep in milliseconds. + * @param nanos + * Extra nanosecond precision + * @throws InterruptedException + * if <code>interrupt()</code> was called for this Thread while + * it was sleeping + * @see Thread#interrupt() + */ + public static void sleep(long millis, int nanos) throws InterruptedException { + VMThread.sleep(millis, nanos); + } + + /** + * Starts the new Thread of execution. The <code>run()</code> method of + * the receiver will be called by the receiver Thread itself (and not the + * Thread calling <code>start()</code>). + * + * @throws IllegalThreadStateException - if this thread has already started. + * @see Thread#run + */ + public synchronized void start() { + checkNotStarted(); + + hasBeenStarted = true; + + VMThread.create(this, stackSize); + } + + /** + * Requests the receiver Thread to stop and throw ThreadDeath. The Thread is + * resumed if it was suspended and awakened if it was sleeping, so that it + * can proceed to throw ThreadDeath. + * + * @deprecated because stopping a thread in this manner is unsafe and can + * leave your application and the VM in an unpredictable state. + */ + @Deprecated + public final void stop() { + stop(new ThreadDeath()); + } + + /** + * Throws {@code UnsupportedOperationException}. + * @deprecated because stopping a thread in this manner is unsafe and can + * leave your application and the VM in an unpredictable state. + */ + @Deprecated + public final synchronized void stop(Throwable throwable) { + throw new UnsupportedOperationException(); + } + + /** + * Throws {@code UnsupportedOperationException}. + * @deprecated May cause deadlocks. + */ + @Deprecated + public final void suspend() { + throw new UnsupportedOperationException(); + } + + /** + * Returns a string containing a concise, human-readable description of the + * Thread. It includes the Thread's name, priority, and group name. + * + * @return a printable representation for the receiver. + */ + @Override + public String toString() { + return "Thread[" + name + "," + priority + "," + group.getName() + "]"; + } + + /** + * Causes the calling Thread to yield execution time to another Thread that + * is ready to run. The actual scheduling is implementation-dependent. + */ + public static void yield() { + VMThread.yield(); + } + + /** + * Indicates whether the current Thread has a monitor lock on the specified + * object. + * + * @param object the object to test for the monitor lock + * @return true if the current thread has a monitor lock on the specified + * object; false otherwise + */ + public static boolean holdsLock(Object object) { + return currentThread().vmThread.holdsLock(object); + } + + /** + * Implemented by objects that want to handle cases where a thread is being + * terminated by an uncaught exception. Upon such termination, the handler + * is notified of the terminating thread and causal exception. If there is + * no explicit handler set then the thread's group is the default handler. + */ + public static interface UncaughtExceptionHandler { + /** + * The thread is being terminated by an uncaught exception. Further + * exceptions thrown in this method are prevent the remainder of the + * method from executing, but are otherwise ignored. + * + * @param thread the thread that has an uncaught exception + * @param ex the exception that was thrown + */ + void uncaughtException(Thread thread, Throwable ex); + } + + /** + * Unparks this thread. This unblocks the thread it if it was + * previously parked, or indicates that the thread is "preemptively + * unparked" if it wasn't already parked. The latter means that the + * next time the thread is told to park, it will merely clear its + * latent park bit and carry on without blocking. + * + * <p>See {@link java.util.concurrent.locks.LockSupport} for more + * in-depth information of the behavior of this method.</p> + * + * @hide for Unsafe + */ + public void unpark() { + VMThread vmt = vmThread; + + if (vmt == null) { + /* + * vmThread is null before the thread is start()ed. In + * this case, we just go ahead and set the state to + * PREEMPTIVELY_UNPARKED. Since this happens before the + * thread is started, we don't have to worry about + * synchronizing with it. + */ + parkState = ParkState.PREEMPTIVELY_UNPARKED; + return; + } + + synchronized (vmt) { + switch (parkState) { + case ParkState.PREEMPTIVELY_UNPARKED: { + /* + * Nothing to do in this case: By definition, a + * preemptively unparked thread is to remain in + * the preemptively unparked state if it is told + * to unpark. + */ + break; + } + case ParkState.UNPARKED: { + parkState = ParkState.PREEMPTIVELY_UNPARKED; + break; + } + default /*parked*/: { + parkState = ParkState.UNPARKED; + vmt.notifyAll(); + break; + } + } + } + } + + /** + * Parks the current thread for a particular number of nanoseconds, or + * indefinitely. If not indefinitely, this method unparks the thread + * after the given number of nanoseconds if no other thread unparks it + * first. If the thread has been "preemptively unparked," this method + * cancels that unparking and returns immediately. This method may + * also return spuriously (that is, without the thread being told to + * unpark and without the indicated amount of time elapsing). + * + * <p>See {@link java.util.concurrent.locks.LockSupport} for more + * in-depth information of the behavior of this method.</p> + * + * <p>This method must only be called when <code>this</code> is the current + * thread. + * + * @param nanos number of nanoseconds to park for or <code>0</code> + * to park indefinitely + * @throws IllegalArgumentException thrown if <code>nanos < 0</code> + * + * @hide for Unsafe + */ + public void parkFor(long nanos) { + VMThread vmt = vmThread; + + if (vmt == null) { + // Running threads should always have an associated vmThread. + throw new AssertionError(); + } + + synchronized (vmt) { + switch (parkState) { + case ParkState.PREEMPTIVELY_UNPARKED: { + parkState = ParkState.UNPARKED; + break; + } + case ParkState.UNPARKED: { + long millis = nanos / NANOS_PER_MILLI; + nanos %= NANOS_PER_MILLI; + + parkState = ParkState.PARKED; + try { + vmt.wait(millis, (int) nanos); + } catch (InterruptedException ex) { + interrupt(); + } finally { + /* + * Note: If parkState manages to become + * PREEMPTIVELY_UNPARKED before hitting this + * code, it should left in that state. + */ + if (parkState == ParkState.PARKED) { + parkState = ParkState.UNPARKED; + } + } + break; + } + default /*parked*/: { + throw new AssertionError( + "shouldn't happen: attempt to repark"); + } + } + } + } + + /** + * Parks the current thread until the specified system time. This + * method attempts to unpark the current thread immediately after + * <code>System.currentTimeMillis()</code> reaches the specified + * value, if no other thread unparks it first. If the thread has + * been "preemptively unparked," this method cancels that + * unparking and returns immediately. This method may also return + * spuriously (that is, without the thread being told to unpark + * and without the indicated amount of time elapsing). + * + * <p>See {@link java.util.concurrent.locks.LockSupport} for more + * in-depth information of the behavior of this method.</p> + * + * <p>This method must only be called when <code>this</code> is the + * current thread. + * + * @param time the time after which the thread should be unparked, + * in absolute milliseconds-since-the-epoch + * + * @hide for Unsafe + */ + public void parkUntil(long time) { + VMThread vmt = vmThread; + + if (vmt == null) { + // Running threads should always have an associated vmThread. + throw new AssertionError(); + } + + synchronized (vmt) { + /* + * Note: This conflates the two time bases of "wall clock" + * time and "monotonic uptime" time. However, given that + * the underlying system can only wait on monotonic time, + * it is unclear if there is any way to avoid the + * conflation. The downside here is that if, having + * calculated the delay, the wall clock gets moved ahead, + * this method may not return until well after the wall + * clock has reached the originally designated time. The + * reverse problem (the wall clock being turned back) + * isn't a big deal, since this method is allowed to + * spuriously return for any reason, and this situation + * can safely be construed as just such a spurious return. + */ + long delayMillis = time - System.currentTimeMillis(); + + if (delayMillis <= 0) { + parkState = ParkState.UNPARKED; + } else { + parkFor(delayMillis * NANOS_PER_MILLI); + } + } + } +} diff --git a/libdvm/src/main/java/java/lang/ThreadGroup.java b/libdvm/src/main/java/java/lang/ThreadGroup.java new file mode 100644 index 0000000..e99e99f --- /dev/null +++ b/libdvm/src/main/java/java/lang/ThreadGroup.java @@ -0,0 +1,726 @@ +/* + * 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.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import libcore.util.CollectionUtils; + +/** + * {@code ThreadGroup} is a means of organizing threads into a hierarchical structure. + * This class is obsolete. See <i>Effective Java</i> Item 73, "Avoid thread groups" for details. + * @see Thread + */ +public class ThreadGroup implements Thread.UncaughtExceptionHandler { + + // Name of this ThreadGroup + // VM needs this field name for debugging. + private String name; + + // Maximum priority for Threads inside this ThreadGroup + private int maxPriority = Thread.MAX_PRIORITY; + + // The ThreadGroup to which this ThreadGroup belongs + // VM needs this field name for debugging. + final ThreadGroup parent; + + /** + * Weak references to the threads in this group. + * Access is guarded by synchronizing on this field. + */ + private final List<WeakReference<Thread>> threadRefs = new ArrayList<WeakReference<Thread>>(5); + + /** + * View of the threads. + * Access is guarded by synchronizing on threadRefs. + */ + private final Iterable<Thread> threads = CollectionUtils.dereferenceIterable(threadRefs, true); + + /** + * Thread groups. Access is guarded by synchronizing on this field. + */ + private final List<ThreadGroup> groups = new ArrayList<ThreadGroup>(3); + + // Whether this ThreadGroup is a daemon ThreadGroup or not + private boolean isDaemon; + + // Whether this ThreadGroup has already been destroyed or not + private boolean isDestroyed; + + /* the VM uses these directly; do not rename */ + static final ThreadGroup mSystem = new ThreadGroup(); + static final ThreadGroup mMain = new ThreadGroup(mSystem, "main"); + + /** + * Constructs a new {@code ThreadGroup} with the given name. The new {@code ThreadGroup} + * will be child of the {@code ThreadGroup} to which the calling thread belongs. + * + * @param name the name + * @see Thread#currentThread + */ + public ThreadGroup(String name) { + this(Thread.currentThread().getThreadGroup(), name); + } + + /** + * Constructs a new {@code ThreadGroup} with the given name, as a child of the + * given {@code ThreadGroup}. + * + * @param parent the parent + * @param name the name + * @throws NullPointerException if {@code parent == null} + * @throws IllegalThreadStateException if {@code parent} has been + * destroyed already + */ + public ThreadGroup(ThreadGroup parent, String name) { + if (parent == null) { + throw new NullPointerException("parent == null"); + } + this.name = name; + this.parent = parent; + if (parent != null) { + parent.add(this); + this.setMaxPriority(parent.getMaxPriority()); + if (parent.isDaemon()) { + this.setDaemon(true); + } + } + } + + /** + * Initialize the special "system" ThreadGroup. Was "main" in Harmony, + * but we have an additional group above that in Android. + */ + private ThreadGroup() { + this.name = "system"; + this.parent = null; + } + + /** + * Returns the number of running {@code Thread}s which are children of this thread group, + * directly or indirectly. + * + * @return the number of children + */ + public int activeCount() { + int count = 0; + synchronized (threadRefs) { + for (Thread thread : threads) { + if (thread.isAlive()) { + count++; + } + } + } + synchronized (groups) { + for (ThreadGroup group : groups) { + count += group.activeCount(); + } + } + return count; + } + + /** + * Returns the number of {@code ThreadGroup}s which are children of this group, + * directly or indirectly. + * + * @return the number of children + */ + public int activeGroupCount() { + int count = 0; + synchronized (groups) { + for (ThreadGroup group : groups) { + // One for this group & the subgroups + count += 1 + group.activeGroupCount(); + } + } + return count; + } + + /** + * Adds a {@code ThreadGroup} to this thread group. + * + * @param g ThreadGroup to add + * @throws IllegalThreadStateException if this group has been destroyed already + */ + private void add(ThreadGroup g) throws IllegalThreadStateException { + synchronized (groups) { + if (isDestroyed) { + throw new IllegalThreadStateException(); + } + groups.add(g); + } + } + + /** + * Does nothing. The definition of this method depends on the deprecated + * method {@link #suspend()}. The exact behavior of this call was never + * specified. + * + * @param b Used to control low memory implicit suspension + * @return {@code true} (always) + * + * @deprecated Required deprecated method suspend(). + */ + @Deprecated + public boolean allowThreadSuspension(boolean b) { + // Does not apply to this VM, no-op + return true; + } + + /** + * Does nothing. + */ + public final void checkAccess() { + } + + /** + * Destroys this thread group and recursively all its subgroups. It is only legal + * to destroy a {@code ThreadGroup} that has no threads in it. Any daemon + * {@code ThreadGroup} is destroyed automatically when it becomes empty (no threads + * or thread groups in it). + * + * @throws IllegalThreadStateException if this thread group or any of its + * subgroups has been destroyed already or if it still contains + * threads. + */ + public final void destroy() { + synchronized (threadRefs) { + synchronized (groups) { + if (isDestroyed) { + throw new IllegalThreadStateException( + "Thread group was already destroyed: " + + (this.name != null ? this.name : "n/a")); + } + if (threads.iterator().hasNext()) { + throw new IllegalThreadStateException( + "Thread group still contains threads: " + + (this.name != null ? this.name : "n/a")); + } + // Call recursively for subgroups + while (!groups.isEmpty()) { + // We always get the first element - remember, when the + // child dies it removes itself from our collection. See + // below. + groups.get(0).destroy(); + } + + if (parent != null) { + parent.remove(this); + } + + // Now that the ThreadGroup is really destroyed it can be tagged as so + this.isDestroyed = true; + } + } + } + + /* + * Auxiliary method that destroys this thread group and recursively all its + * subgroups if this is a daemon ThreadGroup. + * + * @see #destroy + * @see #setDaemon + * @see #isDaemon + */ + private void destroyIfEmptyDaemon() { + // Has to be non-destroyed daemon to make sense + synchronized (threadRefs) { + if (isDaemon && !isDestroyed && !threads.iterator().hasNext()) { + synchronized (groups) { + if (groups.isEmpty()) { + destroy(); + } + } + } + } + } + + /** + * Iterates over all active threads in this group (and its sub-groups) and + * stores the threads in the given array. Returns when the array is full or + * no more threads remain, whichever happens first. + * + * <p>Note that this method will silently ignore any threads that don't fit in the + * supplied array. + * + * @param threads the array into which the {@code Thread}s will be copied + * @return the number of {@code Thread}s that were copied + */ + public int enumerate(Thread[] threads) { + return enumerate(threads, true); + } + + /** + * Iterates over all active threads in this group (and, optionally, its + * sub-groups) and stores the threads in the given array. Returns when the + * array is full or no more threads remain, whichever happens first. + * + * <p>Note that this method will silently ignore any threads that don't fit in the + * supplied array. + * + * @param threads the array into which the {@code Thread}s will be copied + * @param recurse indicates whether {@code Thread}s in subgroups should be + * recursively copied as well + * @return the number of {@code Thread}s that were copied + */ + public int enumerate(Thread[] threads, boolean recurse) { + return enumerateGeneric(threads, recurse, 0, true); + } + + /** + * Iterates over all thread groups in this group (and its sub-groups) and + * and stores the groups in the given array. Returns when the array is full + * or no more groups remain, whichever happens first. + * + * <p>Note that this method will silently ignore any thread groups that don't fit in the + * supplied array. + * + * @param groups the array into which the {@code ThreadGroup}s will be copied + * @return the number of {@code ThreadGroup}s that were copied + */ + public int enumerate(ThreadGroup[] groups) { + return enumerate(groups, true); + } + + /** + * Iterates over all thread groups in this group (and, optionally, its + * sub-groups) and stores the groups in the given array. Returns when + * the array is full or no more groups remain, whichever happens first. + * + * <p>Note that this method will silently ignore any thread groups that don't fit in the + * supplied array. + * + * @param groups the array into which the {@code ThreadGroup}s will be copied + * @param recurse indicates whether {@code ThreadGroup}s in subgroups should be + * recursively copied as well or not + * @return the number of {@code ThreadGroup}s that were copied + */ + public int enumerate(ThreadGroup[] groups, boolean recurse) { + return enumerateGeneric(groups, recurse, 0, false); + } + + /** + * Copies into <param>enumeration</param> starting at + * <param>enumerationIndex</param> all Threads or ThreadGroups in the + * receiver. If <param>recurse</param> is true, recursively enumerate the + * elements in subgroups. + * + * If the array passed as parameter is too small no exception is thrown - + * the extra elements are simply not copied. + * + * @param enumeration array into which the elements will be copied + * @param recurse Indicates whether subgroups should be enumerated or not + * @param enumerationIndex Indicates in which position of the enumeration + * array we are + * @param enumeratingThreads Indicates whether we are enumerating Threads or + * ThreadGroups + * @return How many elements were enumerated/copied over + */ + private int enumerateGeneric(Object[] enumeration, boolean recurse, int enumerationIndex, + boolean enumeratingThreads) { + if (enumeratingThreads) { + synchronized (threadRefs) { + // walk the references directly so we can iterate in reverse order + for (int i = threadRefs.size() - 1; i >= 0; --i) { + Thread thread = threadRefs.get(i).get(); + if (thread != null && thread.isAlive()) { + if (enumerationIndex >= enumeration.length) { + return enumerationIndex; + } + enumeration[enumerationIndex++] = thread; + } + } + } + } else { + synchronized (groups) { + for (int i = groups.size() - 1; i >= 0; --i) { + if (enumerationIndex >= enumeration.length) { + return enumerationIndex; + } + enumeration[enumerationIndex++] = groups.get(i); + } + } + } + + if (recurse) { + synchronized (groups) { + for (ThreadGroup group : groups) { + if (enumerationIndex >= enumeration.length) { + return enumerationIndex; + } + enumerationIndex = group.enumerateGeneric(enumeration, recurse, + enumerationIndex, enumeratingThreads); + } + } + } + return enumerationIndex; + } + + /** + * Returns the maximum allowed priority for a {@code Thread} in this thread group. + * + * @return the maximum priority + * + * @see #setMaxPriority + */ + public final int getMaxPriority() { + return maxPriority; + } + + /** + * Returns the name of this thread group. + * + * @return the group's name + */ + public final String getName() { + return name; + } + + /** + * Returns this thread group's parent {@code ThreadGroup}. It can be null if this + * is the the root ThreadGroup. + * + * @return the parent + */ + public final ThreadGroup getParent() { + return parent; + } + + /** + * Interrupts every {@code Thread} in this group and recursively in all its + * subgroups. + * + * @see Thread#interrupt + */ + public final void interrupt() { + synchronized (threadRefs) { + for (Thread thread : threads) { + thread.interrupt(); + } + } + synchronized (groups) { + for (ThreadGroup group : groups) { + group.interrupt(); + } + } + } + + /** + * Checks whether this thread group is a daemon {@code ThreadGroup}. + * + * @return true if this thread group is a daemon {@code ThreadGroup} + * + * @see #setDaemon + * @see #destroy + */ + public final boolean isDaemon() { + return isDaemon; + } + + /** + * Checks whether this thread group has already been destroyed. + * + * @return true if this thread group has already been destroyed + * @see #destroy + */ + public synchronized boolean isDestroyed() { + return isDestroyed; + } + + /** + * Outputs to {@code System.out} a text representation of the + * hierarchy of {@code Thread}s and {@code ThreadGroup}s in this thread group (and recursively). + * Proper indentation is used to show the nesting of groups inside groups + * and threads inside groups. + */ + public void list() { + // We start in a fresh line + System.out.println(); + list(0); + } + + /* + * Outputs to {@code System.out}a text representation of the + * hierarchy of Threads and ThreadGroups in this thread group (and recursively). + * The indentation will be four spaces per level of nesting. + * + * @param levels How many levels of nesting, so that proper indentation can + * be output. + */ + private void list(int levels) { + indent(levels); + System.out.println(this.toString()); + + ++levels; + synchronized (threadRefs) { + for (Thread thread : threads) { + indent(levels); + System.out.println(thread); + } + } + synchronized (groups) { + for (ThreadGroup group : groups) { + group.list(levels); + } + } + } + + private void indent(int levels) { + for (int i = 0; i < levels; i++) { + System.out.print(" "); // 4 spaces for each level + } + } + + /** + * Checks whether this thread group is a direct or indirect parent group of a + * given {@code ThreadGroup}. + * + * @param g the potential child {@code ThreadGroup} + * @return true if this thread group is parent of {@code g} + */ + public final boolean parentOf(ThreadGroup g) { + while (g != null) { + if (this == g) { + return true; + } + g = g.parent; + } + return false; + } + + /** + * Removes an immediate subgroup. + * + * @param g ThreadGroup to remove + * + * @see #add(Thread) + * @see #add(ThreadGroup) + */ + private void remove(ThreadGroup g) { + synchronized (groups) { + for (Iterator<ThreadGroup> i = groups.iterator(); i.hasNext(); ) { + ThreadGroup threadGroup = i.next(); + if (threadGroup.equals(g)) { + i.remove(); + break; + } + } + } + destroyIfEmptyDaemon(); + } + + /** + * Resumes every thread in this group and recursively in all its + * subgroups. + * + * @see Thread#resume + * @see #suspend + * + * @deprecated Requires deprecated method Thread.resume(). + */ + @SuppressWarnings("deprecation") + @Deprecated + public final void resume() { + synchronized (threadRefs) { + for (Thread thread : threads) { + thread.resume(); + } + } + synchronized (groups) { + for (ThreadGroup group : groups) { + group.resume(); + } + } + } + + /** + * Sets whether this is a daemon {@code ThreadGroup} or not. Daemon + * thread groups are automatically destroyed when they become empty. + * + * @param isDaemon the new value + * @see #isDaemon + * @see #destroy + */ + public final void setDaemon(boolean isDaemon) { + this.isDaemon = isDaemon; + } + + /** + * Configures the maximum allowed priority for a {@code Thread} in this group and + * recursively in all its subgroups. + * + * <p>A caller can never increase the maximum priority of a thread group. + * Such an attempt will not result in an exception, it will + * simply leave the thread group with its current maximum priority. + * + * @param newMax the new maximum priority to be set + * + * @throws IllegalArgumentException if the new priority is greater than + * Thread.MAX_PRIORITY or less than Thread.MIN_PRIORITY + * + * @see #getMaxPriority + */ + public final void setMaxPriority(int newMax) { + if (newMax <= this.maxPriority) { + if (newMax < Thread.MIN_PRIORITY) { + newMax = Thread.MIN_PRIORITY; + } + + int parentPriority = parent == null ? newMax : parent.getMaxPriority(); + this.maxPriority = parentPriority <= newMax ? parentPriority : newMax; + synchronized (groups) { + for (ThreadGroup group : groups) { + group.setMaxPriority(newMax); + } + } + } + } + + /** + * Stops every thread in this group and recursively in all its subgroups. + * + * @see Thread#stop() + * @see Thread#stop(Throwable) + * @see ThreadDeath + * + * @deprecated Requires deprecated method Thread.stop(). + */ + @SuppressWarnings("deprecation") + @Deprecated + public final void stop() { + if (stopHelper()) { + Thread.currentThread().stop(); + } + } + + @SuppressWarnings("deprecation") + private boolean stopHelper() { + boolean stopCurrent = false; + synchronized (threadRefs) { + Thread current = Thread.currentThread(); + for (Thread thread : threads) { + if (thread == current) { + stopCurrent = true; + } else { + thread.stop(); + } + } + } + synchronized (groups) { + for (ThreadGroup group : groups) { + stopCurrent |= group.stopHelper(); + } + } + return stopCurrent; + } + + /** + * Suspends every thread in this group and recursively in all its + * subgroups. + * + * @see Thread#suspend + * @see #resume + * + * @deprecated Requires deprecated method Thread.suspend(). + */ + @SuppressWarnings("deprecation") + @Deprecated + public final void suspend() { + if (suspendHelper()) { + Thread.currentThread().suspend(); + } + } + + @SuppressWarnings("deprecation") + private boolean suspendHelper() { + boolean suspendCurrent = false; + synchronized (threadRefs) { + Thread current = Thread.currentThread(); + for (Thread thread : threads) { + if (thread == current) { + suspendCurrent = true; + } else { + thread.suspend(); + } + } + } + synchronized (groups) { + for (ThreadGroup group : groups) { + suspendCurrent |= group.suspendHelper(); + } + } + return suspendCurrent; + } + + @Override + public String toString() { + return getClass().getName() + "[name=" + getName() + + ",maxPriority=" + getMaxPriority() + "]"; + } + + /** + * Handles uncaught exceptions. Any uncaught exception in any {@code Thread} + * is forwarded to the thread's {@code ThreadGroup} by invoking this + * method. + * + * <p>New code should use {@link Thread#setUncaughtExceptionHandler} instead of thread groups. + * + * @param t the Thread that terminated with an uncaught exception + * @param e the uncaught exception itself + */ + public void uncaughtException(Thread t, Throwable e) { + if (parent != null) { + parent.uncaughtException(t, e); + } else if (Thread.getDefaultUncaughtExceptionHandler() != null) { + // TODO The spec is unclear regarding this. What do we do? + Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, e); + } else if (!(e instanceof ThreadDeath)) { + // No parent group, has to be 'system' Thread Group + e.printStackTrace(System.err); + } + } + + /** + * Called by the Thread constructor. + */ + final void addThread(Thread thread) throws IllegalThreadStateException { + synchronized (threadRefs) { + if (isDestroyed) { + throw new IllegalThreadStateException(); + } + threadRefs.add(new WeakReference<Thread>(thread)); + } + } + + /** + * Called by the VM when a Thread dies. + */ + final void removeThread(Thread thread) throws IllegalThreadStateException { + synchronized (threadRefs) { + for (Iterator<Thread> i = threads.iterator(); i.hasNext(); ) { + if (i.next().equals(thread)) { + i.remove(); + break; + } + } + } + destroyIfEmptyDaemon(); + } +} diff --git a/libdvm/src/main/java/java/lang/VMClassLoader.java b/libdvm/src/main/java/java/lang/VMClassLoader.java new file mode 100644 index 0000000..d7162c6 --- /dev/null +++ b/libdvm/src/main/java/java/lang/VMClassLoader.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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. + */ + +package java.lang; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +class VMClassLoader { + + /** + * Get a resource from a file in the bootstrap class path. + * + * It would be simpler to just walk through the class path elements + * ourselves, but that would require reopening Jar files. + * + * We assume that the bootclasspath can't change once the VM has + * started. This assumption seems to be supported by the spec. + */ + static URL getResource(String name) { + int numEntries = getBootClassPathSize(); + for (int i = 0; i < numEntries; i++) { + String urlStr = getBootClassPathResource(name, i); + if (urlStr != null) { + try { + return new URL(urlStr); + } catch (MalformedURLException mue) { + mue.printStackTrace(); + // unexpected; keep going + } + } + } + return null; + } + + /* + * Get an enumeration with all matching resources. + */ + static List<URL> getResources(String name) { + ArrayList<URL> list = new ArrayList<URL>(); + int numEntries = getBootClassPathSize(); + for (int i = 0; i < numEntries; i++) { + String urlStr = getBootClassPathResource(name, i); + if (urlStr != null) { + try { + list.add(new URL(urlStr)); + } catch (MalformedURLException mue) { + mue.printStackTrace(); + // unexpected; keep going + } + } + } + return list; + } + + /** + * Load class with bootstrap class loader. + */ + native static Class loadClass(String name, boolean resolve) throws ClassNotFoundException; + + native static Class getPrimitiveClass(char type); + + native static Class findLoadedClass(ClassLoader cl, String name); + + /** + * Boot class path manipulation, for getResources(). + */ + native private static int getBootClassPathSize(); + native private static String getBootClassPathResource(String name, int index); +} diff --git a/libdvm/src/main/java/java/lang/VMThread.java b/libdvm/src/main/java/java/lang/VMThread.java new file mode 100644 index 0000000..01f6ee3 --- /dev/null +++ b/libdvm/src/main/java/java/lang/VMThread.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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. + */ + +package java.lang; + +class VMThread { + Thread thread; + int vmData; + + VMThread(Thread t) { + thread = t; + } + + native static void create(Thread t, long stackSize); + + static native Thread currentThread(); + static native boolean interrupted(); + static native void sleep (long msec, int nsec) throws InterruptedException; + static native void yield(); + + native void interrupt(); + + native boolean isInterrupted(); + + /** + * Starts the VMThread (and thus the Java Thread) with the given + * stack size. + */ + void start(long stackSize) { + VMThread.create(thread, stackSize); + } + + /** + * Queries whether this Thread holds a monitor lock on the + * given object. + */ + native boolean holdsLock(Object object); + + native void setPriority(int newPriority); + native int getStatus(); + + /** + * Holds a mapping from native Thread statuses to Java one. Required for + * translating back the result of getStatus(). + */ + static final Thread.State[] STATE_MAP = new Thread.State[] { + Thread.State.TERMINATED, // ZOMBIE + Thread.State.RUNNABLE, // RUNNING + Thread.State.TIMED_WAITING, // TIMED_WAIT + Thread.State.BLOCKED, // MONITOR + Thread.State.WAITING, // WAIT + Thread.State.NEW, // INITIALIZING + Thread.State.NEW, // STARTING + Thread.State.RUNNABLE, // NATIVE + Thread.State.WAITING, // VMWAIT + Thread.State.RUNNABLE // SUSPENDED + }; + + /** + * Tell the VM that the thread's name has changed. This is useful for + * DDMS, which would otherwise be oblivious to Thread.setName calls. + */ + native void nameChanged(String newName); +} diff --git a/libdvm/src/main/java/java/lang/reflect/AccessibleObject.java b/libdvm/src/main/java/java/lang/reflect/AccessibleObject.java new file mode 100644 index 0000000..9c6b8c7 --- /dev/null +++ b/libdvm/src/main/java/java/lang/reflect/AccessibleObject.java @@ -0,0 +1,320 @@ +/* + * 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. + */ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package java.lang.reflect; + +import java.lang.annotation.Annotation; +import java.util.Hashtable; +import org.apache.harmony.kernel.vm.StringUtils; + +/** + * {@code AccessibleObject} is the superclass of all member reflection classes + * (Field, Constructor, Method). AccessibleObject provides the ability to toggle + * a flag controlling access checks for these objects. By default, accessing a + * member (for example, setting a field or invoking a method) checks the + * validity of the access (for example, invoking a private method from outside + * the defining class is prohibited) and throws IllegalAccessException if the + * operation is not permitted. If the accessible flag is set to true, these + * checks are omitted. This allows privileged code, such as Java object + * serialization, object inspectors, and debuggers to have complete access to + * objects. + * + * @see Field + * @see Constructor + * @see Method + */ +public class AccessibleObject implements AnnotatedElement { + + // If true, object is accessible, bypassing normal access checks + boolean flag = false; + + // Holds a mapping from Java type names to native type codes. + static Hashtable<String, String> trans; + + static { + trans = new Hashtable<String, String>(9); + trans.put("byte", "B"); + trans.put("char", "C"); + trans.put("short", "S"); + trans.put("int", "I"); + trans.put("long", "J"); + trans.put("float", "F"); + trans.put("double", "D"); + trans.put("void", "V"); + trans.put("boolean", "Z"); + } + + /** + * Attempts to set the value of the accessible flag for all the objects in + * the array provided. Setting this + * flag to {@code false} will enable access checks, setting to {@code true} + * will disable them. + * + * @param objects + * the accessible objects + * @param flag + * the new value for the accessible flag + * + * @see #setAccessible(boolean) + */ + public static void setAccessible(AccessibleObject[] objects, boolean flag) { + synchronized(AccessibleObject.class) { + for (AccessibleObject object : objects) { + object.flag = flag; + } + } + } + + /** + * Constructs a new {@code AccessibleObject} instance. {@code + * AccessibleObject} instances can only be constructed by the virtual + * machine. + */ + protected AccessibleObject() { + } + + /** + * Indicates whether this object is accessible without access checks being + * performed. Returns the accessible flag. + * + * @return {@code true} if this object is accessible without access + * checks, {@code false} otherwise + */ + public boolean isAccessible() { + return flag; + } + + /** + * Attempts to set the value of the accessible flag. Setting this flag to + * {@code false} will enable access checks, setting to {@code true} will + * disable them. + * + * @param flag + * the new value for the accessible flag + */ + public void setAccessible(boolean flag) { + this.flag = flag; + } + + public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) { + throw new UnsupportedOperationException(); + } + + public Annotation[] getDeclaredAnnotations() { + throw new UnsupportedOperationException(); + } + + public Annotation[] getAnnotations() { + // for all but Class, getAnnotations == getDeclaredAnnotations + return getDeclaredAnnotations(); + } + + public <T extends Annotation> T getAnnotation(Class<T> annotationType) { + throw new UnsupportedOperationException(); + } + + /** + * Returns the signature for a class. This is the kind of signature used + * internally by the JVM, with one-character codes representing the basic + * types. It is not suitable for printing. + * + * @param clazz + * the class for which a signature is required + * + * @return The signature as a string + */ + String getSignature(Class<?> clazz) { + String result = ""; + String nextType = clazz.getName(); + + if(trans.containsKey(nextType)) { + result = trans.get(nextType); + } else { + if(clazz.isArray()) { + result = "[" + getSignature(clazz.getComponentType()); + } else { + result = "L" + nextType + ";"; + } + } + return result; + } + + /** + * Returns a printable String consisting of the canonical names of the + * classes contained in an array. The form is that used in parameter and + * exception lists, that is, the class or type names are separated by + * commas. + * + * @param types + * the array of classes + * + * @return The String of names + */ + String toString(Class<?>[] types) { + StringBuilder result = new StringBuilder(); + + if (types.length != 0) { + appendTypeName(result, types[0]); + for (int i = 1; i < types.length; i++) { + result.append(','); + appendTypeName(result, types[i]); + } + } + + return result.toString(); + } + + /** + * Gets the Signature attribute for this instance. Returns {@code null} + * if not found. + */ + /*package*/ String getSignatureAttribute() { + /* + * Note: This method would have been declared abstract, but the + * standard API lists this class as concrete. + */ + throw new UnsupportedOperationException(); + } + + /** + * Retrieve the signature attribute from an arbitrary class. This is + * the same as Class.getSignatureAttribute(), but it can be used from + * the java.lang.reflect package. + */ + /*package*/ static String getClassSignatureAttribute(Class clazz) { + Object[] annotation = getClassSignatureAnnotation(clazz); + + if (annotation == null) { + return null; + } + + return StringUtils.combineStrings(annotation); + } + + /** + * Retrieve the signature annotation from an arbitrary class. This is + * the same as Class.getSignatureAttribute(), but it can be used from + * the java.lang.reflect package. + */ + private static native Object[] getClassSignatureAnnotation(Class clazz); + + /** + * Appends the best {@link #toString} name for {@code c} to {@code out}. + * This works around the fact that {@link Class#getName} is lousy for + * primitive arrays (it writes "[C" instead of "char[]") and {@link + * Class#getCanonicalName()} is lousy for nested classes (it uses a "." + * separator rather than a "$" separator). + */ + void appendTypeName(StringBuilder out, Class<?> c) { + int dimensions = 0; + while (c.isArray()) { + c = c.getComponentType(); + dimensions++; + } + out.append(c.getName()); + for (int d = 0; d < dimensions; d++) { + out.append("[]"); + } + } + + /** + * Appends names of the specified array classes to the buffer. The array + * elements may represent a simple type, a reference type or an array type. + * Output format: java.lang.Object[], java.io.File, void + * + * @param types array of classes to print the names + * @throws NullPointerException if any of the arguments is null + */ + void appendArrayGenericType(StringBuilder sb, Type[] types) { + if (types.length > 0) { + appendGenericType(sb, types[0]); + for (int i = 1; i < types.length; i++) { + sb.append(','); + appendGenericType(sb, types[i]); + } + } + } + + /** + * Appends the generic type representation to the buffer. + * + * @param sb buffer + * @param obj the generic type which representation should be appended to the buffer + * + * @throws NullPointerException if any of the arguments is null + */ + void appendGenericType(StringBuilder sb, Type obj) { + if (obj instanceof TypeVariable) { + sb.append(((TypeVariable)obj).getName()); + } else if (obj instanceof ParameterizedType) { + sb.append(obj.toString()); + } else if (obj instanceof GenericArrayType) { //XXX: is it a working branch? + Type simplified = ((GenericArrayType)obj).getGenericComponentType(); + appendGenericType(sb, simplified); + sb.append("[]"); + } else if (obj instanceof Class) { + Class c = ((Class<?>)obj); + if (c.isArray()){ + String as[] = c.getName().split("\\["); + int len = as.length-1; + if (as[len].length() > 1){ + sb.append(as[len].substring(1, as[len].length()-1)); + } else { + char ch = as[len].charAt(0); + if (ch == 'I') + sb.append("int"); + else if (ch == 'B') + sb.append("byte"); + else if (ch == 'J') + sb.append("long"); + else if (ch == 'F') + sb.append("float"); + else if (ch == 'D') + sb.append("double"); + else if (ch == 'S') + sb.append("short"); + else if (ch == 'C') + sb.append("char"); + else if (ch == 'Z') + sb.append("boolean"); + else if (ch == 'V') //XXX: is it a working branch? + sb.append("void"); + } + for (int i = 0; i < len; i++){ + sb.append("[]"); + } + } else { + sb.append(c.getName()); + } + } + } +} diff --git a/libdvm/src/main/java/java/lang/reflect/Constructor.java b/libdvm/src/main/java/java/lang/reflect/Constructor.java new file mode 100644 index 0000000..9a0e03c --- /dev/null +++ b/libdvm/src/main/java/java/lang/reflect/Constructor.java @@ -0,0 +1,460 @@ +/* + * 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. + */ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package java.lang.reflect; + +import java.lang.annotation.Annotation; +import libcore.util.EmptyArray; +import org.apache.harmony.kernel.vm.StringUtils; +import org.apache.harmony.luni.lang.reflect.GenericSignatureParser; +import org.apache.harmony.luni.lang.reflect.ListOfTypes; +import org.apache.harmony.luni.lang.reflect.Types; + +/** + * This class represents a constructor. Information about the constructor can be + * accessed, and the constructor can be invoked dynamically. + * + * @param <T> the class that declares this constructor + */ +public final class Constructor<T> extends AccessibleObject implements GenericDeclaration, + Member { + + Class<T> declaringClass; + + Class<?>[] parameterTypes; + + Class<?>[] exceptionTypes; + + ListOfTypes genericExceptionTypes; + ListOfTypes genericParameterTypes; + TypeVariable<Constructor<T>>[] formalTypeParameters; + private volatile boolean genericTypesAreInitialized = false; + + private synchronized void initGenericTypes() { + if (!genericTypesAreInitialized) { + String signatureAttribute = getSignatureAttribute(); + GenericSignatureParser parser = new GenericSignatureParser( + declaringClass.getClassLoader()); + parser.parseForConstructor(this, signatureAttribute, exceptionTypes); + formalTypeParameters = parser.formalTypeParameters; + genericParameterTypes = parser.parameterTypes; + genericExceptionTypes = parser.exceptionTypes; + genericTypesAreInitialized = true; + } + } + + int slot; + + /** + * Prevent this class from being instantiated. + */ + private Constructor(){ + //do nothing + } + + /** + * Creates an instance of the class. Only called from native code, thus + * private. + * + * @param declaringClass + * the class this constructor object belongs to + * @param ptypes + * the parameter types of the constructor + * @param extypes + * the exception types of the constructor + * @param slot + * the slot of the constructor inside the VM class structure + */ + private Constructor (Class<T> declaringClass, Class<?>[] ptypes, Class<?>[] extypes, int slot){ + this.declaringClass = declaringClass; + this.parameterTypes = ptypes; + this.exceptionTypes = extypes; // may be null + this.slot = slot; + } + + @Override /*package*/ String getSignatureAttribute() { + Object[] annotation = Method.getSignatureAnnotation(declaringClass, slot); + + if (annotation == null) { + return null; + } + + return StringUtils.combineStrings(annotation); + } + + public TypeVariable<Constructor<T>>[] getTypeParameters() { + initGenericTypes(); + return formalTypeParameters.clone(); + } + + /** + * Returns the string representation of the constructor's declaration, + * including the type parameters. + * + * @return the string representation of the constructor's declaration + */ + public String toGenericString() { + StringBuilder sb = new StringBuilder(80); + initGenericTypes(); + // append modifiers if any + int modifier = getModifiers(); + if (modifier != 0) { + sb.append(Modifier.toString(modifier & ~Modifier.VARARGS)).append(' '); + } + // append type parameters + if (formalTypeParameters != null && formalTypeParameters.length > 0) { + sb.append('<'); + for (int i = 0; i < formalTypeParameters.length; i++) { + appendGenericType(sb, formalTypeParameters[i]); + if (i < formalTypeParameters.length - 1) { + sb.append(","); + } + } + sb.append("> "); + } + // append constructor name + appendTypeName(sb, getDeclaringClass()); + // append parameters + sb.append('('); + appendArrayGenericType(sb, + Types.getClonedTypeArray(genericParameterTypes)); + sb.append(')'); + // append exceptions if any + Type[] genericExceptionTypeArray = + Types.getClonedTypeArray(genericExceptionTypes); + if (genericExceptionTypeArray.length > 0) { + sb.append(" throws "); + appendArrayGenericType(sb, genericExceptionTypeArray); + } + return sb.toString(); + } + + /** + * Returns the generic parameter types as an array of {@code Type} + * instances, in declaration order. If this constructor has no generic + * parameters, an empty array is returned. + * + * @return the parameter types + * + * @throws GenericSignatureFormatError + * if the generic constructor signature is invalid + * @throws TypeNotPresentException + * if any parameter type points to a missing type + * @throws MalformedParameterizedTypeException + * if any parameter type points to a type that cannot be + * instantiated for some reason + */ + public Type[] getGenericParameterTypes() { + initGenericTypes(); + return Types.getClonedTypeArray(genericParameterTypes); + } + + /** + * Returns the exception types as an array of {@code Type} instances. If + * this constructor has no declared exceptions, an empty array will be + * returned. + * + * @return an array of generic exception types + * + * @throws GenericSignatureFormatError + * if the generic constructor signature is invalid + * @throws TypeNotPresentException + * if any exception type points to a missing type + * @throws MalformedParameterizedTypeException + * if any exception type points to a type that cannot be + * instantiated for some reason + */ + public Type[] getGenericExceptionTypes() { + initGenericTypes(); + return Types.getClonedTypeArray(genericExceptionTypes); + } + + @Override + public Annotation[] getDeclaredAnnotations() { + return Method.getDeclaredAnnotations(declaringClass, slot); + } + + @Override public <A extends Annotation> A getAnnotation(Class<A> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + return Method.getAnnotation(declaringClass, slot, annotationType); + } + + @Override public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + return Method.isAnnotationPresent(declaringClass, slot, annotationType); + } + + /** + * Returns an array of arrays that represent the annotations of the formal + * parameters of this constructor. If there are no parameters on this + * constructor, then an empty array is returned. If there are no annotations + * set, then an array of empty arrays is returned. + * + * @return an array of arrays of {@code Annotation} instances + */ + public Annotation[][] getParameterAnnotations() { + Annotation[][] parameterAnnotations + = Method.getParameterAnnotations(declaringClass, slot); + if (parameterAnnotations.length == 0) { + return Method.noAnnotations(parameterTypes.length); + } + return parameterAnnotations; + } + + /** + * Indicates whether or not this constructor takes a variable number of + * arguments. + * + * @return {@code true} if a vararg is declare, otherwise + * {@code false} + */ + public boolean isVarArgs() { + int mods = Method.getMethodModifiers(declaringClass, slot); + return (mods & Modifier.VARARGS) != 0; + } + + /** + * Indicates whether or not this constructor is synthetic (artificially + * introduced by the compiler). + * + * @return {@code true} if this constructor is synthetic, {@code false} + * otherwise + */ + public boolean isSynthetic() { + int mods = Method.getMethodModifiers(declaringClass, slot); + return (mods & Modifier.SYNTHETIC) != 0; + } + + /** + * Indicates whether or not the specified {@code object} is equal to this + * constructor. To be equal, the specified object must be an instance + * of {@code Constructor} with the same declaring class and parameter types + * as this constructor. + * + * @param object + * the object to compare + * + * @return {@code true} if the specified object is equal to this + * constructor, {@code false} otherwise + * + * @see #hashCode + */ + @Override + public boolean equals(Object object) { + return object instanceof Constructor && toString().equals(object.toString()); + } + + /** + * Returns the class that declares this constructor. + * + * @return the declaring class + */ + public Class<T> getDeclaringClass() { + return declaringClass; + } + + /** + * Returns the exception types as an array of {@code Class} instances. If + * this constructor has no declared exceptions, an empty array will be + * returned. + * + * @return the declared exception classes + */ + public Class<?>[] getExceptionTypes() { + if (exceptionTypes == null) { + return EmptyArray.CLASS; + } + return exceptionTypes.clone(); + } + + /** + * Returns the modifiers for this constructor. The {@link Modifier} class + * should be used to decode the result. + * + * @return the modifiers for this constructor + * + * @see Modifier + */ + public int getModifiers() { + return Method.getMethodModifiers(declaringClass, slot); + } + + /** + * Returns the name of this constructor. + * + * @return the name of this constructor + */ + public String getName() { + return declaringClass.getName(); + } + + /** + * Returns an array of the {@code Class} objects associated with the + * parameter types of this constructor. If the constructor was declared with + * no parameters, an empty array will be returned. + * + * @return the parameter types + */ + public Class<?>[] getParameterTypes() { + return parameterTypes.clone(); + } + + /** + * Returns the constructor's signature in non-printable form. This is called + * (only) from IO native code and needed for deriving the serialVersionUID + * of the class + * + * @return the constructor's signature + */ + @SuppressWarnings("unused") + private String getSignature() { + StringBuilder result = new StringBuilder(); + + result.append('('); + for (int i = 0; i < parameterTypes.length; i++) { + result.append(getSignature(parameterTypes[i])); + } + result.append(")V"); + + return result.toString(); + } + + /** + * Returns an integer hash code for this constructor. Constructors which are + * equal return the same value for this method. The hash code for a + * Constructor is the hash code of the name of the declaring class. + * + * @return the hash code + * + * @see #equals + */ + @Override + public int hashCode() { + return declaringClass.getName().hashCode(); + } + + /** + * Returns a new instance of the declaring class, initialized by dynamically + * invoking the constructor represented by this {@code Constructor} object. + * This reproduces the effect of {@code new declaringClass(arg1, arg2, ... , + * argN)} This method performs the following: + * <ul> + * <li>A new instance of the declaring class is created. If the declaring + * class cannot be instantiated (i.e. abstract class, an interface, an array + * type, or a primitive type) then an InstantiationException is thrown.</li> + * <li>If this Constructor object is enforcing access control (see + * {@link AccessibleObject}) and this constructor is not accessible from the + * current context, an IllegalAccessException is thrown.</li> + * <li>If the number of arguments passed and the number of parameters do not + * match, an IllegalArgumentException is thrown.</li> + * <li>For each argument passed: + * <ul> + * <li>If the corresponding parameter type is a primitive type, the argument + * is unboxed. If the unboxing fails, an IllegalArgumentException is + * thrown.</li> + * <li>If the resulting argument cannot be converted to the parameter type + * via a widening conversion, an IllegalArgumentException is thrown.</li> + * </ul> + * <li>The constructor represented by this {@code Constructor} object is + * then invoked. If an exception is thrown during the invocation, it is + * caught and wrapped in an InvocationTargetException. This exception is + * then thrown. If the invocation completes normally, the newly initialized + * object is returned. + * </ul> + * + * @param args + * the arguments to the constructor + * + * @return the new, initialized, object + * + * @exception InstantiationException + * if the class cannot be instantiated + * @exception IllegalAccessException + * if this constructor is not accessible + * @exception IllegalArgumentException + * if an incorrect number of arguments are passed, or an + * argument could not be converted by a widening conversion + * @exception InvocationTargetException + * if an exception was thrown by the invoked constructor + * + * @see AccessibleObject + */ + public T newInstance(Object... args) throws InstantiationException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException { + return constructNative (args, declaringClass, parameterTypes, slot, flag); + } + + private native T constructNative(Object[] args, Class<T> declaringClass, + Class<?>[] parameterTypes, int slot, + boolean noAccessCheck) throws InstantiationException, IllegalAccessException, + InvocationTargetException; + + /** + * Returns a string containing a concise, human-readable description of this + * constructor. The format of the string is: + * + * <ol> + * <li>modifiers (if any) + * <li>declaring class name + * <li>'(' + * <li>parameter types, separated by ',' (if any) + * <li>')' + * <li>'throws' plus exception types, separated by ',' (if any) + * </ol> + * + * For example: + * {@code public String(byte[],String) throws UnsupportedEncodingException} + * + * @return a printable representation for this constructor + */ + @Override + public String toString() { + StringBuilder result = new StringBuilder(Modifier.toString(getModifiers())); + + if (result.length() != 0) + result.append(' '); + result.append(declaringClass.getName()); + result.append("("); + result.append(toString(parameterTypes)); + result.append(")"); + if (exceptionTypes != null && exceptionTypes.length != 0) { + result.append(" throws "); + result.append(toString(exceptionTypes)); + } + + return result.toString(); + } +} diff --git a/libdvm/src/main/java/java/lang/reflect/Field.java b/libdvm/src/main/java/java/lang/reflect/Field.java new file mode 100644 index 0000000..0aacb11 --- /dev/null +++ b/libdvm/src/main/java/java/lang/reflect/Field.java @@ -0,0 +1,934 @@ +/* + * 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. + */ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package java.lang.reflect; + +import java.lang.annotation.Annotation; +import java.util.Comparator; +import org.apache.harmony.kernel.vm.StringUtils; +import org.apache.harmony.luni.lang.reflect.GenericSignatureParser; +import org.apache.harmony.luni.lang.reflect.Types; + +/** + * This class represents a field. Information about the field can be accessed, + * and the field's value can be accessed dynamically. + */ +public final class Field extends AccessibleObject implements Member { + + /** + * Orders fields by their name and declaring class. + * + * @hide + */ + public static final Comparator<Field> ORDER_BY_NAME_AND_DECLARING_CLASS + = new Comparator<Field>() { + @Override public int compare(Field a, Field b) { + int comparison = a.name.compareTo(b.name); + if (comparison != 0) { + return comparison; + } + + return a.getDeclaringClass().getName().compareTo(b.getDeclaringClass().getName()); + } + }; + + private Class<?> declaringClass; + + private Class<?> type; + + private Type genericType; + + private volatile boolean genericTypesAreInitialized = false; + + private String name; + + private int slot; + + private static final char TYPE_BOOLEAN = 'Z'; + + private static final char TYPE_BYTE = 'B'; + + private static final char TYPE_CHAR = 'C'; + + private static final char TYPE_SHORT = 'S'; + + private static final char TYPE_INTEGER = 'I'; + + private static final char TYPE_FLOAT = 'F'; + + private static final char TYPE_LONG = 'J'; + + private static final char TYPE_DOUBLE = 'D'; + + /** + * Construct a clone of the given instance. + * + * @param orig non-null; the original instance to clone + */ + /*package*/ Field(Field orig) { + this(orig.declaringClass, orig.type, orig.name, orig.slot); + + // Copy the accessible flag. + if (orig.flag) { + this.flag = true; + } + } + + private Field(Class<?> declaringClass, Class<?> type, String name, int slot) { + this.declaringClass = declaringClass; + this.type = type; + this.name = name; + this.slot = slot; + } + + private synchronized void initGenericType() { + if (!genericTypesAreInitialized) { + String signatureAttribute = getSignatureAttribute(); + GenericSignatureParser parser = new GenericSignatureParser( + declaringClass.getClassLoader()); + parser.parseForField(this.declaringClass, signatureAttribute); + genericType = parser.fieldType; + if (genericType == null) { + genericType = getType(); + } + genericTypesAreInitialized = true; + } + } + + /** {@inheritDoc} */ + @Override + /* package */String getSignatureAttribute() { + Object[] annotation = getSignatureAnnotation(declaringClass, slot); + + if (annotation == null) { + return null; + } + + return StringUtils.combineStrings(annotation); + } + + /** + * Get the Signature annotation for this field. Returns null if not found. + */ + native private Object[] getSignatureAnnotation(Class declaringClass, int slot); + + /** + * Indicates whether or not this field is synthetic. + * + * @return {@code true} if this field is synthetic, {@code false} otherwise + */ + public boolean isSynthetic() { + int flags = getFieldModifiers(declaringClass, slot); + return (flags & Modifier.SYNTHETIC) != 0; + } + + /** + * Returns the string representation of this field, including the field's + * generic type. + * + * @return the string representation of this field + */ + public String toGenericString() { + StringBuilder sb = new StringBuilder(80); + // append modifiers if any + int modifier = getModifiers(); + if (modifier != 0) { + sb.append(Modifier.toString(modifier)).append(' '); + } + // append generic type + appendGenericType(sb, getGenericType()); + sb.append(' '); + // append full field name + sb.append(getDeclaringClass().getName()).append('.').append(getName()); + return sb.toString(); + } + + /** + * Indicates whether or not this field is an enumeration constant. + * + * @return {@code true} if this field is an enumeration constant, {@code + * false} otherwise + */ + public boolean isEnumConstant() { + int flags = getFieldModifiers(declaringClass, slot); + return (flags & Modifier.ENUM) != 0; + } + + /** + * Returns the generic type of this field. + * + * @return the generic type + * @throws GenericSignatureFormatError + * if the generic field signature is invalid + * @throws TypeNotPresentException + * if the generic type points to a missing type + * @throws MalformedParameterizedTypeException + * if the generic type points to a type that cannot be + * instantiated for some reason + */ + public Type getGenericType() { + initGenericType(); + return Types.getType(genericType); + } + + @Override public Annotation[] getDeclaredAnnotations() { + return getDeclaredAnnotations(declaringClass, slot); + } + private static native Annotation[] getDeclaredAnnotations(Class declaringClass, int slot); + + @Override public <A extends Annotation> A getAnnotation(Class<A> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + return getAnnotation(declaringClass, slot, annotationType); + } + private static native <A extends Annotation> A getAnnotation( + Class<?> declaringClass, int slot, Class<A> annotationType); + + @Override public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + return isAnnotationPresent(declaringClass, slot, annotationType); + } + private static native boolean isAnnotationPresent( + Class<?> declaringClass, int slot, Class<? extends Annotation> annotationType); + + /** + * Indicates whether or not the specified {@code object} is equal to this + * field. To be equal, the specified object must be an instance of + * {@code Field} with the same declaring class, type and name as this field. + * + * @param object + * the object to compare + * @return {@code true} if the specified object is equal to this method, + * {@code false} otherwise + * @see #hashCode + */ + @Override + public boolean equals(Object object) { + return object instanceof Field && toString().equals(object.toString()); + } + + /** + * Returns the value of the field in the specified object. This reproduces + * the effect of {@code object.fieldName} + * + * <p>If the type of this field is a primitive type, the field value is + * automatically boxed. + * + * <p>If this field is static, the object argument is ignored. + * Otherwise, if the object is null, a NullPointerException is thrown. If + * the object is not an instance of the declaring class of the method, an + * IllegalArgumentException is thrown. + * + * <p>If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * + * @param object + * the object to access + * @return the field value, possibly boxed + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public Object get(Object object) throws IllegalAccessException, IllegalArgumentException { + return getField(object, declaringClass, type, slot, flag); + } + + /** + * Returns the value of the field in the specified object as a {@code + * boolean}. This reproduces the effect of {@code object.fieldName} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * + * @param object + * the object to access + * @return the field value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public boolean getBoolean(Object object) throws IllegalAccessException, + IllegalArgumentException { + return getZField(object, declaringClass, type, slot, flag, TYPE_BOOLEAN); + } + + /** + * Returns the value of the field in the specified object as a {@code byte}. + * This reproduces the effect of {@code object.fieldName} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * + * @param object + * the object to access + * @return the field value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public byte getByte(Object object) throws IllegalAccessException, IllegalArgumentException { + return getBField(object, declaringClass, type, slot, flag, TYPE_BYTE); + } + + /** + * Returns the value of the field in the specified object as a {@code char}. + * This reproduces the effect of {@code object.fieldName} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * + * @param object + * the object to access + * @return the field value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public char getChar(Object object) throws IllegalAccessException, IllegalArgumentException { + return getCField(object, declaringClass, type, slot, flag, TYPE_CHAR); + } + + /** + * Returns the class that declares this field. + * + * @return the declaring class + */ + public Class<?> getDeclaringClass() { + return declaringClass; + } + + /** + * Returns the value of the field in the specified object as a {@code + * double}. This reproduces the effect of {@code object.fieldName} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * + * @param object + * the object to access + * @return the field value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public double getDouble(Object object) throws IllegalAccessException, IllegalArgumentException { + return getDField(object, declaringClass, type, slot, flag, TYPE_DOUBLE); + } + + /** + * Returns the value of the field in the specified object as a {@code float} + * . This reproduces the effect of {@code object.fieldName} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * + * @param object + * the object to access + * @return the field value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public float getFloat(Object object) throws IllegalAccessException, IllegalArgumentException { + return getFField(object, declaringClass, type, slot, flag, TYPE_FLOAT); + } + + /** + * Returns the value of the field in the specified object as an {@code int}. + * This reproduces the effect of {@code object.fieldName} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * + * @param object + * the object to access + * @return the field value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public int getInt(Object object) throws IllegalAccessException, IllegalArgumentException { + return getIField(object, declaringClass, type, slot, flag, TYPE_INTEGER); + } + + /** + * Returns the value of the field in the specified object as a {@code long}. + * This reproduces the effect of {@code object.fieldName} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * + * @param object + * the object to access + * @return the field value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public long getLong(Object object) throws IllegalAccessException, IllegalArgumentException { + return getJField(object, declaringClass, type, slot, flag, TYPE_LONG); + } + + /** + * Returns the modifiers for this field. The {@link Modifier} class should + * be used to decode the result. + * + * @return the modifiers for this field + * @see Modifier + */ + public int getModifiers() { + return getFieldModifiers(declaringClass, slot); + } + + private native int getFieldModifiers(Class<?> declaringClass, int slot); + + /** + * Returns the name of this field. + * + * @return the name of this field + */ + public String getName() { + return name; + } + + /** + * Returns the value of the field in the specified object as a {@code short} + * . This reproduces the effect of {@code object.fieldName} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * + * @param object + * the object to access + * @return the field value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public short getShort(Object object) throws IllegalAccessException, IllegalArgumentException { + return getSField(object, declaringClass, type, slot, flag, TYPE_SHORT); + } + + /** + * Returns the constructor's signature in non-printable form. This is called + * (only) from IO native code and needed for deriving the serialVersionUID + * of the class + * + * @return the constructor's signature. + */ + @SuppressWarnings("unused") + private String getSignature() { + return getSignature(type); + } + + /** + * Return the {@link Class} associated with the type of this field. + * + * @return the type of this field + */ + public Class<?> getType() { + return type; + } + + /** + * Returns an integer hash code for this field. Objects which are equal + * return the same value for this method. + * <p> + * The hash code for a Field is the exclusive-or combination of the hash + * code of the field's name and the hash code of the name of its declaring + * class. + * + * @return the hash code for this field + * @see #equals + */ + @Override + public int hashCode() { + return name.hashCode() ^ getDeclaringClass().getName().hashCode(); + } + + /** + * Sets the value of the field in the specified object to the value. This + * reproduces the effect of {@code object.fieldName = value} + * + * <p>If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * + * <p>If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * + * <p>If the field type is a primitive type, the value is automatically + * unboxed. If the unboxing fails, an IllegalArgumentException is thrown. If + * the value cannot be converted to the field type via a widening + * conversion, an IllegalArgumentException is thrown. + * + * @param object + * the object to access + * @param value + * the new value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public void set(Object object, Object value) throws IllegalAccessException, + IllegalArgumentException { + setField(object, declaringClass, type, slot, flag, value); + } + + /** + * Sets the value of the field in the specified object to the {@code + * boolean} value. This reproduces the effect of {@code object.fieldName = + * value} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * <p> + * If the value cannot be converted to the field type via a widening + * conversion, an IllegalArgumentException is thrown. + * + * @param object + * the object to access + * @param value + * the new value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public void setBoolean(Object object, boolean value) throws IllegalAccessException, + IllegalArgumentException { + setZField(object, declaringClass, type, slot, flag, TYPE_BOOLEAN, value); + } + + /** + * Sets the value of the field in the specified object to the {@code byte} + * value. This reproduces the effect of {@code object.fieldName = value} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * <p> + * If the value cannot be converted to the field type via a widening + * conversion, an IllegalArgumentException is thrown. + * + * @param object + * the object to access + * @param value + * the new value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public void setByte(Object object, byte value) throws IllegalAccessException, + IllegalArgumentException { + setBField(object, declaringClass, type, slot, flag, TYPE_BYTE, value); + } + + /** + * Sets the value of the field in the specified object to the {@code char} + * value. This reproduces the effect of {@code object.fieldName = value} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * <p> + * If the value cannot be converted to the field type via a widening + * conversion, an IllegalArgumentException is thrown. + * + * @param object + * the object to access + * @param value + * the new value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public void setChar(Object object, char value) throws IllegalAccessException, + IllegalArgumentException { + setCField(object, declaringClass, type, slot, flag, TYPE_CHAR, value); + } + + /** + * Sets the value of the field in the specified object to the {@code double} + * value. This reproduces the effect of {@code object.fieldName = value} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * <p> + * If the value cannot be converted to the field type via a widening + * conversion, an IllegalArgumentException is thrown. + * + * @param object + * the object to access + * @param value + * the new value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public void setDouble(Object object, double value) throws IllegalAccessException, + IllegalArgumentException { + setDField(object, declaringClass, type, slot, flag, TYPE_DOUBLE, value); + } + + /** + * Sets the value of the field in the specified object to the {@code float} + * value. This reproduces the effect of {@code object.fieldName = value} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * <p> + * If the value cannot be converted to the field type via a widening + * conversion, an IllegalArgumentException is thrown. + * + * @param object + * the object to access + * @param value + * the new value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public void setFloat(Object object, float value) throws IllegalAccessException, + IllegalArgumentException { + setFField(object, declaringClass, type, slot, flag, TYPE_FLOAT, value); + } + + /** + * Set the value of the field in the specified object to the {@code int} + * value. This reproduces the effect of {@code object.fieldName = value} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * <p> + * If the value cannot be converted to the field type via a widening + * conversion, an IllegalArgumentException is thrown. + * + * @param object + * the object to access + * @param value + * the new value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public void setInt(Object object, int value) throws IllegalAccessException, + IllegalArgumentException { + setIField(object, declaringClass, type, slot, flag, TYPE_INTEGER, value); + } + + /** + * Sets the value of the field in the specified object to the {@code long} + * value. This reproduces the effect of {@code object.fieldName = value} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * <p> + * If the value cannot be converted to the field type via a widening + * conversion, an IllegalArgumentException is thrown. + * + * @param object + * the object to access + * @param value + * the new value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public void setLong(Object object, long value) throws IllegalAccessException, + IllegalArgumentException { + setJField(object, declaringClass, type, slot, flag, TYPE_LONG, value); + } + + /** + * Sets the value of the field in the specified object to the {@code short} + * value. This reproduces the effect of {@code object.fieldName = value} + * <p> + * If this field is static, the object argument is ignored. + * Otherwise, if the object is {@code null}, a NullPointerException is + * thrown. If the object is not an instance of the declaring class of the + * method, an IllegalArgumentException is thrown. + * <p> + * If this Field object is enforcing access control (see AccessibleObject) + * and this field is not accessible from the current context, an + * IllegalAccessException is thrown. + * <p> + * If the value cannot be converted to the field type via a widening + * conversion, an IllegalArgumentException is thrown. + * + * @param object + * the object to access + * @param value + * the new value + * @throws NullPointerException + * if the object is {@code null} and the field is non-static + * @throws IllegalArgumentException + * if the object is not compatible with the declaring class + * @throws IllegalAccessException + * if this field is not accessible + */ + public void setShort(Object object, short value) throws IllegalAccessException, + IllegalArgumentException { + setSField(object, declaringClass, type, slot, flag, TYPE_SHORT, value); + } + + /** + * Returns a string containing a concise, human-readable description of this + * field. + * <p> + * The format of the string is: + * <ol> + * <li>modifiers (if any) + * <li>type + * <li>declaring class name + * <li>'.' + * <li>field name + * </ol> + * <p> + * For example: {@code public static java.io.InputStream + * java.lang.System.in} + * + * @return a printable representation for this field + */ + @Override + public String toString() { + StringBuilder result = new StringBuilder(Modifier.toString(getModifiers())); + if (result.length() != 0) { + result.append(' '); + } + appendTypeName(result, type); + result.append(' '); + appendTypeName(result, declaringClass); + result.append('.'); + result.append(name); + return result.toString(); + } + + private native Object getField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck) throws IllegalAccessException; + + private native double getDField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor) throws IllegalAccessException; + + private native int getIField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor) throws IllegalAccessException; + + private native long getJField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor) throws IllegalAccessException; + + private native boolean getZField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor) throws IllegalAccessException; + + private native float getFField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor) throws IllegalAccessException; + + private native char getCField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor) throws IllegalAccessException; + + private native short getSField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor) throws IllegalAccessException; + + private native byte getBField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor) throws IllegalAccessException; + + private native void setField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, Object value) throws IllegalAccessException; + + private native void setDField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor, double v) throws IllegalAccessException; + + private native void setIField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor, int i) throws IllegalAccessException; + + private native void setJField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor, long j) throws IllegalAccessException; + + private native void setZField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor, boolean z) throws IllegalAccessException; + + private native void setFField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor, float f) throws IllegalAccessException; + + private native void setCField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor, char c) throws IllegalAccessException; + + private native void setSField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor, short s) throws IllegalAccessException; + + private native void setBField(Object o, Class<?> declaringClass, Class<?> type, int slot, + boolean noAccessCheck, char descriptor, byte b) throws IllegalAccessException; + +} diff --git a/libdvm/src/main/java/java/lang/reflect/Method.java b/libdvm/src/main/java/java/lang/reflect/Method.java new file mode 100644 index 0000000..b2f970a --- /dev/null +++ b/libdvm/src/main/java/java/lang/reflect/Method.java @@ -0,0 +1,597 @@ +/* + * 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. + */ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package java.lang.reflect; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Comparator; +import libcore.util.EmptyArray; +import org.apache.harmony.kernel.vm.StringUtils; +import org.apache.harmony.luni.lang.reflect.GenericSignatureParser; +import org.apache.harmony.luni.lang.reflect.ListOfTypes; +import org.apache.harmony.luni.lang.reflect.Types; + +/** + * This class represents a method. Information about the method can be accessed, + * and the method can be invoked dynamically. + */ +public final class Method extends AccessibleObject implements GenericDeclaration, Member { + + /** + * Orders methods by their name, parameters and return type. + * + * @hide + */ + public static final Comparator<Method> ORDER_BY_SIGNATURE = new Comparator<Method>() { + public int compare(Method a, Method b) { + int comparison = a.name.compareTo(b.name); + if (comparison != 0) { + return comparison; + } + + Class<?>[] aParameters = a.parameterTypes; + Class<?>[] bParameters = b.parameterTypes; + int length = Math.min(aParameters.length, bParameters.length); + for (int i = 0; i < length; i++) { + comparison = aParameters[i].getName().compareTo(bParameters[i].getName()); + if (comparison != 0) { + return comparison; + } + } + + if (aParameters.length != bParameters.length) { + return aParameters.length - bParameters.length; + } + + // this is necessary for methods that have covariant return types. + return a.getReturnType().getName().compareTo(b.getReturnType().getName()); + } + }; + + private int slot; + + private Class<?> declaringClass; + + private String name; + + private Class<?>[] parameterTypes; + + private Class<?>[] exceptionTypes; + + private Class<?> returnType; + + private ListOfTypes genericExceptionTypes; + private ListOfTypes genericParameterTypes; + private Type genericReturnType; + private TypeVariable<Method>[] formalTypeParameters; + private volatile boolean genericTypesAreInitialized = false; + + private synchronized void initGenericTypes() { + if (!genericTypesAreInitialized) { + String signatureAttribute = getSignatureAttribute(); + GenericSignatureParser parser = new GenericSignatureParser( + declaringClass.getClassLoader()); + parser.parseForMethod(this, signatureAttribute, exceptionTypes); + formalTypeParameters = parser.formalTypeParameters; + genericParameterTypes = parser.parameterTypes; + genericExceptionTypes = parser.exceptionTypes; + genericReturnType = parser.returnType; + genericTypesAreInitialized = true; + } + } + + /** + * Construct a clone of the given instance. + * + * @param orig non-null; the original instance to clone + */ + /*package*/ Method(Method orig) { + this(orig.declaringClass, orig.parameterTypes, orig.exceptionTypes, + orig.returnType, orig.name, orig.slot); + + // Copy the accessible flag. + if (orig.flag) { + this.flag = true; + } + } + + private Method(Class<?> declaring, Class<?>[] paramTypes, Class<?>[] exceptTypes, Class<?> returnType, String name, int slot) + { + this.declaringClass = declaring; + this.name = name; + this.slot = slot; + this.parameterTypes = paramTypes; + this.exceptionTypes = exceptTypes; // may be null + this.returnType = returnType; + } + + public TypeVariable<Method>[] getTypeParameters() { + initGenericTypes(); + return formalTypeParameters.clone(); + } + + /** {@inheritDoc} */ + @Override /*package*/ String getSignatureAttribute() { + Object[] annotation = getSignatureAnnotation(declaringClass, slot); + + if (annotation == null) { + return null; + } + + return StringUtils.combineStrings(annotation); + } + + /** + * Returns the Signature annotation for this method. Returns {@code null} if + * not found. + */ + static native Object[] getSignatureAnnotation(Class declaringClass, int slot); + + /** + * Returns the string representation of the method's declaration, including + * the type parameters. + * + * @return the string representation of this method + */ + public String toGenericString() { + StringBuilder sb = new StringBuilder(80); + + initGenericTypes(); + + // append modifiers if any + int modifier = getModifiers(); + if (modifier != 0) { + sb.append(Modifier.toString(modifier & ~(Modifier.BRIDGE + + Modifier.VARARGS))).append(' '); + } + // append type parameters + if (formalTypeParameters != null && formalTypeParameters.length > 0) { + sb.append('<'); + for (int i = 0; i < formalTypeParameters.length; i++) { + appendGenericType(sb, formalTypeParameters[i]); + if (i < formalTypeParameters.length - 1) { + sb.append(","); + } + } + sb.append("> "); + } + // append return type + appendGenericType(sb, Types.getType(genericReturnType)); + sb.append(' '); + // append method name + appendTypeName(sb, getDeclaringClass()); + sb.append(".").append(getName()); + // append parameters + sb.append('('); + appendArrayGenericType(sb, + Types.getClonedTypeArray(genericParameterTypes)); + sb.append(')'); + // append exceptions if any + Type[] genericExceptionTypeArray = Types.getClonedTypeArray( + genericExceptionTypes); + if (genericExceptionTypeArray.length > 0) { + sb.append(" throws "); + appendArrayGenericType(sb, genericExceptionTypeArray); + } + return sb.toString(); + } + + /** + * Returns the parameter types as an array of {@code Type} instances, in + * declaration order. If this method has no parameters, an empty array is + * returned. + * + * @return the parameter types + * + * @throws GenericSignatureFormatError + * if the generic method signature is invalid + * @throws TypeNotPresentException + * if any parameter type points to a missing type + * @throws MalformedParameterizedTypeException + * if any parameter type points to a type that cannot be + * instantiated for some reason + */ + public Type[] getGenericParameterTypes() { + initGenericTypes(); + return Types.getClonedTypeArray(genericParameterTypes); + } + + /** + * Returns the exception types as an array of {@code Type} instances. If + * this method has no declared exceptions, an empty array will be returned. + * + * @return an array of generic exception types + * + * @throws GenericSignatureFormatError + * if the generic method signature is invalid + * @throws TypeNotPresentException + * if any exception type points to a missing type + * @throws MalformedParameterizedTypeException + * if any exception type points to a type that cannot be + * instantiated for some reason + */ + public Type[] getGenericExceptionTypes() { + initGenericTypes(); + return Types.getClonedTypeArray(genericExceptionTypes); + } + + /** + * Returns the return type of this method as a {@code Type} instance. + * + * @return the return type of this method + * + * @throws GenericSignatureFormatError + * if the generic method signature is invalid + * @throws TypeNotPresentException + * if the return type points to a missing type + * @throws MalformedParameterizedTypeException + * if the return type points to a type that cannot be + * instantiated for some reason + */ + public Type getGenericReturnType() { + initGenericTypes(); + return Types.getType(genericReturnType); + } + + @Override + public Annotation[] getDeclaredAnnotations() { + return getDeclaredAnnotations(declaringClass, slot); + } + static native Annotation[] getDeclaredAnnotations(Class<?> declaringClass, int slot); + + @Override public <A extends Annotation> A getAnnotation(Class<A> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + return getAnnotation(declaringClass, slot, annotationType); + } + static native <A extends Annotation> A getAnnotation( + Class<?> declaringClass, int slot, Class<A> annotationType); + + @Override public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + return isAnnotationPresent(declaringClass, slot, annotationType); + } + static native boolean isAnnotationPresent( + Class<?> declaringClass, int slot, Class<? extends Annotation> annotationType); + + private static final Annotation[] NO_ANNOTATIONS = new Annotation[0]; + + /** + * Creates an array of empty Annotation arrays. + */ + /*package*/ static Annotation[][] noAnnotations(int size) { + Annotation[][] annotations = new Annotation[size][]; + for (int i = 0; i < size; i++) { + annotations[i] = NO_ANNOTATIONS; + } + return annotations; + } + + /** + * Returns an array of arrays that represent the annotations of the formal + * parameters of this method. If there are no parameters on this method, + * then an empty array is returned. If there are no annotations set, then + * and array of empty arrays is returned. + * + * @return an array of arrays of {@code Annotation} instances + */ + public Annotation[][] getParameterAnnotations() { + Annotation[][] parameterAnnotations + = getParameterAnnotations(declaringClass, slot); + if (parameterAnnotations.length == 0) { + return noAnnotations(parameterTypes.length); + } + return parameterAnnotations; + } + + static native Annotation[][] getParameterAnnotations(Class declaringClass, int slot); + + /** + * Indicates whether or not this method takes a variable number argument. + * + * @return {@code true} if a vararg is declared, {@code false} otherwise + */ + public boolean isVarArgs() { + int modifiers = getMethodModifiers(declaringClass, slot); + return (modifiers & Modifier.VARARGS) != 0; + } + + /** + * Indicates whether or not this method is a bridge. + * + * @return {@code true} if this method is a bridge, {@code false} otherwise + */ + public boolean isBridge() { + int modifiers = getMethodModifiers(declaringClass, slot); + return (modifiers & Modifier.BRIDGE) != 0; + } + + /** + * Indicates whether or not this method is synthetic. + * + * @return {@code true} if this method is synthetic, {@code false} otherwise + */ + public boolean isSynthetic() { + int modifiers = getMethodModifiers(declaringClass, slot); + return (modifiers & Modifier.SYNTHETIC) != 0; + } + + /** + * Returns the default value for the annotation member represented by this + * method. + * + * @return the default value, or {@code null} if none + * + * @throws TypeNotPresentException + * if this annotation member is of type {@code Class} and no + * definition can be found + */ + public Object getDefaultValue() { + return getDefaultValue(declaringClass, slot); + } + native private Object getDefaultValue(Class declaringClass, int slot); + + /** + * Indicates whether or not the specified {@code object} is equal to this + * method. To be equal, the specified object must be an instance + * of {@code Method} with the same declaring class and parameter types + * as this method. + * + * @param object + * the object to compare + * + * @return {@code true} if the specified object is equal to this + * method, {@code false} otherwise + * + * @see #hashCode + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof Method)) { + return false; + } + Method rhs = (Method) object; + // We don't compare exceptionTypes because two methods + // can't differ only by their declared exceptions. + return declaringClass.equals(rhs.declaringClass) && + name.equals(rhs.name) && + getModifiers() == rhs.getModifiers() && + returnType.equals(rhs.returnType) && + Arrays.equals(parameterTypes, rhs.parameterTypes); + } + + /** + * Returns the class that declares this method. + * + * @return the declaring class + */ + public Class<?> getDeclaringClass() { + return declaringClass; + } + + /** + * Returns the exception types as an array of {@code Class} instances. If + * this method has no declared exceptions, an empty array is returned. + * + * @return the declared exception classes + */ + public Class<?>[] getExceptionTypes() { + if (exceptionTypes == null) { + return EmptyArray.CLASS; + } + return exceptionTypes.clone(); + } + + /** + * Returns the modifiers for this method. The {@link Modifier} class should + * be used to decode the result. + * + * @return the modifiers for this method + * + * @see Modifier + */ + public int getModifiers() { + return getMethodModifiers(declaringClass, slot); + } + + static native int getMethodModifiers(Class<?> declaringClass, int slot); + + /** + * Returns the name of the method represented by this {@code Method} + * instance. + * + * @return the name of this method + */ + public String getName() { + return name; + } + + /** + * Returns an array of {@code Class} objects associated with the parameter + * types of this method. If the method was declared with no parameters, an + * empty array will be returned. + * + * @return the parameter types + */ + public Class<?>[] getParameterTypes() { + return parameterTypes.clone(); + } + + /** + * Returns the {@code Class} associated with the return type of this + * method. + * + * @return the return type + */ + public Class<?> getReturnType() { + return returnType; + } + + /** + * Returns an integer hash code for this method. Objects which are equal + * return the same value for this method. The hash code for this Method is + * the hash code of the name of this method. + * + * @return hash code for this method + * + * @see #equals + */ + @Override + public int hashCode() { + return name.hashCode(); + } + + /** + * Returns the result of dynamically invoking this method. Equivalent to + * {@code receiver.methodName(arg1, arg2, ... , argN)}. + * + * <p>If the method is static, the receiver argument is ignored (and may be null). + * + * <p>If the method takes no arguments, you can pass {@code (Object[]) null} instead of + * allocating an empty array. + * + * <p>If you're calling a varargs method, you need to pass an {@code Object[]} for the + * varargs parameter: that conversion is usually done in {@code javac}, not the VM, and + * the reflection machinery does not do this for you. (It couldn't, because it would be + * ambiguous.) + * + * <p>Reflective method invocation follows the usual process for method lookup. + * + * <p>If an exception is thrown during the invocation it is caught and + * wrapped in an InvocationTargetException. This exception is then thrown. + * + * <p>If the invocation completes normally, the return value itself is + * returned. If the method is declared to return a primitive type, the + * return value is boxed. If the return type is void, null is returned. + * + * @param receiver + * the object on which to call this method (or null for static methods) + * @param args + * the arguments to the method + * @return the result + * + * @throws NullPointerException + * if {@code receiver == null} for a non-static method + * @throws IllegalAccessException + * if this method is not accessible (see {@link AccessibleObject}) + * @throws IllegalArgumentException + * if the number of arguments doesn't match the number of parameters, the receiver + * is incompatible with the declaring class, or an argument could not be unboxed + * or converted by a widening conversion to the corresponding parameter type + * @throws InvocationTargetException + * if an exception was thrown by the invoked method + */ + public Object invoke(Object receiver, Object... args) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + if (args == null) { + args = EmptyArray.OBJECT; + } + return invokeNative(receiver, args, declaringClass, parameterTypes, returnType, slot, flag); + } + + private native Object invokeNative(Object obj, Object[] args, Class<?> declaringClass, + Class<?>[] parameterTypes, Class<?> returnType, int slot, boolean noAccessCheck) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException; + + /** + * Returns a string containing a concise, human-readable description of this + * method. The format of the string is: + * + * <ol> + * <li>modifiers (if any) + * <li>return type or 'void' + * <li>declaring class name + * <li>'(' + * <li>parameter types, separated by ',' (if any) + * <li>')' + * <li>'throws' plus exception types, separated by ',' (if any) + * </ol> + * + * For example: {@code public native Object + * java.lang.Method.invoke(Object,Object) throws + * IllegalAccessException,IllegalArgumentException + * ,InvocationTargetException} + * + * @return a printable representation for this method + */ + @Override + public String toString() { + StringBuilder result = new StringBuilder(Modifier.toString(getModifiers())); + + if (result.length() != 0) + result.append(' '); + result.append(returnType.getName()); + result.append(' '); + result.append(declaringClass.getName()); + result.append('.'); + result.append(name); + result.append("("); + result.append(toString(parameterTypes)); + result.append(")"); + if (exceptionTypes != null && exceptionTypes.length != 0) { + result.append(" throws "); + result.append(toString(exceptionTypes)); + } + + return result.toString(); + } + + /** + * Returns the constructor's signature in non-printable form. This is called + * (only) from IO native code and needed for deriving the serialVersionUID + * of the class + * + * @return The constructor's signature. + */ + @SuppressWarnings("unused") + private String getSignature() { + StringBuilder result = new StringBuilder(); + + result.append('('); + for (int i = 0; i < parameterTypes.length; i++) { + result.append(getSignature(parameterTypes[i])); + } + result.append(')'); + result.append(getSignature(returnType)); + + return result.toString(); + } + +} diff --git a/libdvm/src/main/java/java/lang/reflect/Proxy.java b/libdvm/src/main/java/java/lang/reflect/Proxy.java new file mode 100644 index 0000000..3b10887 --- /dev/null +++ b/libdvm/src/main/java/java/lang/reflect/Proxy.java @@ -0,0 +1,278 @@ +/* + * 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.reflect; + +import java.io.Serializable; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * {@code Proxy} defines methods for creating dynamic proxy classes and instances. + * A proxy class implements a declared set of interfaces and delegates method + * invocations to an {@code InvocationHandler}. + * + * @see InvocationHandler + * @since 1.3 + */ +public class Proxy implements Serializable { + + private static final long serialVersionUID = -2222568056686623797L; + + // maps class loaders to created classes by interface names + private static final Map<ClassLoader, Map<String, WeakReference<Class<?>>>> loaderCache = new WeakHashMap<ClassLoader, Map<String, WeakReference<Class<?>>>>(); + + // to find previously created types + private static final Map<Class<?>, String> proxyCache = new WeakHashMap<Class<?>, String>(); + + private static int NextClassNameIndex = 0; + + /** + * The invocation handler on which the method calls are dispatched. + */ + protected InvocationHandler h; + + @SuppressWarnings("unused") + private Proxy() { + } + + /** + * Constructs a new {@code Proxy} instance with the specified invocation + * handler. + * + * @param h + * the invocation handler for the newly created proxy + */ + protected Proxy(InvocationHandler h) { + this.h = h; + } + + /** + * Returns the dynamically built {@code Class} for the specified interfaces. + * Creates a new {@code Class} when necessary. The order of the interfaces + * is relevant. Invocations of this method with the same interfaces but + * different order result in different generated classes. The interfaces + * must be visible from the supplied class loader; no duplicates are + * permitted. All non-public interfaces must be defined in the same package. + * + * @param loader + * the class loader that will define the proxy class + * @param interfaces + * an array of {@code Class} objects, each one identifying an + * interface that will be implemented by the returned proxy + * class + * @return a proxy class that implements all of the interfaces referred to + * in the contents of {@code interfaces} + * @throws IllegalArgumentException + * if any of the interface restrictions are violated + * @throws NullPointerException + * if either {@code interfaces} or any of its elements are + * {@code null} + */ + public static Class<?> getProxyClass(ClassLoader loader, + Class<?>... interfaces) throws IllegalArgumentException { + // check that interfaces are a valid array of visible interfaces + if (interfaces == null) { + throw new NullPointerException("interfaces == null"); + } + String commonPackageName = null; + for (int i = 0, length = interfaces.length; i < length; i++) { + Class<?> next = interfaces[i]; + if (next == null) { + throw new NullPointerException("interfaces[" + i + "] == null"); + } + String name = next.getName(); + if (!next.isInterface()) { + throw new IllegalArgumentException(name + " is not an interface"); + } + if (loader != next.getClassLoader()) { + try { + if (next != Class.forName(name, false, loader)) { + throw new IllegalArgumentException(name + + " is not visible from class loader"); + } + } catch (ClassNotFoundException ex) { + throw new IllegalArgumentException(name + " is not visible from class loader"); + } + } + for (int j = i + 1; j < length; j++) { + if (next == interfaces[j]) { + throw new IllegalArgumentException(name + " appears more than once"); + } + } + if (!Modifier.isPublic(next.getModifiers())) { + int last = name.lastIndexOf('.'); + String p = last == -1 ? "" : name.substring(0, last); + if (commonPackageName == null) { + commonPackageName = p; + } else if (!commonPackageName.equals(p)) { + throw new IllegalArgumentException("non-public interfaces must be " + + "in the same package"); + } + } + } + + // search cache for matching proxy class using the class loader + synchronized (loaderCache) { + Map<String, WeakReference<Class<?>>> interfaceCache = loaderCache + .get(loader); + if (interfaceCache == null) { + loaderCache + .put( + loader, + (interfaceCache = new HashMap<String, WeakReference<Class<?>>>())); + } + + String interfaceKey = ""; + if (interfaces.length == 1) { + interfaceKey = interfaces[0].getName(); + } else { + StringBuilder names = new StringBuilder(); + for (int i = 0, length = interfaces.length; i < length; i++) { + names.append(interfaces[i].getName()); + names.append(' '); + } + interfaceKey = names.toString(); + } + + Class<?> newClass; + WeakReference<Class<?>> ref = interfaceCache.get(interfaceKey); + if (ref == null) { + String nextClassName = "$Proxy" + NextClassNameIndex++; + if (commonPackageName != null && commonPackageName.length() > 0) { + nextClassName = commonPackageName + "." + nextClassName; + } + if (loader == null) { + loader = ClassLoader.getSystemClassLoader(); + } + newClass = generateProxy(nextClassName.replace('.', '/'), interfaces, loader); + // Need a weak reference to the class so it can + // be unloaded if the class loader is discarded + interfaceCache.put(interfaceKey, new WeakReference<Class<?>>(newClass)); + synchronized (proxyCache) { + // the value is unused + proxyCache.put(newClass, ""); + } + } else { + newClass = ref.get(); + assert newClass != null : "\ninterfaceKey=\"" + interfaceKey + "\"" + + "\nloaderCache=\"" + loaderCache + "\"" + + "\nintfCache=\"" + interfaceCache + "\"" + + "\nproxyCache=\"" + proxyCache + "\""; + } + return newClass; + } + } + + /** + * Returns an instance of the dynamically built class for the specified + * interfaces. Method invocations on the returned instance are forwarded to + * the specified invocation handler. The interfaces must be visible from the + * supplied class loader; no duplicates are permitted. All non-public + * interfaces must be defined in the same package. + * + * @param loader + * the class loader that will define the proxy class + * @param interfaces + * an array of {@code Class} objects, each one identifying an + * interface that will be implemented by the returned proxy + * object + * @param h + * the invocation handler that handles the dispatched method + * invocations + * @return a new proxy object that delegates to the handler {@code h} + * @throws IllegalArgumentException + * if any of the interface restrictions are violated + * @throws NullPointerException + * if the interfaces or any of its elements are null + */ + public static Object newProxyInstance(ClassLoader loader, + Class<?>[] interfaces, InvocationHandler h) + throws IllegalArgumentException { + if (h == null) { + throw new NullPointerException("h == null"); + } + try { + return getProxyClass(loader, interfaces).getConstructor( + new Class<?>[] { InvocationHandler.class }).newInstance( + new Object[] { h }); + } catch (NoSuchMethodException ex) { + throw (InternalError) (new InternalError(ex.toString()) + .initCause(ex)); + } catch (IllegalAccessException ex) { + throw (InternalError) (new InternalError(ex.toString()) + .initCause(ex)); + } catch (InstantiationException ex) { + throw (InternalError) (new InternalError(ex.toString()) + .initCause(ex)); + } catch (InvocationTargetException ex) { + Throwable target = ex.getTargetException(); + throw (InternalError) (new InternalError(target.toString()) + .initCause(target)); + } + } + + /** + * Indicates whether or not the specified class is a dynamically generated + * proxy class. + * + * @param cl + * the class + * @return {@code true} if the class is a proxy class, {@code false} + * otherwise + * @throws NullPointerException + * if the class is {@code null} + */ + public static boolean isProxyClass(Class<?> cl) { + if (cl == null) { + throw new NullPointerException("cl == null"); + } + synchronized (proxyCache) { + return proxyCache.containsKey(cl); + } + } + + /** + * Returns the invocation handler of the specified proxy instance. + * + * @param proxy + * the proxy instance + * @return the invocation handler of the specified proxy instance + * @throws IllegalArgumentException + * if the supplied {@code proxy} is not a proxy object + */ + public static InvocationHandler getInvocationHandler(Object proxy) + throws IllegalArgumentException { + + if (isProxyClass(proxy.getClass())) { + return ((Proxy) proxy).h; + } + + throw new IllegalArgumentException("not a proxy instance"); + } + + native private static Class generateProxy(String name, Class[] interfaces, + ClassLoader loader); + + /* + * The VM clones this method's descriptor when generating a proxy class. + * There is no implementation. + */ + native private static void constructorPrototype(InvocationHandler h); +} diff --git a/libdvm/src/main/java/org/apache/harmony/kernel/vm/StringUtils.java b/libdvm/src/main/java/org/apache/harmony/kernel/vm/StringUtils.java new file mode 100644 index 0000000..01d0e30 --- /dev/null +++ b/libdvm/src/main/java/org/apache/harmony/kernel/vm/StringUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package org.apache.harmony.kernel.vm; + +/** + * String utility functions. + */ +public final class StringUtils { + /** + * This class is uninstantiable. + */ + private StringUtils() { + // This space intentionally left blank. + } + + /** + * Combine a list of strings in an <code>Object[]</code> into a single + * string. + * + * @param list non-null; the strings to combine + * @return non-null; the combined form + */ + public static String combineStrings(Object[] list) { + int listLength = list.length; + + switch (listLength) { + case 0: { + return ""; + } + case 1: { + return (String) list[0]; + } + } + + int strLength = 0; + + for (int i = 0; i < listLength; i++) { + strLength += ((String) list[i]).length(); + } + + StringBuilder sb = new StringBuilder(strLength); + + for (int i = 0; i < listLength; i++) { + sb.append(list[i]); + } + + return sb.toString(); + } +} diff --git a/libdvm/src/main/java/org/apache/harmony/lang/annotation/AnnotationFactory.java b/libdvm/src/main/java/org/apache/harmony/lang/annotation/AnnotationFactory.java new file mode 100644 index 0000000..3fd13f5 --- /dev/null +++ b/libdvm/src/main/java/org/apache/harmony/lang/annotation/AnnotationFactory.java @@ -0,0 +1,317 @@ +/* + * 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.apache.harmony.lang.annotation; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.annotation.IncompleteAnnotationException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import static org.apache.harmony.lang.annotation.AnnotationMember.ARRAY; +import static org.apache.harmony.lang.annotation.AnnotationMember.ERROR; + +/** + * The annotation implementation based on dynamically generated proxy instances. + * It conforms to all requirements stated in public APIs, see in particular + * {@link java.lang.reflect.AnnotatedElement java.lang.reflect.AnnotatedElement} + * and {@link java.lang.annotation.Annotation java.lang.annotation.Annotation}. + * Namely, annotation instances are immutable and serializable; they provide + * conforming access to annotation member values and required implementations of + * methods declared in Annotation interface. + * + * @see android.lang.annotation.AnnotationMember + * @see java.lang.annotation.Annotation + * + * @author Alexey V. Varlamov, Serguei S. Zapreyev + * @version $Revision$ + */ +@SuppressWarnings({"serial"}) +public final class AnnotationFactory implements InvocationHandler, Serializable { + + private static final transient + Map<Class<? extends Annotation>, AnnotationMember[]> + cache = new WeakHashMap<Class<? extends Annotation>, AnnotationMember[]>(); + + /** + * Reflects specified annotation type and returns an array + * of member element definitions with default values. + */ + public static AnnotationMember[] getElementsDescription(Class<? extends Annotation> annotationType ) { + AnnotationMember[] desc = cache.get(annotationType); + if (desc == null) { + if (!annotationType.isAnnotation()) { + throw new IllegalArgumentException("Type is not annotation: " + + annotationType.getName()); + } + Method[] m = annotationType.getDeclaredMethods(); + desc = new AnnotationMember[m.length]; + int idx = 0; + for (Method element : m) { + String name = element.getName(); + Class<?> type = element.getReturnType(); + try { + desc[idx] = new AnnotationMember(name, + element.getDefaultValue(), type, element); + } catch (Throwable t) { + desc[idx] = new AnnotationMember(name, t, type, element); + } + idx++; + } + cache.put(annotationType, desc); + } + return desc; + } + + /** + * Provides a new annotation instance. + * @param annotationType the annotation type definition + * @param elements name-value pairs representing elements of the annotation + * @return a new annotation instance + */ + public static Annotation createAnnotation( + Class<? extends Annotation> annotationType, + AnnotationMember[] elements) + { + AnnotationFactory antn = new AnnotationFactory(annotationType, elements); + return (Annotation)Proxy.newProxyInstance( annotationType.getClassLoader(), + new Class[]{annotationType}, antn); + } + + private final Class<? extends Annotation> klazz; + private AnnotationMember[] elements; + + /** + * New instances should not be created directly, use factory method + * {@link #createAnnotation(Class, AnnotationMember[]) createAnnotation()} + * instead. + * + * @param klzz class defining the annotation type + * @param values actual element values + */ + private AnnotationFactory(Class<? extends Annotation> klzz, AnnotationMember[] values) { + klazz = klzz; + AnnotationMember[] defs = getElementsDescription(klazz); + if (values == null) { + elements = defs; + } else { + //merge default and actual values + elements = new AnnotationMember[defs.length]; + next: for (int i = elements.length - 1; i >= 0; i-- ){ + for (AnnotationMember val : values){ + if (val.name.equals(defs[i].name)) { + elements[i] = val.setDefinition(defs[i]); + continue next; + } + } + elements[i] = defs[i]; + } + } + } + + /** + * Reads the object, obtains actual member definitions for the annotation type, + * and merges deserialized values with the new definitions. + */ + private void readObject(ObjectInputStream os) throws IOException, + ClassNotFoundException { + os.defaultReadObject(); + // Annotation type members can be changed arbitrarily + // So there may be zombi elements from the previous life; + // they hardly fit into this new annotation's incarnation, + // as we have no defining methods for them. + // Reasonably just drop such elements, + // but seems better to keep them for compatibility + AnnotationMember[] defs = getElementsDescription(klazz); + AnnotationMember[] old = elements; + List<AnnotationMember> merged = new ArrayList<AnnotationMember>( + defs.length + old.length); + nextOld: for (AnnotationMember el1 : old) { + for (AnnotationMember el2 : defs) { + if (el2.name.equals(el1.name)) { + continue nextOld; + } + } + merged.add(el1); //phantom element + } + nextNew: for (AnnotationMember def : defs){ + for (AnnotationMember val : old){ + if (val.name.equals(def.name)) { + // nothing to do about cached errors (if any) + // anyway they remain relevant to values + merged.add(val.setDefinition(def)); + continue nextNew; + } + } + merged.add(def); // brand new element + } + elements = merged.toArray(new AnnotationMember[merged.size()]); + } + + /** + * Returns true if the specified object represents the same annotation instance. + * That is, if it implements the same annotation type and + * returns the same element values. + * <br>Note, actual underlying implementation mechanism does not matter - it may + * differ completely from this class. + * @return true if the passed object is equivalent annotation instance, + * false otherwise. + * @see android.lang.annotation.AnnotationMember#equals(Object) + */ + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!klazz.isInstance(obj)) { + return false; + } + Object handler = null; + if (Proxy.isProxyClass(obj.getClass()) + && (handler = Proxy.getInvocationHandler(obj)) instanceof AnnotationFactory ) { + AnnotationFactory other = (AnnotationFactory) handler; + if (elements.length != other.elements.length) { + return false; + } + next: for (AnnotationMember el1 : elements){ + for (AnnotationMember el2 : other.elements) { + if (el1.equals(el2)) { + continue next; + } + } + return false; + } + return true; + } else { + // encountered foreign annotation implementaton + // so have to obtain element values via invocation + // of corresponding methods + for (final AnnotationMember el : elements) { + if (el.tag == ERROR) { + // undefined value is incomparable (transcendent) + return false; + } + try { + if (!el.definingMethod.isAccessible()) { + el.definingMethod.setAccessible(true); + } + Object otherValue = el.definingMethod.invoke(obj); + if (otherValue != null ) { + if (el.tag == ARRAY) { + if (!el.equalArrayValue(otherValue)) { + return false; + } + } else { + if (!el.value.equals(otherValue)) { + return false; + } + } + } else if (el.value != AnnotationMember.NO_VALUE) { + return false; + } + } catch (Throwable e) { + return false; + } + } + return true; + } + } + + /** + * Returns a hash code composed as a sum of hash codes of member elements, + * including elements with default values. + * @see android.lang.annotation.AnnotationMember#hashCode() + */ + public int hashCode() { + int hash = 0; + for (AnnotationMember element : elements) { + hash += element.hashCode(); + } + return hash; + } + + /** + * Provides detailed description of this annotation instance, + * including all member name-values pairs. + * @return string representation of this annotation + */ + public String toString() { + StringBuilder result = new StringBuilder(); + result.append('@'); + result.append(klazz.getName()); + result.append('('); + for (int i = 0; i < elements.length; ++i) { + if (i != 0) { + result.append(", "); + } + result.append(elements[i]); + } + result.append(')'); + return result.toString(); + } + + /** + * Processes a method invocation request to this annotation instance. + * Recognizes the methods declared in the + * {@link java.lang.annotation.Annotation java.lang.annotation.Annotation} + * interface, and member-defining methods of the implemented annotation type. + * @throws IllegalArgumentException If the specified method is none of the above + * @return the invocation result + */ + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + String name = method.getName(); + Class[] params = method.getParameterTypes(); + if (params.length == 0) { + if ("annotationType".equals(name)) { + return klazz; + } else if ("toString".equals(name)) { + return toString(); + } else if ("hashCode".equals(name)) { + return hashCode(); + } + + // this must be element value request + AnnotationMember element = null; + for (AnnotationMember el : elements) { + if (name.equals(el.name)) { + element = el; + break; + } + } + if (element == null || !method.equals(element.definingMethod)) { + throw new IllegalArgumentException(method.toString()); + } else { + Object value = element.validateValue(); + if (value == null) { + throw new IncompleteAnnotationException(klazz, name); + } + return value; + } + } else if (params.length == 1 && params[0] == Object.class && "equals".equals(name)){ + return Boolean.valueOf(equals(args[0])); + } + throw new IllegalArgumentException( + "Invalid method for annotation type: " + method); + } +} diff --git a/libdvm/src/main/java/org/apache/harmony/lang/annotation/AnnotationMember.java b/libdvm/src/main/java/org/apache/harmony/lang/annotation/AnnotationMember.java new file mode 100644 index 0000000..f64332a --- /dev/null +++ b/libdvm/src/main/java/org/apache/harmony/lang/annotation/AnnotationMember.java @@ -0,0 +1,387 @@ +/* + * 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.apache.harmony.lang.annotation; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.annotation.AnnotationTypeMismatchException; +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * This class represents member element of an annotation. + * It consists of name and value, supplemented with element + * definition information (such as declared type of element). + * <br>The value may be one of the following types: + * <ul> + * <li> boxed primitive + * <li> Class + * <li> enum constant + * <li> annotation (nested) + * <li> one-dimensional array of the above + * <li> Throwable + * </ul> + * The last type is specific for this implementation; a Throwable value + * means that the error occured during parsing or resolution of corresponding + * class-data structures and throwing is delayed until the element + * is requested for value. + * + * @see android.lang.annotation.AnnotationFactory + * + * @author Alexey V. Varlamov, Serguei S. Zapreyev + * @version $Revision$ + */ +@SuppressWarnings({"serial"}) +public class AnnotationMember implements Serializable { + + /** + * Tag description of a Throwable value type. + */ + protected static final char ERROR = '!'; + + /** + * Tag description of an array value type. + */ + protected static final char ARRAY = '['; + + /** + * Tag description of all value types except arrays and Throwables. + */ + protected static final char OTHER = '*'; + +// public static final char INT = 'I'; +// public static final char CHAR = 'C'; +// public static final char DOUBLE = 'D'; +// public static final char FLOAT = 'F'; +// public static final char BYTE = 'B'; +// public static final char LONG = 'J'; +// public static final char SHORT = 'S'; +// public static final char BOOL = 'Z'; +// public static final char CLASS = 'c'; +// public static final char ENUM = 'e'; +// public static final char ANTN = '@'; + + private enum DefaultValues {NO_VALUE} + + /** + * Singleton representing missing element value. + */ + protected static final Object NO_VALUE = DefaultValues.NO_VALUE; + + protected final String name; + protected final Object value; // a primitive value is wrapped to the corresponding wrapper class + protected final char tag; + // no sense to serialize definition info as it can be changed arbitrarily + protected transient Class<?> elementType; + protected transient Method definingMethod; + + + /** + * Creates a new element with specified name and value. + * Definition info will be provided later when this + * element becomes actual annotation member. + * @param name element name, must not be null + * @param val element value, should be of addmissible type, + * as specified in the description of this class + * + * @see #setDefinition(AnnotationMember) + */ + public AnnotationMember(String name, Object val) { + this.name = name; + value = val == null ? NO_VALUE : val; + if (value instanceof Throwable) { + tag = ERROR; + } else if (value.getClass().isArray()) { + tag = ARRAY; + } else { + tag = OTHER; + } + } + + /** + * Creates the completely defined element. + * @param name element name, must not be null + * @param value element value, should be of addmissible type, + * as specified in the description of this class + * @param m element-defining method, reflected on the annotation type + * @param type declared type of this element + * (return type of the defining method) + */ + public AnnotationMember(String name, Object val, Class type, Method m) { + this(name, val); + + definingMethod = m; + + if (type == int.class) { + elementType = Integer.class; + } else if (type == boolean.class) { + elementType = Boolean.class; + } else if (type == char.class) { + elementType = Character.class; + } else if (type == float.class) { + elementType = Float.class; + } else if (type == double.class) { + elementType = Double.class; + } else if (type == long.class) { + elementType = Long.class; + } else if (type == short.class) { + elementType = Short.class; + } else if (type == byte.class) { + elementType = Byte.class; + } else { + elementType = type; + } + } + + /** + * Fills in element's definition info and returns this. + */ + protected AnnotationMember setDefinition(AnnotationMember copy) { + definingMethod = copy.definingMethod; + elementType = copy.elementType; + return this; + } + + /** + * Returns readable description of this annotation value. + */ + public String toString() { + if (tag == ARRAY) { + StringBuilder sb = new StringBuilder(80); + sb.append(name).append("=["); + int len = Array.getLength(value); + for (int i = 0; i < len; i++) { + if (i != 0) sb.append(", "); + sb.append(Array.get(value, i)); + } + return sb.append("]").toString(); + } else { + return name+ "=" +value; + } + } + + /** + * Returns true if the specified object represents equal element + * (equivalent name-value pair). + * <br> A special case is the contained Throwable value; it is considered + * transcendent so no other element would be equal. + * @return true if passed object is equivalent element representation, + * false otherwise + * @see #equalArrayValue(Object) + * @see java.lang.annotation.Annotation#equals(Object) + */ + public boolean equals(Object obj) { + if (obj == this) { + // not a mere optimization, + // this is needed for consistency with hashCode() + return true; + } + if (obj instanceof AnnotationMember) { + AnnotationMember that = (AnnotationMember)obj; + if (name.equals(that.name) && tag == that.tag) { + if (tag == ARRAY) { + return equalArrayValue(that.value); + } else if (tag == ERROR) { + // undefined value is incomparable (transcendent) + return false; + } else { + return value.equals(that.value); + } + } + } + return false; + } + + /** + * Returns true if the contained value and a passed object are equal arrays, + * false otherwise. Appropriate overloaded method of Arrays.equals() + * is used for equality testing. + * @see java.util.Arrays#equals(java.lang.Object[], java.lang.Object[]) + * @return true if the value is array and is equal to specified object, + * false otherwise + */ + public boolean equalArrayValue(Object otherValue) { + if (value instanceof Object[] && otherValue instanceof Object[]) { + return Arrays.equals((Object[])value, (Object[])otherValue); + } + Class type = value.getClass(); + if (type != otherValue.getClass()) { + return false; + } + if (type == int[].class) { + return Arrays.equals((int[])value, (int[])otherValue); + } else if (type == byte[].class) { + return Arrays.equals((byte[])value, (byte[])otherValue); + } else if (type == short[].class) { + return Arrays.equals((short[])value, (short[])otherValue); + } else if (type == long[].class) { + return Arrays.equals((long[])value, (long[])otherValue); + } else if (type == char[].class) { + return Arrays.equals((char[])value, (char[])otherValue); + } else if (type == boolean[].class) { + return Arrays.equals((boolean[])value, (boolean[])otherValue); + } else if (type == float[].class) { + return Arrays.equals((float[])value, (float[])otherValue); + } else if (type == double[].class) { + return Arrays.equals((double[])value, (double[])otherValue); + } + return false; + } + + /** + * Computes hash code of this element. The formula is as follows: + * <code> (name.hashCode() * 127) ^ value.hashCode() </code> + * <br>If value is an array, one of overloaded Arrays.hashCode() + * methods is used. + * @return the hash code + * @see java.util.Arrays#hashCode(java.lang.Object[]) + * @see java.lang.annotation.Annotation#hashCode() + */ + public int hashCode() { + int hash = name.hashCode() * 127; + if (tag == ARRAY) { + Class type = value.getClass(); + if (type == int[].class) { + return hash ^ Arrays.hashCode((int[])value); + } else if (type == byte[].class) { + return hash ^ Arrays.hashCode((byte[])value); + } else if (type == short[].class) { + return hash ^ Arrays.hashCode((short[])value); + } else if (type == long[].class) { + return hash ^ Arrays.hashCode((long[])value); + } else if (type == char[].class) { + return hash ^ Arrays.hashCode((char[])value); + } else if (type == boolean[].class) { + return hash ^ Arrays.hashCode((boolean[])value); + } else if (type == float[].class) { + return hash ^ Arrays.hashCode((float[])value); + } else if (type == double[].class) { + return hash ^ Arrays.hashCode((double[])value); + } + return hash ^ Arrays.hashCode((Object[])value); + } else { + return hash ^ value.hashCode(); + } + } + + /** + * Throws contained error (if any) with a renewed stack trace. + */ + public void rethrowError() throws Throwable { + if (tag == ERROR) { + // need to throw cloned exception for thread safety + // besides it is better to provide actual stack trace + // rather than recorded during parsing + + // first check for expected types + if (value instanceof TypeNotPresentException) { + TypeNotPresentException tnpe = (TypeNotPresentException)value; + throw new TypeNotPresentException(tnpe.typeName(), tnpe.getCause()); + } else if (value instanceof EnumConstantNotPresentException) { + EnumConstantNotPresentException ecnpe = (EnumConstantNotPresentException)value; + throw new EnumConstantNotPresentException(ecnpe.enumType(), ecnpe.constantName()); + } else if (value instanceof ArrayStoreException) { + ArrayStoreException ase = (ArrayStoreException)value; + throw new ArrayStoreException(ase.getMessage()); + } + // got some other error, have to go with deep cloning + // via serialization mechanism + Throwable error = (Throwable)value; + StackTraceElement[] ste = error.getStackTrace(); + ByteArrayOutputStream bos = new ByteArrayOutputStream( + ste == null ? 512 : (ste.length + 1) * 80); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(error); + oos.flush(); + oos.close(); + ByteArrayInputStream bis = new ByteArrayInputStream(bos + .toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + error = (Throwable)ois.readObject(); + ois.close(); + + throw error; + } + } + + /** + * Validates contained value against its member definition + * and if ok returns the value. + * Otherwise, if the value type mismatches definition + * or the value itself describes an error, + * throws appropriate exception. + * <br> Note, this method may return null if this element was constructed + * with such value. + * + * @see #rethrowError() + * @see #copyValue() + * @return actual valid value or null if no value + */ + public Object validateValue() throws Throwable { + if (tag == ERROR) { + rethrowError(); + } + if (value == NO_VALUE) { + return null; + } + if (elementType == value.getClass() + || elementType.isInstance(value)) { // nested annotation value + return copyValue(); + } else { + throw new AnnotationTypeMismatchException(definingMethod, + value.getClass().getName()); + } + + } + + + /** + * Provides mutation-safe access to contained value. That is, caller is free + * to modify the returned value, it will not affect the contained data value. + * @return cloned value if it is mutable or the original immutable value + */ + public Object copyValue() throws Throwable + { + if (tag != ARRAY || Array.getLength(value) == 0) { + return value; + } + Class type = value.getClass(); + if (type == int[].class) { + return ((int[])value).clone(); + } else if (type == byte[].class) { + return ((byte[])value).clone(); + } else if (type == short[].class) { + return ((short[])value).clone(); + } else if (type == long[].class) { + return ((long[])value).clone(); + } else if (type == char[].class) { + return ((char[])value).clone(); + } else if (type == boolean[].class) { + return ((boolean[])value).clone(); + } else if (type == float[].class) { + return ((float[])value).clone(); + } else if (type == double[].class) { + return ((double[])value).clone(); + } + return ((Object[])value).clone(); + } +} diff --git a/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/GenericSignatureParser.java b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/GenericSignatureParser.java new file mode 100644 index 0000000..490daaf --- /dev/null +++ b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/GenericSignatureParser.java @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package org.apache.harmony.luni.lang.reflect; + +import java.lang.reflect.Constructor; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.GenericSignatureFormatError; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +/** + * Implements a parser for the generics signature attribute. + * Uses a top-down, recursive descent parsing approach for the following grammar: + * <pre> + * ClassSignature ::= + * OptFormalTypeParams SuperclassSignature {SuperinterfaceSignature}. + * SuperclassSignature ::= ClassTypeSignature. + * SuperinterfaceSignature ::= ClassTypeSignature. + * + * OptFormalTypeParams ::= + * ["<" FormalTypeParameter {FormalTypeParameter} ">"]. + * + * FormalTypeParameter ::= Ident ClassBound {InterfaceBound}. + * ClassBound ::= ":" [FieldTypeSignature]. + * InterfaceBound ::= ":" FieldTypeSignature. + * + * FieldTypeSignature ::= + * ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature. + * ArrayTypeSignature ::= "[" TypSignature. + * + * ClassTypeSignature ::= + * "L" {Ident "/"} Ident OptTypeArguments {"." Ident OptTypeArguments} ";". + * + * OptTypeArguments ::= "<" TypeArgument {TypeArgument} ">". + * + * TypeArgument ::= ([WildcardIndicator] FieldTypeSignature) | "*". + * WildcardIndicator ::= "+" | "-". + * + * TypeVariableSignature ::= "T" Ident ";". + * + * TypSignature ::= FieldTypeSignature | BaseType. + * BaseType ::= "B" | "C" | "D" | "F" | "I" | "J" | "S" | "Z". + * + * MethodTypeSignature ::= + * OptFormalTypeParams "(" {TypeSignature} ")" ReturnType {ThrowsSignature}. + * ThrowsSignature ::= ("^" ClassTypeSignature) | ("^" TypeVariableSignature). + * + * ReturnType ::= TypSignature | VoidDescriptor. + * VoidDescriptor ::= "V". + * </pre> + */ +public class GenericSignatureParser { + + public ListOfTypes exceptionTypes; + public ListOfTypes parameterTypes; + public TypeVariable[] formalTypeParameters; + public Type returnType; + public Type fieldType; + public ListOfTypes interfaceTypes; + public Type superclassType; + public ClassLoader loader; + + GenericDeclaration genericDecl; + + /* + * Parser: + */ + char symbol; // 0: eof; else valid term symbol or first char of identifier. + String identifier; + + + /* + * Scanner: + * eof is private to the scan methods + * and it's set only when a scan is issued at the end of the buffer. + */ + private boolean eof; + + char[] buffer; + int pos; + + public GenericSignatureParser(ClassLoader loader) { + this.loader = loader; + } + + void setInput(GenericDeclaration genericDecl, String input) { + if (input != null) { + this.genericDecl = genericDecl; + this.buffer = input.toCharArray(); + this.eof = false; + scanSymbol(); + } + else { + this.eof = true; + } + } + + /** + * Parses the generic signature of a class and creates the data structure + * representing the signature. + * + * @param genericDecl the GenericDeclaration calling this method + * @param signature the generic signature of the class + */ + public void parseForClass(GenericDeclaration genericDecl, + String signature) { + setInput(genericDecl, signature); + if (!eof) { + parseClassSignature(); + } else { + if(genericDecl instanceof Class) { + Class c = (Class) genericDecl; + this.formalTypeParameters = ListOfVariables.EMPTY; + this.superclassType = c.getSuperclass(); + this.interfaceTypes = new ListOfTypes(c.getInterfaces()); + } else { + this.formalTypeParameters = ListOfVariables.EMPTY; + this.superclassType = Object.class; + this.interfaceTypes = ListOfTypes.EMPTY; + } + } + } + + /** + * Parses the generic signature of a method and creates the data structure + * representing the signature. + * + * @param genericDecl the GenericDeclaration calling this method + * @param signature the generic signature of the class + */ + public void parseForMethod(GenericDeclaration genericDecl, + String signature, Class<?>[] rawExceptionTypes) { + setInput(genericDecl, signature); + if (!eof) { + parseMethodTypeSignature(rawExceptionTypes); + } else { + if(genericDecl instanceof Method) { + Method m = (Method) genericDecl; + this.formalTypeParameters = ListOfVariables.EMPTY; + this.parameterTypes = new ListOfTypes(m.getParameterTypes()); + this.exceptionTypes = new ListOfTypes(m.getExceptionTypes()); + this.returnType = m.getReturnType(); + } else { + this.formalTypeParameters = ListOfVariables.EMPTY; + this.parameterTypes = ListOfTypes.EMPTY; + this.exceptionTypes = ListOfTypes.EMPTY; + this.returnType = void.class; + } + } + } + + /** + * Parses the generic signature of a constructor and creates the data + * structure representing the signature. + * + * @param genericDecl the GenericDeclaration calling this method + * @param signature the generic signature of the class + */ + public void parseForConstructor(GenericDeclaration genericDecl, + String signature, Class<?>[] rawExceptionTypes) { + setInput(genericDecl, signature); + if (!eof) { + parseMethodTypeSignature(rawExceptionTypes); + } else { + if(genericDecl instanceof Constructor) { + Constructor c = (Constructor) genericDecl; + this.formalTypeParameters = ListOfVariables.EMPTY; + this.parameterTypes = new ListOfTypes(c.getParameterTypes()); + this.exceptionTypes = new ListOfTypes(c.getExceptionTypes()); + } else { + this.formalTypeParameters = ListOfVariables.EMPTY; + this.parameterTypes = ListOfTypes.EMPTY; + this.exceptionTypes = ListOfTypes.EMPTY; + } + } + } + + /** + * Parses the generic signature of a field and creates the data structure + * representing the signature. + * + * @param genericDecl the GenericDeclaration calling this method + * @param signature the generic signature of the class + */ + public void parseForField(GenericDeclaration genericDecl, + String signature) { + setInput(genericDecl, signature); + if (!eof) { + this.fieldType = parseFieldTypeSignature(); + } + } + + + // + // Parser: + // + + void parseClassSignature() { + // ClassSignature ::= + // OptFormalTypeParameters SuperclassSignature {SuperinterfaceSignature}. + + parseOptFormalTypeParameters(); + + // SuperclassSignature ::= ClassTypeSignature. + this.superclassType = parseClassTypeSignature(); + + interfaceTypes = new ListOfTypes(16); + while (symbol > 0) { + // SuperinterfaceSignature ::= ClassTypeSignature. + interfaceTypes.add(parseClassTypeSignature()); + } + } + + void parseOptFormalTypeParameters() { + // OptFormalTypeParameters ::= + // ["<" FormalTypeParameter {FormalTypeParameter} ">"]. + + ListOfVariables typeParams = new ListOfVariables(); + + if (symbol == '<') { + scanSymbol(); + typeParams.add(parseFormalTypeParameter()); + while ((symbol != '>') && (symbol > 0)) { + typeParams.add(parseFormalTypeParameter()); + } + expect('>'); + } + this.formalTypeParameters = typeParams.getArray(); + } + + ImplForVariable<GenericDeclaration> parseFormalTypeParameter() { + // FormalTypeParameter ::= Ident ClassBound {InterfaceBound}. + + scanIdentifier(); + String name = identifier.intern(); // FIXME: is this o.k.? + + ListOfTypes bounds = new ListOfTypes(8); + + // ClassBound ::= ":" [FieldTypeSignature]. + expect(':'); + if (symbol == 'L' || symbol == '[' || symbol == 'T') { + bounds.add(parseFieldTypeSignature()); + } + + while (symbol == ':') { + // InterfaceBound ::= ":" FieldTypeSignature. + scanSymbol(); + bounds.add(parseFieldTypeSignature()); + } + + return new ImplForVariable<GenericDeclaration>(genericDecl, name, bounds); + } + + Type parseFieldTypeSignature() { + // FieldTypeSignature ::= ClassTypeSignature | ArrayTypeSignature + // | TypeVariableSignature. + + switch (symbol) { + case 'L': + return parseClassTypeSignature(); + case '[': + // ArrayTypeSignature ::= "[" TypSignature. + scanSymbol(); + return new ImplForArray(parseTypeSignature()); + case 'T': + return parseTypeVariableSignature(); + default: + throw new GenericSignatureFormatError(); + } + } + + Type parseClassTypeSignature() { + // ClassTypeSignature ::= "L" {Ident "/"} Ident + // OptTypeArguments {"." Ident OptTypeArguments} ";". + + expect('L'); + + StringBuilder qualIdent = new StringBuilder(); + scanIdentifier(); + while (symbol == '/') { + scanSymbol(); + qualIdent.append(identifier).append("."); + scanIdentifier(); + } + + qualIdent.append(this.identifier); + + ListOfTypes typeArgs = parseOptTypeArguments(); + ImplForType parentType = + new ImplForType(null, qualIdent.toString(), typeArgs, loader); + ImplForType type = parentType; + + while (symbol == '.') { + // Deal with Member Classes: + scanSymbol(); + scanIdentifier(); + qualIdent.append("$").append(identifier); // FIXME: is "$" correct? + typeArgs = parseOptTypeArguments(); + type = new ImplForType(parentType, qualIdent.toString(), typeArgs, + loader); + } + + expect(';'); + + return type; + } + + ListOfTypes parseOptTypeArguments() { + // OptTypeArguments ::= "<" TypeArgument {TypeArgument} ">". + + ListOfTypes typeArgs = new ListOfTypes(8); + if (symbol == '<') { + scanSymbol(); + + typeArgs.add(parseTypeArgument()); + while ((symbol != '>') && (symbol > 0)) { + typeArgs.add(parseTypeArgument()); + } + expect('>'); + } + return typeArgs; + } + + Type parseTypeArgument() { + // TypeArgument ::= (["+" | "-"] FieldTypeSignature) | "*". + ListOfTypes extendsBound = new ListOfTypes(1); + ListOfTypes superBound = new ListOfTypes(1); + if (symbol == '*') { + scanSymbol(); + extendsBound.add(Object.class); + return new ImplForWildcard(extendsBound, superBound); + } + else if (symbol == '+') { + scanSymbol(); + extendsBound.add(parseFieldTypeSignature()); + return new ImplForWildcard(extendsBound, superBound); + } + else if (symbol == '-') { + scanSymbol(); + superBound.add(parseFieldTypeSignature()); + extendsBound.add(Object.class); + return new ImplForWildcard(extendsBound, superBound); + } + else { + return parseFieldTypeSignature(); + } + } + + ImplForVariable<GenericDeclaration> parseTypeVariableSignature() { + // TypeVariableSignature ::= "T" Ident ";". + expect('T'); + scanIdentifier(); + expect(';'); + // Reference to type variable: + // Note: we don't know the declaring GenericDeclaration yet. + return new ImplForVariable<GenericDeclaration>(genericDecl, identifier); + } + + Type parseTypeSignature() { + switch (symbol) { + case 'B': scanSymbol(); return byte.class; + case 'C': scanSymbol(); return char.class; + case 'D': scanSymbol(); return double.class; + case 'F': scanSymbol(); return float.class; + case 'I': scanSymbol(); return int.class; + case 'J': scanSymbol(); return long.class; + case 'S': scanSymbol(); return short.class; + case 'Z': scanSymbol(); return boolean.class; + default: + // Not an elementary type, but a FieldTypeSignature. + return parseFieldTypeSignature(); + } + } + + /** + * @param rawExceptionTypes the non-generic exceptions. This is necessary + * because the signature may omit the exceptions when none are generic. + * May be null for methods that declare no exceptions. + */ + void parseMethodTypeSignature(Class<?>[] rawExceptionTypes) { + // MethodTypeSignature ::= [FormalTypeParameters] + // "(" {TypeSignature} ")" ReturnType {ThrowsSignature}. + + parseOptFormalTypeParameters(); + + parameterTypes = new ListOfTypes(16); + expect('('); + while (symbol != ')' && (symbol > 0)) { + parameterTypes.add(parseTypeSignature()); + } + expect(')'); + + returnType = parseReturnType(); + + if (symbol == '^') { + exceptionTypes = new ListOfTypes(8); + do { + scanSymbol(); + + // ThrowsSignature ::= ("^" ClassTypeSignature) | + // ("^" TypeVariableSignature). + if (symbol == 'T') { + exceptionTypes.add(parseTypeVariableSignature()); + } else { + exceptionTypes.add(parseClassTypeSignature()); + } + } while (symbol == '^'); + } else if (rawExceptionTypes != null) { + exceptionTypes = new ListOfTypes(rawExceptionTypes); + } else { + exceptionTypes = new ListOfTypes(0); + } + } + + Type parseReturnType() { + // ReturnType ::= TypeSignature | "V". + if (symbol != 'V') { return parseTypeSignature(); } + else { scanSymbol(); return void.class; } + } + + + // + // Scanner: + // + + void scanSymbol() { + if (!eof) { + if (pos < buffer.length) { + symbol = buffer[pos]; + pos++; + } else { + symbol = 0; + eof = true; + } + } else { + throw new GenericSignatureFormatError(); + } + } + + void expect(char c) { + if (symbol == c) { + scanSymbol(); + } else { + throw new GenericSignatureFormatError(); + } + } + + boolean isStopSymbol(char ch) { + switch (ch) { + case ':': + case '/': + case ';': + case '<': + case '.': + return true; + } + return false; + } + + // PRE: symbol is the first char of the identifier. + // POST: symbol = the next symbol AFTER the identifier. + void scanIdentifier() { + if (!eof) { + StringBuilder identBuf = new StringBuilder(32); + if (!isStopSymbol(symbol)) { + identBuf.append(symbol); + do { + char ch = buffer[pos]; + if ((ch >= 'a') && (ch <= 'z') || (ch >= 'A') && (ch <= 'Z') + || !isStopSymbol(ch)) { + identBuf.append(buffer[pos]); + pos++; + } else { + identifier = identBuf.toString(); + scanSymbol(); + return; + } + } while (pos != buffer.length); + identifier = identBuf.toString(); + symbol = 0; + eof = true; + } else { + // Ident starts with incorrect char. + symbol = 0; + eof = true; + throw new GenericSignatureFormatError(); + } + } else { + throw new GenericSignatureFormatError(); + } + } + +} diff --git a/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ImplForArray.java b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ImplForArray.java new file mode 100644 index 0000000..c9b76b1 --- /dev/null +++ b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ImplForArray.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package org.apache.harmony.luni.lang.reflect; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Type; + +public final class ImplForArray implements GenericArrayType { + private final Type componentType; + + public ImplForArray(Type componentType) { + this.componentType = componentType; + } + + public Type getGenericComponentType() { + try { + return ((ImplForType)componentType).getResolvedType(); + } catch (ClassCastException e) { + return componentType; + } + } + + public String toString() { + return componentType.toString() + "[]"; + } +} diff --git a/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ImplForType.java b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ImplForType.java new file mode 100644 index 0000000..b1f05d4 --- /dev/null +++ b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ImplForType.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package org.apache.harmony.luni.lang.reflect; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public final class ImplForType implements ParameterizedType { + private final ListOfTypes args; + private final ImplForType ownerType0; // Potentially unresolved. + private Type ownerTypeRes; + private Class rawType; // Already resolved. + private final String rawTypeName; + private ClassLoader loader; + + + public ImplForType(ImplForType ownerType, String rawTypeName, + ListOfTypes args, ClassLoader loader) { + this.ownerType0 = ownerType; + this.rawTypeName = rawTypeName; + this.args = args; + this.loader = loader; + } + + + public Type[] getActualTypeArguments() { + // ASSUMPTION: args is never null!!! + return args.getResolvedTypes().clone(); + } + + public Type getOwnerType() { + if (ownerTypeRes == null) { + if (ownerType0 != null) { + ownerTypeRes = ownerType0.getResolvedType(); + } else { + ownerTypeRes = getRawType().getDeclaringClass(); + } + } + return ownerTypeRes; + } + + public Class getRawType() { + if (rawType == null) { + // Here the actual loading of the class has to be performed and the + // Exceptions have to be re-thrown TypeNotPresent... + // How to deal with member (nested) classes? + try { + rawType = Class.forName(rawTypeName, false, loader); + } catch (ClassNotFoundException e) { + throw new TypeNotPresentException(rawTypeName, e); + } + } + return rawType; + } + + + Type getResolvedType() { + if (args.getResolvedTypes().length == 0) { + return getRawType(); + } else { + return this; + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(rawTypeName); + if (args.length() > 0) { + sb.append("<").append(args).append(">"); + } + return sb.toString(); + } +} diff --git a/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ImplForVariable.java b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ImplForVariable.java new file mode 100644 index 0000000..783d223 --- /dev/null +++ b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ImplForVariable.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package org.apache.harmony.luni.lang.reflect; + +import java.lang.reflect.Constructor; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + + +public final class ImplForVariable<D extends GenericDeclaration> implements TypeVariable<D> { + private ImplForVariable<D> formalVar; + private final GenericDeclaration declOfVarUser; + private final String name; + private D genericDeclaration; + private ListOfTypes bounds; + + @Override + public boolean equals(Object o) { + if(!(o instanceof TypeVariable)) { + return false; + } + TypeVariable<?> that = (TypeVariable<?>) o; + return getName().equals(that.getName()) && + getGenericDeclaration().equals(that.getGenericDeclaration()); + } + + + @Override + public int hashCode() { + return 31 * getName().hashCode() + getGenericDeclaration().hashCode(); + } + + /** + * @param genericDecl declaration where a type variable is declared + * @param name type variable name + * @param bounds class and interface bounds + */ + ImplForVariable(D genericDecl, String name, ListOfTypes bounds) { + this.genericDeclaration = genericDecl; + this.name = name; + this.bounds = bounds; + this.formalVar = this; + this.declOfVarUser = null; + } + + /** + * @param genericDecl declaration where a type variable is used + * @param name type variable name + */ + ImplForVariable(D genericDecl, String name) { + this.name = name; + this.declOfVarUser = genericDecl; + } + + static TypeVariable findFormalVar(GenericDeclaration layer, String name) { + TypeVariable[] formalVars = layer.getTypeParameters(); + for (TypeVariable var : formalVars) { + if (name.equals(var.getName())) { + return var; + } + } + // resolve() looks up the next level only, if null is returned + return null; + } + + private static GenericDeclaration nextLayer(GenericDeclaration decl) { + if (decl instanceof Class) { + // FIXME: Is the following hierarchy correct?: + Class cl = (Class)decl; + decl = cl.getEnclosingMethod(); + if (decl != null) { + return decl; + } + decl = cl.getEnclosingConstructor(); + if (decl != null) { + return decl; + } + return cl.getEnclosingClass(); + } else if (decl instanceof Method) { + return ((Method)decl).getDeclaringClass(); + } else if (decl instanceof Constructor) { + return ((Constructor)decl).getDeclaringClass(); + } else { + throw new AssertionError(); + } + } + + void resolve() { + if (formalVar != null) { + return; + } + GenericDeclaration curLayer = declOfVarUser; + TypeVariable var; + while ((var = findFormalVar(curLayer, name)) == null) { + curLayer = nextLayer(curLayer); + if (curLayer == null) { + throw new AssertionError("illegal type variable reference"); + } + } + formalVar = (ImplForVariable<D>) var; + this.genericDeclaration = formalVar.genericDeclaration; + this.bounds = formalVar.bounds; + } + + public Type[] getBounds() { + resolve(); + return bounds.getResolvedTypes().clone(); + } + + public D getGenericDeclaration() { + resolve(); + return genericDeclaration; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } +} diff --git a/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ImplForWildcard.java b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ImplForWildcard.java new file mode 100644 index 0000000..b0605f2 --- /dev/null +++ b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ImplForWildcard.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package org.apache.harmony.luni.lang.reflect; + +import java.lang.reflect.MalformedParameterizedTypeException; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.util.Arrays; + +public final class ImplForWildcard implements WildcardType { + + private final ListOfTypes extendsBound, superBound; + + public ImplForWildcard(ListOfTypes extendsBound, ListOfTypes superBound) { + this.extendsBound = extendsBound; + this.superBound = superBound; + } + + public Type[] getLowerBounds() throws TypeNotPresentException, + MalformedParameterizedTypeException { + return superBound.getResolvedTypes().clone(); + } + + public Type[] getUpperBounds() throws TypeNotPresentException, + MalformedParameterizedTypeException { + return extendsBound.getResolvedTypes().clone(); + } + + @Override + public boolean equals(Object o) { + if(!(o instanceof WildcardType)) { + return false; + } + WildcardType that = (WildcardType) o; + return Arrays.equals(getLowerBounds(), that.getLowerBounds()) && + Arrays.equals(getUpperBounds(), that.getUpperBounds()); + } + + @Override + public int hashCode() { + return 31 * Arrays.hashCode(getLowerBounds()) + + Arrays.hashCode(getUpperBounds()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("?"); + if ((extendsBound.length() == 1 && extendsBound.getResolvedTypes()[0] != Object.class) + || extendsBound.length() > 1) { + sb.append(" extends ").append(extendsBound); + } else if (superBound.length() > 0) { + sb.append(" super ").append(superBound); + } + return sb.toString(); + } +} diff --git a/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ListOfTypes.java b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ListOfTypes.java new file mode 100644 index 0000000..59470f5 --- /dev/null +++ b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ListOfTypes.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package org.apache.harmony.luni.lang.reflect; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public final class ListOfTypes { + public static final ListOfTypes EMPTY = new ListOfTypes(0); + + private final ArrayList<Type> types; + private Type[] resolvedTypes; + + ListOfTypes(int capacity) { + types = new ArrayList<Type>(capacity); + } + + ListOfTypes(Type[] types) { + this.types = new ArrayList<Type>(types.length); + for (Type type : types) { + this.types.add(type); + } + } + + void add(Type type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + types.add(type); + } + + int length() { + return types.size(); + } + + public Type[] getResolvedTypes() { + Type[] result = resolvedTypes; + return result != null ? result : (resolvedTypes = resolveTypes(types)); + } + + private Type[] resolveTypes(List<Type> unresolved) { + Type[] result = new Type[unresolved.size()]; + for (int i = 0; i < unresolved.size(); i++) { + Type type = unresolved.get(i); + try { + result[i] = ((ImplForType) type).getResolvedType(); + } catch (ClassCastException e) { + result[i] = type; + } + } + return result; + } + + @Override public String toString() { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < types.size(); i++) { + if (i > 0) { + result.append(", "); + } + result.append(types.get(i)); + } + return result.toString(); + } +} diff --git a/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ListOfVariables.java b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ListOfVariables.java new file mode 100644 index 0000000..0e757ac --- /dev/null +++ b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/ListOfVariables.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package org.apache.harmony.luni.lang.reflect; + +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; + +class ListOfVariables { + public static final TypeVariable[] EMPTY = new ImplForVariable[0]; + + ArrayList<TypeVariable<?>> array = new ArrayList<TypeVariable<?>>(); + + void add (TypeVariable<?> elem) { + array.add(elem); + } + + TypeVariable<?>[] getArray() { + TypeVariable<?>[] a = new TypeVariable[array.size()]; + return array.toArray(a); + } +} diff --git a/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/Types.java b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/Types.java new file mode 100644 index 0000000..bd700fc --- /dev/null +++ b/libdvm/src/main/java/org/apache/harmony/luni/lang/reflect/Types.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package org.apache.harmony.luni.lang.reflect; + +import java.lang.reflect.Type; + +public class Types { + + public static Type[] getClonedTypeArray(ListOfTypes types) { + return types.getResolvedTypes().clone(); + } + + public static Type getType(Type type) { + if (type instanceof ImplForType) { + return ((ImplForType)type).getResolvedType(); + } else { + return type; + } + } +} diff --git a/libdvm/src/main/java/sun/misc/Unsafe.java b/libdvm/src/main/java/sun/misc/Unsafe.java new file mode 100644 index 0000000..884340b --- /dev/null +++ b/libdvm/src/main/java/sun/misc/Unsafe.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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. + */ + +package sun.misc; + +import dalvik.system.VMStack; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * The package name notwithstanding, this class is the quasi-standard + * way for Java code to gain access to and use functionality which, + * when unsupervised, would allow one to break the pointer/type safety + * of Java. + */ +public final class Unsafe { + /** Traditional dalvik name. */ + private static final Unsafe THE_ONE = new Unsafe(); + /** Traditional RI name. */ + private static final Unsafe theUnsafe = THE_ONE; + + /** + * This class is only privately instantiable. + */ + private Unsafe() {} + + /** + * Gets the unique instance of this class. This is only allowed in + * very limited situations. + */ + public static Unsafe getUnsafe() { + /* + * Only code on the bootclasspath is allowed to get at the + * Unsafe instance. + */ + ClassLoader calling = VMStack.getCallingClassLoader(); + if ((calling != null) && (calling != Unsafe.class.getClassLoader())) { + throw new SecurityException("Unsafe access denied"); + } + + return THE_ONE; + } + + /** + * Gets the raw byte offset from the start of an object's memory to + * the memory used to store the indicated instance field. + * + * @param field non-null; the field in question, which must be an + * instance field + * @return the offset to the field + */ + public long objectFieldOffset(Field field) { + if (Modifier.isStatic(field.getModifiers())) { + throw new IllegalArgumentException( + "valid for instance fields only"); + } + + return objectFieldOffset0(field); + } + + /** + * Helper for {@link #objectFieldOffset}, which does all the work, + * assuming the parameter is deemed valid. + * + * @param field non-null; the instance field + * @return the offset to the field + */ + private static native long objectFieldOffset0(Field field); + + /** + * Gets the offset from the start of an array object's memory to + * the memory used to store its initial (zeroeth) element. + * + * @param clazz non-null; class in question; must be an array class + * @return the offset to the initial element + */ + public int arrayBaseOffset(Class clazz) { + if (! clazz.isArray()) { + throw new IllegalArgumentException( + "valid for array classes only"); + } + + return arrayBaseOffset0(clazz); + } + + /** + * Helper for {@link #arrayBaseOffset}, which does all the work, + * assuming the parameter is deemed valid. + * + * @return the offset to the field + */ + private static native int arrayBaseOffset0(Class clazz); + + /** + * Gets the size of each element of the given array class. + * + * @param clazz non-null; class in question; must be an array class + * @return > 0; the size of each element of the array + */ + public int arrayIndexScale(Class clazz) { + if (! clazz.isArray()) { + throw new IllegalArgumentException( + "valid for array classes only"); + } + + return arrayIndexScale0(clazz); + } + + /** + * Helper for {@link #arrayIndexScale}, which does all the work, + * assuming the parameter is deemed valid. + * + * @return the offset to the field + */ + private static native int arrayIndexScale0(Class clazz); + + /** + * Performs a compare-and-set operation on an <code>int</code> + * field within the given object. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @param expectedValue expected value of the field + * @param newValue new value to store in the field if the contents are + * as expected + * @return <code>true</code> if the new value was in fact stored, and + * <code>false</code> if not + */ + public native boolean compareAndSwapInt(Object obj, long offset, + int expectedValue, int newValue); + + /** + * Performs a compare-and-set operation on a <code>long</code> + * field within the given object. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @param expectedValue expected value of the field + * @param newValue new value to store in the field if the contents are + * as expected + * @return <code>true</code> if the new value was in fact stored, and + * <code>false</code> if not + */ + public native boolean compareAndSwapLong(Object obj, long offset, + long expectedValue, long newValue); + + /** + * Performs a compare-and-set operation on an <code>Object</code> + * field (that is, a reference field) within the given object. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @param expectedValue expected value of the field + * @param newValue new value to store in the field if the contents are + * as expected + * @return <code>true</code> if the new value was in fact stored, and + * <code>false</code> if not + */ + public native boolean compareAndSwapObject(Object obj, long offset, + Object expectedValue, Object newValue); + + /** + * Gets an <code>int</code> field from the given object, + * using <code>volatile</code> semantics. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @return the retrieved value + */ + public native int getIntVolatile(Object obj, long offset); + + /** + * Stores an <code>int</code> field into the given object, + * using <code>volatile</code> semantics. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @param newValue the value to store + */ + public native void putIntVolatile(Object obj, long offset, int newValue); + + /** + * Gets a <code>long</code> field from the given object, + * using <code>volatile</code> semantics. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @return the retrieved value + */ + public native long getLongVolatile(Object obj, long offset); + + /** + * Stores a <code>long</code> field into the given object, + * using <code>volatile</code> semantics. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @param newValue the value to store + */ + public native void putLongVolatile(Object obj, long offset, long newValue); + + /** + * Gets an <code>Object</code> field from the given object, + * using <code>volatile</code> semantics. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @return the retrieved value + */ + public native Object getObjectVolatile(Object obj, long offset); + + /** + * Stores an <code>Object</code> field into the given object, + * using <code>volatile</code> semantics. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @param newValue the value to store + */ + public native void putObjectVolatile(Object obj, long offset, + Object newValue); + + /** + * Gets an <code>int</code> field from the given object. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @return the retrieved value + */ + public native int getInt(Object obj, long offset); + + /** + * Stores an <code>int</code> field into the given object. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @param newValue the value to store + */ + public native void putInt(Object obj, long offset, int newValue); + + /** + * Lazy set an int field. + */ + public native void putOrderedInt(Object obj, long offset, int newValue); + + /** + * Gets a <code>long</code> field from the given object. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @return the retrieved value + */ + public native long getLong(Object obj, long offset); + + /** + * Stores a <code>long</code> field into the given object. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @param newValue the value to store + */ + public native void putLong(Object obj, long offset, long newValue); + + /** + * Lazy set a long field. + */ + public native void putOrderedLong(Object obj, long offset, long newValue); + + /** + * Gets an <code>Object</code> field from the given object. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @return the retrieved value + */ + public native Object getObject(Object obj, long offset); + + /** + * Stores an <code>Object</code> field into the given object. + * + * @param obj non-null; object containing the field + * @param offset offset to the field within <code>obj</code> + * @param newValue the value to store + */ + public native void putObject(Object obj, long offset, Object newValue); + + /** + * Lazy set an object field. + */ + public native void putOrderedObject(Object obj, long offset, + Object newValue); + + /** + * Parks the calling thread for the specified amount of time, + * unless the "permit" for the thread is already available (due to + * a previous call to {@link #unpark}. This method may also return + * spuriously (that is, without the thread being told to unpark + * and without the indicated amount of time elapsing). + * + * <p>See {@link java.util.concurrent.locks.LockSupport} for more + * in-depth information of the behavior of this method.</p> + * + * @param absolute whether the given time value is absolute + * milliseconds-since-the-epoch (<code>true</code>) or relative + * nanoseconds-from-now (<code>false</code>) + * @param time the (absolute millis or relative nanos) time value + */ + public void park(boolean absolute, long time) { + if (absolute) { + Thread.currentThread().parkUntil(time); + } else { + Thread.currentThread().parkFor(time); + } + } + + /** + * Unparks the given object, which must be a {@link Thread}. + * + * <p>See {@link java.util.concurrent.locks.LockSupport} for more + * in-depth information of the behavior of this method.</p> + * + * @param obj non-null; the object to unpark + */ + public void unpark(Object obj) { + if (obj instanceof Thread) { + ((Thread) obj).unpark(); + } else { + throw new IllegalArgumentException("valid for Threads only"); + } + } + + /** + * Allocates an instance of the given class without running the constructor. + * The class' <clinit> will be run, if necessary. + */ + public native Object allocateInstance(Class<?> c); +} |