diff options
author | Brian Carlstrom <bdc@google.com> | 2013-05-22 11:05:31 -0700 |
---|---|---|
committer | Brian Carlstrom <bdc@google.com> | 2013-05-22 11:12:51 -0700 |
commit | 2cf03dc15c40b92634ff606694af5a6e9aa4db09 (patch) | |
tree | 35e908dd1d72be7f2adf401360aea9646760623c | |
parent | 2efa673c6b6408b306b0372fdf37d232b29c2526 (diff) | |
download | libcore-2cf03dc15c40b92634ff606694af5a6e9aa4db09.zip libcore-2cf03dc15c40b92634ff606694af5a6e9aa4db09.tar.gz libcore-2cf03dc15c40b92634ff606694af5a6e9aa4db09.tar.bz2 |
Adding libart support to libcore
Change-Id: I86febf08eacf42bb4b2f06dbd51c5b2d5b25c9fb
24 files changed, 10950 insertions, 0 deletions
diff --git a/JavaLibrary.mk b/JavaLibrary.mk index a28e609..0e57401 100644 --- a/JavaLibrary.mk +++ b/JavaLibrary.mk @@ -51,7 +51,13 @@ endef # The Java files and their associated resources. core_src_files := $(call all-main-java-files-under,dalvik dex dom json luni support xml) + +ifeq ($(WITH_ART),false) core_src_files += $(call all-main-java-files-under,libdvm) +else +core_src_files += $(call all-main-java-files-under,libart) +endif + core_resource_dirs := $(call all-core-resource-dirs,main) test_resource_dirs := $(call all-core-resource-dirs,test) diff --git a/libart/src/main/java/dalvik/system/BaseDexClassLoader.java b/libart/src/main/java/dalvik/system/BaseDexClassLoader.java new file mode 100644 index 0000000..6a1a493 --- /dev/null +++ b/libart/src/main/java/dalvik/system/BaseDexClassLoader.java @@ -0,0 +1,139 @@ +/* + * 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.ArrayList; +import java.util.Enumeration; +import java.util.List; + +/** + * 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 { + List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); + Class c = pathList.findClass(name, suppressedExceptions); + if (c == null) { + ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); + for (Throwable t : suppressedExceptions) { + cnfe.addSuppressed(t); + } + throw cnfe; + } + 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/libart/src/main/java/dalvik/system/DexFile.java b/libart/src/main/java/dalvik/system/DexFile.java new file mode 100644 index 0000000..06b834a --- /dev/null +++ b/libart/src/main/java/dalvik/system/DexFile.java @@ -0,0 +1,318 @@ +/* + * 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.ArrayList; +import java.util.Enumeration; +import java.util.List; +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 { + if (mCookie != 0) { + 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, null); + } + + /** + * See {@link #loadClass(String, ClassLoader)}. + * + * This takes a "binary" class name to better match ClassLoader semantics. + * + * @hide + */ + public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) { + return defineClass(name, loader, mCookie, suppressed); + } + + private static Class defineClass(String name, ClassLoader loader, int cookie, + List<Throwable> suppressed) { + Class result = null; + try { + result = defineClassNative(name, loader, cookie); + } catch (NoClassDefFoundError e) { + if (suppressed != null) { + suppressed.add(e); + } + } catch (ClassNotFoundException e) { + if (suppressed != null) { + suppressed.add(e); + } + } + return result; + } + + private static native Class defineClassNative(String name, ClassLoader loader, int cookie) + throws ClassNotFoundException, NoClassDefFoundError; + + /** + * 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/libart/src/main/java/dalvik/system/DexPathList.java b/libart/src/main/java/dalvik/system/DexPathList.java new file mode 100644 index 0000000..5518c83 --- /dev/null +++ b/libart/src/main/java/dalvik/system/DexPathList.java @@ -0,0 +1,451 @@ +/* + * 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.List; +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 oat file. + */ + private static String optimizedPathFor(File path, + File optimizedDirectory) { + String fileName = path.getName() + ".oat"; + 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. + * + * @param name of class to find + * @param suppressed exceptions encountered whilst finding the class + * @return the named class or {@code null} if the class is not + * found in any of the dex files + */ + public Class findClass(String name, List<Throwable> suppressed) { + for (Element element : dexElements) { + DexFile dex = element.dexFile; + + if (dex != null) { + Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); + 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/libart/src/main/java/java/lang/Class.java b/libart/src/main/java/java/lang/Class.java new file mode 100644 index 0000000..9e3f74c --- /dev/null +++ b/libart/src/main/java/java/lang/Class.java @@ -0,0 +1,1630 @@ +/* + * 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 com.android.dex.Dex; +import dalvik.system.VMStack; +import java.io.InputStream; +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.AbstractMethod; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.InvocationTargetException; +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.List; +import libcore.reflect.AnnotationAccess; +import libcore.reflect.GenericSignatureParser; +import libcore.reflect.InternalNames; +import libcore.reflect.Types; +import libcore.util.CollectionUtils; +import libcore.util.EmptyArray; + +/** + * 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>descriptor</em>, which is the letter "L", followed by the + * class name and a semicolon (";"). The descriptor 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 descriptors 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 descriptor of the class representing the leaf component type, + * which can be either an object type or a primitive type. The descriptor of a + * {@code Class} representing an array type is the same as its name. Examples + * of array class descriptors 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; + + /** defining class loader, or NULL for the "bootstrap" system loader. */ + private transient ClassLoader classLoader; + + /** + * For array classes, the component class object for instanceof/checkcast (for String[][][], + * this will be String[][]). NULL for non-array classes. + */ + private transient Class<?> componentType; + /** + * DexCache of resolved constant pool entries. Will be null for certain VM-generated classes + * e.g. arrays and primitive classes. + */ + private transient DexCache dexCache; + + /** static, private, and <init> methods. */ + private transient AbstractMethod[] directMethods; + + /** + * Instance fields. These describe the layout of the contents of an Object. Note that only the + * fields directly declared by this class are listed in iFields; fields declared by a + * superclass are listed in the superclass's Class.iFields. + * + * All instance fields that refer to objects are guaranteed to be at the beginning of the field + * list. {@link Class#numReferenceInstanceFields} specifies the number of reference fields. + */ + private transient Field[] iFields; + + /** + * The interface table (iftable_) contains pairs of a interface class and an array of the + * interface methods. There is one pair per interface supported by this class. That + * means one pair for each interface we support directly, indirectly via superclass, or + * indirectly via a superinterface. This will be null if neither we nor our superclass + * implement any interfaces. + * + * Why we need this: given "class Foo implements Face", declare "Face faceObj = new Foo()". + * Invoke faceObj.blah(), where "blah" is part of the Face interface. We can't easily use a + * single vtable. + * + * For every interface a concrete class implements, we create an array of the concrete vtable_ + * methods for the methods in the interface. + */ + private transient Object[] ifTable; + + /** Lazily computed name of this class; always prefer calling getName(). */ + private transient String name; + + /** Static fields */ + private transient Field[] sFields; + + /** The superclass, or NULL if this is java.lang.Object, an interface or primitive type. */ + private transient Class<? super T> superClass; + + /** If class verify fails, we must return same error on subsequent tries. */ + private transient Class<?> verifyErrorClass; + + /** Virtual methods defined in this class; invoked through vtable. */ + private transient Method[] virtualMethods; + + /** + * Virtual method table (vtable), for use by "invoke-virtual". The vtable from the superclass + * is copied in, and virtual methods from our class either replace those from the super or are + * appended. For abstract classes, methods may be created in the vtable that aren't in + * virtual_ methods_ for miranda methods. + */ + private transient Method[] vtable; + + /** access flags; low 16 bits are defined by VM spec */ + private transient int accessFlags; + + /** + * Total size of the Class instance; used when allocating storage on GC heap. + * See also {@link Class#objectSize}. + */ + private transient int classSize; + + /** + * tid used to check for recursive static initializer invocation. + */ + private transient int clinitThreadId; + + /** + * Type index from dex file. + * TODO: really 16bits + */ + private transient int dexTypeIndex; + + /** Number of instance fields that are object references. */ + private transient int numReferenceInstanceFields; + + /** Number of static fields that are object references. */ + private transient int numReferenceStaticFields; + + /** + * Total object size; used when allocating storage on GC heap. For interfaces and abstract + * classes this will be zero. See also {@link Class#classSize}. + */ + private transient int objectSize; + + /** Primitive type value, or 0 if not a primitive type; set for generated primitive classes. */ + private transient int primitiveType; + + /** Bitmap of offsets of iFields. */ + private transient int referenceInstanceOffsets; + + /** Bitmap of offsets of sFields. */ + private transient int referenceStaticOffsets; + + /** State of class initialization */ + private transient int status; + + private Class() { + // Prevent this class to be instantiated, instance should be created by JVM only + } + + /** + * 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 cannot 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 cannot 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 LinkageError) { + throw (LinkageError) cause; + } + throw e; + } + return result; + } + + static native Class<?> classForName(String className, boolean shouldInitialize, + ClassLoader classLoader) throws ClassNotFoundException; + + /** + * Returns an array containing {@code Class} objects for all public classes, + * interfaces, enums and annotations that are members of this class and its + * superclasses. This does not include classes of implemented 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() { + List<Class<?>> result = new ArrayList<Class<?>>(); + for (Class<?> c = this; c != null; c = c.superClass) { + for (Class<?> member : c.getDeclaredClasses()) { + if (Modifier.isPublic(member.getModifiers())) { + result.add(member); + } + } + } + return result.toArray(new Class[result.size()]); + } + + @Override public <A extends Annotation> A getAnnotation(Class<A> annotationType) { + return AnnotationAccess.getAnnotation(this, annotationType); + } + + /** + * Returns an array containing all the annotations of this class. If there are no annotations + * then an empty array is returned. + * + * @see #getDeclaredAnnotations() + */ + @Override public Annotation[] getAnnotations() { + return AnnotationAccess.getAnnotations(this); + } + + /** + * 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 = classLoader; + return loader == null ? BootClassLoader.getInstance() : loader; + } + + /** + * 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 Class<?> getComponentType() { + return componentType; + } + + /** + * Returns the dex file from which this class was loaded. + * + * @hide + */ + public native Dex getDex(); + + /** + * Returns a string from the dex cache, computing the string from the dex file if necessary + * + * @hide + */ + public String getDexCacheString(Dex dex, int dexStringIndex) { + String[] dexCacheStrings = dexCache.strings; + String s = dexCacheStrings[dexStringIndex]; + if (s == null) { + s = dex.strings().get(dexStringIndex); + dexCacheStrings[dexStringIndex] = s; + } + return s; + } + + /** + * Returns a resolved type from the dex cache, computing the string from the dex file if + * necessary + * + * @hide + */ + public Class<?> getDexCacheType(Dex dex, int dexTypeIndex) { + Class<?>[] dexCacheResolvedTypes = dexCache.resolvedTypes; + Class<?> resolvedType = dexCacheResolvedTypes[dexTypeIndex]; + if (resolvedType == null) { + int descriptorIndex = dex.typeIds().get(dexTypeIndex); + String descriptor = getDexCacheString(dex, descriptorIndex); + resolvedType = InternalNames.getClass(getClassLoader(), descriptor); + dexCacheResolvedTypes[dexTypeIndex] = resolvedType; + } + return resolvedType; + } + + /** + * 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 cannot be found. + * @see #getDeclaredConstructor(Class[]) + */ + public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException { + return getConstructor(parameterTypes, true); + } + + /** + * Returns a {@code Constructor} object which represents the constructor + * matching the specified 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 cannot be found. + * @see #getConstructor(Class[]) + */ + public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) + throws NoSuchMethodException { + return getConstructor(parameterTypes, false); + } + + /** + * Returns a constructor with the given parameters. + * + * @param publicOnly true to only return public constructores. + * @param parameterTypes argument types to match the constructor's. + */ + private Constructor<T> getConstructor(Class<?>[] parameterTypes, boolean publicOnly) + throws NoSuchMethodException { + if (parameterTypes == null) { + parameterTypes = EmptyArray.CLASS; + } + for (Class<?> c : parameterTypes) { + if (c == null) { + throw new NoSuchMethodException("parameter type is null"); + } + } + Constructor<T> result = getDeclaredConstructorInternal(parameterTypes); + if (result == null || publicOnly && !Modifier.isPublic(result.getAccessFlags())) { + throw new NoSuchMethodException("<init> " + Arrays.toString(parameterTypes)); + } + return result; + } + + /** + * Returns the constructor with the given parameters if it is defined by this class; null + * otherwise. This may return a non-public member. + * + * @param args the types of the parameters to the constructor. + */ + private Constructor<T> getDeclaredConstructorInternal(Class<?>[] args) { + if (directMethods != null) { + for (AbstractMethod m : directMethods) { + if (m instanceof Constructor && Arrays.equals(args, m.getParameterTypes())) { + return (Constructor<T>) m; + } + } + } + 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() { + ArrayList<Constructor<T>> constructors = new ArrayList(); + getDeclaredConstructors(true, constructors); + return constructors.toArray(new Constructor[constructors.size()]); + } + + /** + * 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() { + ArrayList<Constructor<T>> constructors = new ArrayList(); + getDeclaredConstructors(false, constructors); + return constructors.toArray(new Constructor[constructors.size()]); + } + + private void getDeclaredConstructors(boolean publicOnly, List<Constructor<T>> constructors) { + if (directMethods != null) { + for (AbstractMethod m : directMethods) { + int modifiers = m.getAccessFlags(); + if (!publicOnly || Modifier.isPublic(modifiers)) { + if (m instanceof Constructor) { + constructors.add((Constructor<T>) m); + } + } + } + } + } + + /** + * Returns a {@code Method} object which represents the method matching the + * specified name and parameter types that is declared by the class + * represented by this {@code Class}. + * + * @param name + * the requested method's name. + * @param parameterTypes + * the parameter types of the requested method. + * {@code (Class[]) null} is equivalent to the empty array. + * @return the method described by {@code name} and {@code parameterTypes}. + * @throws NoSuchMethodException + * if the requested constructor cannot be found. + * @throws NullPointerException + * if {@code name} is {@code null}. + * @see #getMethod(String, Class[]) + */ + public Method getDeclaredMethod(String name, Class<?>... parameterTypes) + throws NoSuchMethodException { + return getMethod(name, parameterTypes, false); + } + + /** + * Returns a {@code Method} object which represents the public method with + * the specified 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 cannot be found. + * @see #getDeclaredMethod(String, Class[]) + */ + public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException { + return getMethod(name, parameterTypes, true); + } + + private Method getMethod(String name, Class<?>[] parameterTypes, boolean recursivePublicMethods) + throws NoSuchMethodException { + 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"); + } + } + Method result = recursivePublicMethods ? getPublicMethodRecursive(name, parameterTypes) + : getDeclaredMethodInternal(name, parameterTypes); + // Fail if we didn't find the method or it was expected to be public. + if (result == null || + (recursivePublicMethods && !Modifier.isPublic(result.getAccessFlags()))) { + throw new NoSuchMethodException(name + " " + Arrays.toString(parameterTypes)); + } + return result; + } + + private Method getPublicMethodRecursive(String name, Class<?>[] parameterTypes) { + // search superclasses + for (Class<?> c = this; c != null; c = c.getSuperclass()) { + Method result = c.getDeclaredMethodInternal(name, parameterTypes); + if (result != null && Modifier.isPublic(result.getAccessFlags())) { + return result; + } + } + // search iftable which has a flattened and uniqued list of interfaces + Object[] iftable = ifTable; + if (iftable != null) { + for (int i = 0; i < iftable.length; i += 2) { + Class<?> ifc = (Class<?>) iftable[i]; + Method result = ifc.getPublicMethodRecursive(name, parameterTypes); + if (result != null && Modifier.isPublic(result.getAccessFlags())) { + return result; + } + } + } + return null; + } + + /** + * Returns the method if it is defined by this class; null otherwise. This may return a + * non-public member. + * + * @param name the method name + * @param args the method's parameter types + */ + private Method getDeclaredMethodInternal(String name, Class<?>[] args) { + // Covariant return types permit the class to define multiple + // methods with the same name and parameter types. Prefer to + // return a non-synthetic method in such situations. We may + // still return a synthetic method to handle situations like + // escalated visibility. We never return miranda methods that + // were synthesized by the VM. + int skipModifiers = Modifier.MIRANDA | Modifier.SYNTHETIC; + AbstractMethod result = null; + if (directMethods != null) { + for (AbstractMethod m : directMethods) { + if (name.equals(m.getName()) && Arrays.equals(args, m.getParameterTypes()) && + m instanceof Method) { + int modifiers = m.getAccessFlags(); + if ((modifiers & skipModifiers) == 0) { + return (Method) m; + } else { + // Direct methods cannot be miranda methods, + // so this potential result must be synthetic. + result = m; + } + } + } + } + if (result == null) { + if (virtualMethods != null) { + for (Method m : virtualMethods) { + if (name.equals(m.getName()) && Arrays.equals(args, m.getParameterTypes())) { + int modifiers = m.getAccessFlags(); + if ((modifiers & skipModifiers) == 0) { + return (Method) m; + } else if ((modifiers & Modifier.MIRANDA) == 0) { + // Remember as potential result if it's not a miranda method. + result = m; + } + } + } + } + } + return (Method) result; + } + + /** + * 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() { + int initial_size = virtualMethods == null ? 0 : virtualMethods.length; + initial_size += directMethods == null ? 0 : directMethods.length; + ArrayList<Method> methods = new ArrayList<Method>(initial_size); + getDeclaredMethods(false, methods); + Method[] result = methods.toArray(new Method[methods.size()]); + for (Method m : result) { + // Throw NoClassDefFoundError if types cannot be resolved. + m.getReturnType(); + m.getParameterTypes(); + } + return result; + + } + + /** + * Returns the list of methods without performing any security checks + * first. If no methods exist, an empty array is returned. + */ + private void getDeclaredMethods(boolean publicOnly, List<Method> methods) { + if (virtualMethods != null) { + for (Method m : virtualMethods) { + int modifiers = m.getAccessFlags(); + if (!publicOnly || Modifier.isPublic(modifiers)) { + // Add non-miranda virtual methods. + if ((modifiers & Modifier.MIRANDA) == 0) { + methods.add((Method) m); + } + } + } + } + if (directMethods != null) { + for (AbstractMethod m : directMethods) { + int modifiers = m.getAccessFlags(); + if (!publicOnly || Modifier.isPublic(modifiers)) { + // Add non-constructor direct/static methods. + if (m instanceof Method) { + methods.add((Method) m); + } + } + } + } + } + + /** + * 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>(); + getPublicMethodsInternal(methods); + /* + * Remove duplicate methods defined by superclasses and + * interfaces, 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 getPublicMethodsInternal(List<Method> result) { + getDeclaredMethods(true, result); + if (!isInterface()) { + // Search superclasses, for interfaces don't search java.lang.Object. + for (Class<?> c = superClass; c != null; c = c.superClass) { + c.getDeclaredMethods(true, result); + } + } + // Search iftable which has a flattened and uniqued list of interfaces. + Object[] iftable = ifTable; + if (iftable != null) { + for (int i = 0; i < iftable.length; i += 2) { + Class<?> ifc = (Class<?>) iftable[i]; + ifc.getDeclaredMethods(true, result); + } + } + } + + /** + * 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() + */ + @Override public Annotation[] getDeclaredAnnotations() { + List<Annotation> result = AnnotationAccess.getDeclaredAnnotations(this); + return result.toArray(new Annotation[result.size()]); + } + + /** + * Returns an array containing {@code Class} objects for all classes, + * interfaces, enums and annotations that are members of this class. + */ + public Class<?>[] getDeclaredClasses() { + return AnnotationAccess.getMemberClasses(this); + } + + /** + * 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 = getDeclaredFieldInternal(name); + if (result == null) { + throw new NoSuchFieldException(name); + } else { + result.getType(); // Throw NoClassDefFoundError if type cannot be resolved. + } + 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() { + int initial_size = sFields == null ? 0 : sFields.length; + initial_size += iFields == null ? 0 : iFields.length; + ArrayList<Field> fields = new ArrayList(initial_size); + getDeclaredFields(false, fields); + Field[] result = fields.toArray(new Field[fields.size()]); + for (Field f : result) { + f.getType(); // Throw NoClassDefFoundError if type cannot be resolved. + } + return result; + } + + private void getDeclaredFields(boolean publicOnly, List<Field> fields) { + if (iFields != null) { + for (Field f : iFields) { + if (!publicOnly || Modifier.isPublic(f.getModifiers())) { + fields.add(f); + } + } + } + if (sFields != null) { + for (Field f : sFields) { + if (!publicOnly || Modifier.isPublic(f.getModifiers())) { + fields.add(f); + } + } + } + } + + /** + * Returns the field if it is defined by this class; null otherwise. This + * may return a non-public member. + */ + private Field getDeclaredFieldInternal(String name) { + if (iFields != null) { + for (Field f : iFields) { + if (f.getName().equals(name)) { + return f; + } + } + } + if (sFields != null) { + for (Field f : sFields) { + if (f.getName().equals(name)) { + return f; + } + } + } + return null; + } + + /** + * Returns the class that this class is a member of, or {@code null} if this + * class is a top-level class, a primitive, an array, or defined within a + * method or constructor. + */ + public Class<?> getDeclaringClass() { + return AnnotationAccess.getDeclaringClass(this); + } + + /** + * Returns the class enclosing this class. For most classes this is the same + * as the {@link #getDeclaringClass() declaring class}. For classes defined + * within a method or constructor (typically anonymous inner classes), this + * is the declaring class of that member. + */ + public Class<?> getEnclosingClass() { + Class<?> declaringClass = getDeclaringClass(); + if (declaringClass != null) { + return declaringClass; + } + AccessibleObject member = AnnotationAccess.getEnclosingMethodOrConstructor(this); + return member != null + ? ((Member) member).getDeclaringClass() + : null; + } + + /** + * Returns the enclosing {@code Constructor} of this {@code Class}, if it is an + * anonymous or local/automatic class; otherwise {@code null}. + */ + public Constructor<?> getEnclosingConstructor() { + if (classNameImpliesTopLevel()) { + return null; + } + AccessibleObject result = AnnotationAccess.getEnclosingMethodOrConstructor(this); + return result instanceof Constructor ? (Constructor<?>) result : null; + } + + /** + * Returns the enclosing {@code Method} of this {@code Class}, if it is an + * anonymous or local/automatic class; otherwise {@code null}. + */ + public Method getEnclosingMethod() { + if (classNameImpliesTopLevel()) { + return null; + } + AccessibleObject result = AnnotationAccess.getEnclosingMethodOrConstructor(this); + return result instanceof Method ? (Method) result : null; + } + + /** + * Returns true if this class is definitely a top level class, or false if + * a more expensive check like {@link #getEnclosingClass()} is necessary. + * + * <p>This is a hack that exploits an implementation detail of all Java + * language compilers: generated names always contain "$". As it is possible + * for a top level class to be named with a "$", a false result <strong>does + * not</strong> indicate that this isn't a top-level class. + */ + private boolean classNameImpliesTopLevel() { + return !getName().contains("$"); + } + + /** + * 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 cannot 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); + } else { + result.getType(); // Throw NoClassDefFoundError if type cannot be resolved. + } + return result; + } + + private Field getPublicFieldRecursive(String name) { + // search superclasses + for (Class<?> c = this; c != null; c = c.superClass) { + Field result = c.getDeclaredFieldInternal(name); + if (result != null && (result.getModifiers() & Modifier.PUBLIC) != 0) { + return result; + } + } + + // search iftable which has a flattened and uniqued list of interfaces + if (ifTable != null) { + for (int i = 0; i < ifTable.length; i += 2) { + Class<?> ifc = (Class<?>) ifTable[i]; + 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); + Field[] result = fields.toArray(new Field[fields.size()]); + for (Field f : result) { + f.getType(); // Throw NoClassDefFoundError if type cannot be resolved. + } + return result; + } + + /** + * 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.superClass) { + c.getDeclaredFields(true, result); + } + + // search iftable which has a flattened and uniqued list of interfaces + Object[] iftable = ifTable; + if (iftable != null) { + for (int i = 0; i < iftable.length; i += 2) { + Class<?> ifc = (Class<?>) iftable[i]; + ifc.getDeclaredFields(true, 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, AnnotationAccess.getSignature(this)); + 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, AnnotationAccess.getSignature(this)); + 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. + * + * <p>This method only returns directly-implemented interfaces, and does not + * include interfaces implemented by superclasses or superinterfaces of any + * implemented interfaces. + */ + public Class<?>[] getInterfaces() { + if (isArray()) { + return new Class<?>[] { Cloneable.class, Serializable.class }; + } else if (isProxy()) { + return getProxyInterfaces(); + } else { + return AnnotationAccess.typeIndexToInterfaces(this, getDex(), getTypeIndex()); + } + } + + // Returns the interfaces that this proxy class directly implements. + private native Class<?>[] getProxyInterfaces(); + + /** + * 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() { + int JAVA_FLAGS_MASK = 0xffff; + int modifiers = AnnotationAccess.getInnerClassFlags(this, accessFlags & JAVA_FLAGS_MASK); + return modifiers & JAVA_FLAGS_MASK; + } + + /** + * 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() + "[]"; + } + + if (isAnonymousClass()) { + return ""; + } + + if (isMemberClass() || isLocalClass()) { + return getInnerClassName(); + } + + String name = getName(); + 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 String getInnerClassName() { + return AnnotationAccess.getInnerClassName(this); + } + + /** + * 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 Class<? super T> getSuperclass() { + // For interfaces superClass is Object (which agrees with the JNI spec) + // but not with the expected behavior here. + if (isInterface()) { + return null; + } else { + return superClass; + } + } + + /** + * 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") + @Override public synchronized TypeVariable<Class<T>>[] getTypeParameters() { + GenericSignatureParser parser = new GenericSignatureParser(getClassLoader()); + parser.parseForClass(this, AnnotationAccess.getSignature(this)); + 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.Modifier + return (accessFlags & ACC_ANNOTATION) != 0; + } + + @Override public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) { + return AnnotationAccess.isAnnotationPresent(this, annotationType); + } + + /** + * Tests whether the class represented by this {@code Class} is + * anonymous. + */ + public boolean isAnonymousClass() { + return AnnotationAccess.isAnonymousClass(this); + } + + /** + * Tests whether the class represented by this {@code Class} is an array class. + */ + public boolean isArray() { + return getComponentType() != null; + } + + /** + * Is this a runtime created proxy class? + * + * @hide + */ + public boolean isProxy() { + return (accessFlags & 0x00040000) != 0; + } + + /** + * Can {@code c} be assigned to this class? For example, String can be assigned to Object + * (by an upcast), however, an Object cannot be assigned to a String as a potentially exception + * throwing downcast would be necessary. Similarly for interfaces, a class that implements (or + * an interface that extends) another can be assigned to its parent, but not vice-versa. All + * Classes may assign to themselves. Classes for primitive types may not assign to each other. + * + * @param c the class to check. + * @return {@code true} if {@code c} can be assigned to the class + * represented by this {@code Class}; {@code false} otherwise. + * @throws NullPointerException if {@code c} is {@code null}. + */ + public boolean isAssignableFrom(Class<?> c) { + if (this == c) { + return true; // Can always assign to things of the same type. + } else if (this == Object.class) { + return !c.isPrimitive(); // Can assign any reference to java.lang.Object. + } else if (isArray()) { + return c.isArray() && componentType.isAssignableFrom(c.componentType); + } else if (isInterface()) { + // Search iftable which has a flattened and uniqued list of interfaces. + Object[] iftable = c.ifTable; + if (iftable != null) { + for (int i = 0; i < iftable.length; i += 2) { + Class<?> ifc = (Class<?>) iftable[i]; + if (ifc == this) { + return true; + } + } + } + return false; + } else { + if (!c.isInterface()) { + for (c = c.superClass; c != null; c = c.superClass) { + if (c == this) { + return true; + } + } + } + return false; + } + } + + /** + * 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 boolean isInstance(Object object) { + if (object == null) { + return false; + } + return isAssignableFrom(object.getClass()); + } + + /** + * Tests whether this {@code Class} represents an interface. + */ + public boolean isInterface() { + return (accessFlags & Modifier.INTERFACE) != 0; + } + + /** + * Tests whether the class represented by this {@code Class} is defined + * locally. + */ + public boolean isLocalClass() { + return !classNameImpliesTopLevel() + && AnnotationAccess.getEnclosingMethodOrConstructor(this) != null + && !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 boolean isPrimitive() { + return primitiveType != 0; + } + + /** + * Tests whether this {@code Class} represents a synthetic type. + */ + public boolean isSynthetic() { + final int ACC_SYNTHETIC = 0x1000; // not public in reflect.Modifier + return (accessFlags & ACC_SYNTHETIC) != 0; + } + + /** + * Indicates whether this {@code Class} or its parents override finalize. + * + * @hide + */ + public boolean isFinalizable() { + final int ACC_CLASS_IS_FINALIZABLE = 0x80000000; // not public in reflect.Modifier + return (accessFlags & ACC_CLASS_IS_FINALIZABLE) != 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 cannot be created. + */ + public T newInstance() throws InstantiationException, IllegalAccessException { + if (isPrimitive() || isInterface() || isArray() || Modifier.isAbstract(accessFlags)) { + throw new InstantiationException(this + " cannot be instantiated"); + } + Class<?> caller = VMStack.getStackClass1(); + if (!caller.canAccess(this)) { + throw new IllegalAccessException(this + " is not accessible from " + caller); + } + Constructor<T> init; + try { + init = getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + InstantiationException t = + new InstantiationException(this + " has no zero argument constructor"); + t.initCause(e); + throw t; + } + if (!caller.canAccessMember(this, init.getAccessFlags())) { + throw new IllegalAccessException(init + " is not accessible from " + caller); + } + try { + return init.newInstance(); + } catch (InvocationTargetException e) { + InstantiationException t = new InstantiationException(this); + t.initCause(e); + throw t; + } + } + + private boolean canAccess(Class<?> c) { + if(Modifier.isPublic(c.accessFlags)) { + return true; + } + return inSamePackage(c); + } + + private boolean canAccessMember(Class<?> memberClass, int memberModifiers) { + if (memberClass == this || Modifier.isPublic(memberModifiers)) { + return true; + } + if (Modifier.isPrivate(memberModifiers)) { + return false; + } + if (Modifier.isProtected(memberModifiers)) { + for (Class<?> parent = this.superClass; parent != null; parent = parent.superClass) { + if (parent == memberClass) { + return true; + } + } + } + return inSamePackage(memberClass); + } + + private boolean inSamePackage(Class<?> c) { + if (classLoader != c.classLoader) { + return false; + } + String packageName1 = getPackageName$(); + String packageName2 = c.getPackageName$(); + if (packageName1 == null) { + return packageName2 == null; + } else if (packageName2 == null) { + return false; + } else { + return packageName1.equals(packageName2); + } + } + + @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 packageName = getPackageName$(); + return packageName != null ? loader.getPackage(packageName) : null; + } + return null; + } + + /** + * Returns the package name of this class. This returns null for classes in + * the default package. + * + * @hide + */ + public String getPackageName$() { + String name = getName(); + int last = name.lastIndexOf('.'); + return last == -1 ? null : name.substring(0, last); + } + + /** + * 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 boolean desiredAssertionStatus() { + return false; + } + + /** + * 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); + } + + /** + * The type index of this class in its own Dex, or 0 if it is unknown. If a + * class is referenced by multiple Dex files, it will have a different type + * index in each. Dex files support 65534 type indices, with 65535 + * representing no index. + * + * TODO: 0 is a valid index; this should be -1 if it is unknown + * + * @hide + */ + public int getTypeIndex() { + int result = dexTypeIndex; + if (result == 0) { // uncomputed => Dalvik + result = AnnotationAccess.computeTypeIndex(getDex(), this); + dexTypeIndex = result; + } + return result; + } + + /** + * The annotation directory offset of this class in its own Dex, or 0 if it + * is unknown. + * + * TODO: 0 is a sentinel that means 'no annotations directory'; this should be -1 if unknown + * + * @hide + */ + public native int getAnnotationDirectoryOffset(); + // TODO: Use native code rather than: + // return AnnotationAccess.typeIndexToAnnotationDirectoryOffset(getDex(), getTypeIndex()); + // as native code has access to fast maps between type indices and class + // defs. Move fast maps to managed code. +} diff --git a/libart/src/main/java/java/lang/ClassLoader.java b/libart/src/main/java/java/lang/ClassLoader.java new file mode 100644 index 0000000..fb2eb8f --- /dev/null +++ b/libart/src/main/java/java/lang/ClassLoader.java @@ -0,0 +1,856 @@ +/* + * 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 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; +import java.util.Set; + +/** + * 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>(); + + /** + * To avoid unloading individual classes, {@link java.lang.reflect.Proxy} + * only generates one class for each set of interfaces. This maps sets of + * interfaces to the proxy class that implements all of them. It is declared + * here so that these generated classes can be unloaded with their class + * loader. + * + * @hide + */ + public final Map<Set<Class<?>>, Class<?>> proxyCache + = Collections.synchronizedMap(new HashMap<Set<Class<?>>, Class<?>>()); + + /** + * 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<URL> first = parent.getResources(resName); + Enumeration<URL> 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) { + ClassNotFoundException suppressed = null; + try { + clazz = parent.loadClass(className, false); + } catch (ClassNotFoundException e) { + suppressed = e; + } + + if (clazz == null) { + try { + clazz = findClass(className); + } catch (ClassNotFoundException e) { + e.addSuppressed(suppressed); + throw e; + } + } + } + + 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 final Enumeration<URL> first; + + private final Enumeration<URL> second; + + public TwoEnumerationsInOne(Enumeration<URL> first, Enumeration<URL> second) { + this.first = first; + this.second = second; + } + + @Override + public boolean hasMoreElements() { + return first.hasMoreElements() || second.hasMoreElements(); + } + + @Override + 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 Class.classForName(name, false, null); + } + + @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/libart/src/main/java/java/lang/Daemons.java b/libart/src/main/java/java/lang/Daemons.java new file mode 100644 index 0000000..1422c13 --- /dev/null +++ b/libart/src/main/java/java/lang/Daemons.java @@ -0,0 +1,340 @@ +/* + * 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(); + HeapTrimmerDaemon.INSTANCE.start(); + GCDaemon.INSTANCE.start(); + } + + public static void stop() { + ReferenceQueueDaemon.INSTANCE.stop(); + FinalizerDaemon.INSTANCE.stop(); + FinalizerWatchdogDaemon.INSTANCE.stop(); + HeapTrimmerDaemon.INSTANCE.stop(); + GCDaemon.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.systemThreadGroup, 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); + } + } + + // Invoked by the GC to request that the HeapTrimmerDaemon thread attempt to trim the heap. + public static void requestHeapTrim() { + synchronized (HeapTrimmerDaemon.INSTANCE) { + HeapTrimmerDaemon.INSTANCE.notify(); + } + } + + private static class HeapTrimmerDaemon extends Daemon { + private static final HeapTrimmerDaemon INSTANCE = new HeapTrimmerDaemon(); + + @Override public void run() { + while (isRunning()) { + try { + synchronized (this) { + wait(); + } + VMRuntime.getRuntime().trimHeap(); + } catch (InterruptedException ignored) { + } + } + } + } + + // Invoked by the GC to request that the HeapTrimmerDaemon thread attempt to trim the heap. + public static void requestGC() { + GCDaemon.INSTANCE.requestGC(); + } + + private static class GCDaemon extends Daemon { + private static final GCDaemon INSTANCE = new GCDaemon(); + private int count = 0; + + public void requestGC() { + synchronized (this) { + ++count; + notify(); + } + } + + @Override public void run() { + while (isRunning()) { + try { + synchronized (this) { + // Wait until a request comes in, unless we have a pending request. + while (count == 0) { + wait(); + } + --count; + } + VMRuntime.getRuntime().concurrentGC(); + } catch (InterruptedException ignored) { + } + } + } + } +} diff --git a/libart/src/main/java/java/lang/DexCache.java b/libart/src/main/java/java/lang/DexCache.java new file mode 100644 index 0000000..d0eba2f --- /dev/null +++ b/libart/src/main/java/java/lang/DexCache.java @@ -0,0 +1,39 @@ +/* + * 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.reflect.Field; +import java.lang.reflect.AbstractMethod; + +/** + * A dex cache holds resolved copies of strings, fields, methods, and classes from the dexfile. + */ +final class DexCache { + // Only created by the VM. + private DexCache() {} + + Object[] initializedStaticStorage; + String location; + Field[] resolvedFields; + AbstractMethod[] resolvedMethods; + Class[] resolvedTypes; + String[] strings; + + // Holds pointer to dexFile. + private int dexFile; +} + diff --git a/libart/src/main/java/java/lang/Object.java b/libart/src/main/java/java/lang/Object.java new file mode 100644 index 0000000..9c59870 --- /dev/null +++ b/libart/src/main/java/java/lang/Object.java @@ -0,0 +1,452 @@ +/* + * 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 { + + private transient Class<?> shadow$_klass_; + private transient int shadow$_monitor_; + + /** + * Constructs a new instance of {@code Object}. + */ + public Object() { + if (shadow$_klass_.isFinalizable()) { + java.lang.ref.FinalizerReference.add(this); + } + } + + /** + * 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 " + getClass().getName() + + " doesn't implement Cloneable"); + } + + return internalClone(); + } + + /* + * Native helper method for cloning. + */ + private native Object internalClone(); + + /** + * 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 Class<?> getClass() { + return shadow$_klass_; + } + + /** + * 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 int hashCode() { + return System.identityHashCode(this); + } + + /** + * 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 native void wait() throws InterruptedException; + + /** + * 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/libart/src/main/java/java/lang/Thread.java b/libart/src/main/java/java/lang/Thread.java new file mode 100644 index 0000000..b9d7462 --- /dev/null +++ b/libart/src/main/java/java/lang/Thread.java @@ -0,0 +1,1254 @@ +/* + * 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 */ + private volatile int vmData; + 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's join/sleep/park operations. + */ + private final Object lock = new Object(); + + /** Looked up reflectively and used by java.util.concurrent.locks.LockSupport. */ + 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 == null"); + } + + 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. + */ + public static native Thread 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.systemThreadGroup.activeCount(); + Thread[] threads = new Thread[count + count / 2]; + + // Enumerate the threads and collect the stacktraces. + count = ThreadGroup.systemThreadGroup.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() { + return State.values()[nativeGetStatus(hasBeenStarted)]; + } + + private native int nativeGetStatus(boolean hasBeenStarted); + + /** + * 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. + nativeInterrupt(); + + synchronized (interruptActions) { + for (int i = interruptActions.size() - 1; i >= 0; i--) { + interruptActions.get(i).run(); + } + } + } + + private native void nativeInterrupt(); + + /** + * 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 native boolean 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 (vmData != 0); + } + + /** + * 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 native boolean isInterrupted(); + + /** + * 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 { + synchronized (lock) { + while (isAlive()) { + lock.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; + } + + synchronized (lock) { + 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) { + lock.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 (vmData == 0) { + 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"); + } + + this.name = threadName; + nativeSetName(threadName); + } + + /** + * Tell the VM that the thread's name has changed. This is useful for + * DDMS, which would otherwise be oblivious to Thread.setName calls. + */ + private native void nativeSetName(String newName); + + /** + * 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; + nativeSetPriority(priority); + } + + private native void nativeSetPriority(int newPriority); + + /** + * <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 { + // The JLS 3rd edition, section 17.9 says: "...sleep for zero + // time...need not have observable effects." + if (millis == 0 && nanos == 0) { + return; + } + + long start = System.nanoTime(); + long duration = (millis * NANOS_PER_MILLI) + nanos; + + Object lock = currentThread().lock; + + // Wait may return early, so loop until sleep duration passes. + synchronized (lock) { + while (true) { + sleep(lock, millis, nanos); + + long now = System.nanoTime(); + long elapsed = now - start; + + if (elapsed >= duration) { + break; + } + + duration -= elapsed; + start = now; + millis = duration / NANOS_PER_MILLI; + nanos = (int) (duration % NANOS_PER_MILLI); + } + } + } + + private static native void sleep(Object lock, long millis, int 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; + + nativeCreate(this, stackSize, daemon); + } + + private native static void nativeCreate(Thread t, long stackSize, boolean daemon); + + /** + * 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 native void 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().nativeHoldsLock(object); + } + + private native boolean nativeHoldsLock(Object 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() { + synchronized (lock) { + 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; + lock.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) { + synchronized (lock) { + 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 { + lock.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("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) { + synchronized (lock) { + /* + * 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/libart/src/main/java/java/lang/ThreadGroup.java b/libart/src/main/java/java/lang/ThreadGroup.java new file mode 100644 index 0000000..51b2137 --- /dev/null +++ b/libart/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 systemThreadGroup = new ThreadGroup(); + static final ThreadGroup mainThreadGroup = new ThreadGroup(systemThreadGroup, "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/libart/src/main/java/java/lang/VMClassLoader.java b/libart/src/main/java/java/lang/VMClassLoader.java new file mode 100644 index 0000000..d180a4d --- /dev/null +++ b/libart/src/main/java/java/lang/VMClassLoader.java @@ -0,0 +1,78 @@ +/* + * 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; + } + + 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/libart/src/main/java/java/lang/reflect/AbstractMethod.java b/libart/src/main/java/java/lang/reflect/AbstractMethod.java new file mode 100644 index 0000000..ba7c0c3 --- /dev/null +++ b/libart/src/main/java/java/lang/reflect/AbstractMethod.java @@ -0,0 +1,362 @@ +/* + * 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) 2012 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 com.android.dex.Dex; +import com.android.dex.ProtoId; +import com.android.dex.TypeList; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import libcore.reflect.AnnotationAccess; +import libcore.reflect.GenericSignatureParser; +import libcore.reflect.InternalNames; +import libcore.reflect.ListOfTypes; +import libcore.reflect.Types; + +/** + * This class represents an abstract method. Abstract methods are either methods or constructors. + * @hide + */ +public abstract class AbstractMethod extends AccessibleObject { + private static final Comparator<AbstractMethod> ORDER_BY_SIGNATURE = null; + + /** Method's declaring class */ + Class<?> declaringClass; + /** Method access flags (modifiers) */ + private int accessFlags; + /** DexFile index */ + int methodDexIndex; + /** Dispatch table entry */ + private int methodIndex; + /** DexFile offset of CodeItem for this Method */ + private int codeItemOffset; + /* ART compiler meta-data */ + private int frameSizeInBytes; + private int coreSpillMask; + private int fpSpillMask; + private int mappingTable; + private int gcMap; + private int vmapTable; + /** ART: compiled managed code associated with this Method */ + private int entryPointFromCompiledCode; + /** ART: entry point from interpreter associated with this Method */ + private int entryPointFromInterpreter; + /** ART: if this is a native method, the native code that will be invoked */ + private int nativeMethod; + /* ART: dex cache fast access */ + private String[] dexCacheStrings; + Class<?>[] dexCacheResolvedTypes; + AbstractMethod[] dexCacheResolvedMethods; + private Object[] dexCacheInitializedStaticStorage; + + /** + * Only created by native code. + */ + AbstractMethod() { + } + + public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { + return super.getAnnotation(annotationClass); + } + + /** + * We insert native method stubs for abstract methods so we don't have to + * check the access flags at the time of the method call. This results in + * "native abstract" methods, which can't exist. If we see the "abstract" + * flag set, clear the "native" flag. + * + * We also move the DECLARED_SYNCHRONIZED flag into the SYNCHRONIZED + * position, because the callers of this function are trying to convey + * the "traditional" meaning of the flags to their callers. + */ + private static int fixMethodFlags(int flags) { + if ((flags & Modifier.ABSTRACT) != 0) { + flags &= ~Modifier.NATIVE; + } + flags &= ~Modifier.SYNCHRONIZED; + int ACC_DECLARED_SYNCHRONIZED = 0x00020000; + if ((flags & ACC_DECLARED_SYNCHRONIZED) != 0) { + flags |= Modifier.SYNCHRONIZED; + } + return flags & 0xffff; // mask out bits not used by Java + } + + int getModifiers() { + return fixMethodFlags(accessFlags); + } + + boolean isVarArgs() { + return (accessFlags & Modifier.VARARGS) != 0; + } + + boolean isBridge() { + return (accessFlags & Modifier.BRIDGE) != 0; + } + + public boolean isSynthetic() { + return (accessFlags & Modifier.SYNTHETIC) != 0; + } + + /** + * @hide + */ + public final int getAccessFlags() { + return accessFlags; + } + + /** + * Returns the name of the method or constructor represented by this + * instance. + * + * @return the name of this method + */ + public abstract String getName(); + + /** + * Returns the class that declares this constructor or method. + */ + Class<?> getDeclaringClass() { + return declaringClass; + } + + public int getDexMethodIndex() { + return methodDexIndex; + } + + /** + * 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 + */ + protected Class<?>[] getExceptionTypes() { + if (declaringClass.isProxy()) { + return getExceptionTypesNative(); + } else { + // TODO: use dex cache to speed looking up class + return AnnotationAccess.getExceptions(this); + } + } + + private native Class<?>[] getExceptionTypesNative(); + + /** + * Returns an array of {@code Class} objects associated with the parameter types of this + * abstract method. If the method was declared with no parameters, an + * empty array will be returned. + * + * @return the parameter types + */ + public abstract Class<?>[] getParameterTypes(); + + /** + * Returns true if {@code other} has the same declaring class, name, + * parameters and return type as this method. + */ + @Override public boolean equals(Object other) { + return this == other; // exactly one instance of each member in this runtime + } + + String toGenericString() { + return toGenericStringHelper(); + } + + Type[] getGenericParameterTypes() { + return Types.getClonedTypeArray(getMethodOrConstructorGenericInfo().genericParameterTypes); + } + + Type[] getGenericExceptionTypes() { + return Types.getClonedTypeArray(getMethodOrConstructorGenericInfo().genericExceptionTypes); + } + + @Override public Annotation[] getDeclaredAnnotations() { + List<Annotation> result = AnnotationAccess.getDeclaredAnnotations(this); + return result.toArray(new Annotation[result.size()]); + } + + @Override public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + return AnnotationAccess.isDeclaredAnnotationPresent(this, annotationType); + } + + public Annotation[] getAnnotations() { + return super.getAnnotations(); + } + + /** + * 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 abstract Annotation[][] getParameterAnnotations(); + + /** + * 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") + abstract String getSignature(); + + /** + * Returns a string from the dex cache, computing the string from the dex file if necessary. + * Note this method replicates {@link java.lang.Class#getDexCacheString(Dex, int)}, but in + * Method we can avoid one indirection. + */ + String getDexCacheString(Dex dex, int dexStringIndex) { + String s = (String) dexCacheStrings[dexStringIndex]; + if (s == null) { + s = dex.strings().get(dexStringIndex); + dexCacheStrings[dexStringIndex] = s; + } + return s; + } + + /** + * Returns a resolved type from the dex cache, computing the string from the dex file if + * necessary. Note this method replicates {@link java.lang.Class#getDexCacheType(Dex, int)}, + * but in Method we can avoid one indirection. + */ + Class<?> getDexCacheType(Dex dex, int dexTypeIndex) { + Class<?> resolvedType = dexCacheResolvedTypes[dexTypeIndex]; + if (resolvedType == null) { + int descriptorIndex = dex.typeIds().get(dexTypeIndex); + String descriptor = getDexCacheString(dex, descriptorIndex); + resolvedType = InternalNames.getClass(declaringClass.getClassLoader(), descriptor); + dexCacheResolvedTypes[dexTypeIndex] = resolvedType; + } + return resolvedType; + } + + static final class GenericInfo { + final ListOfTypes genericExceptionTypes; + final ListOfTypes genericParameterTypes; + final Type genericReturnType; + final TypeVariable<?>[] formalTypeParameters; + + GenericInfo(ListOfTypes exceptions, ListOfTypes parameters, Type ret, + TypeVariable<?>[] formal) { + genericExceptionTypes = exceptions; + genericParameterTypes = parameters; + genericReturnType = ret; + formalTypeParameters = formal; + } + } + + /** + * Returns generic information associated with this method/constructor member. + */ + GenericInfo getMethodOrConstructorGenericInfo() { + String signatureAttribute = AnnotationAccess.getSignature(this); + Member member; + Class<?>[] exceptionTypes; + boolean method = this instanceof Method; + if (method) { + Method m = (Method) this; + member = m; + exceptionTypes = m.getExceptionTypes(); + } else { + Constructor<?> c = (Constructor<?>) this; + member = c; + exceptionTypes = c.getExceptionTypes(); + } + GenericSignatureParser parser = + new GenericSignatureParser(member.getDeclaringClass().getClassLoader()); + if (method) { + parser.parseForMethod((GenericDeclaration) this, signatureAttribute, exceptionTypes); + } else { + parser.parseForConstructor((GenericDeclaration) this, signatureAttribute, exceptionTypes); + } + return new GenericInfo(parser.exceptionTypes, parser.parameterTypes, + parser.returnType, parser.formalTypeParameters); + } + + /** + * Helper for Method and Constructor for toGenericString + */ + String toGenericStringHelper() { + StringBuilder sb = new StringBuilder(80); + GenericInfo info = getMethodOrConstructorGenericInfo(); + int modifiers = ((Member)this).getModifiers(); + // append modifiers if any + if (modifiers != 0) { + sb.append(Modifier.toString(modifiers & ~Modifier.VARARGS)).append(' '); + } + // append type parameters + if (info.formalTypeParameters != null && info.formalTypeParameters.length > 0) { + sb.append('<'); + for (int i = 0; i < info.formalTypeParameters.length; i++) { + Types.appendGenericType(sb, info.formalTypeParameters[i]); + if (i < info.formalTypeParameters.length - 1) { + sb.append(","); + } + } + sb.append("> "); + } + Class<?> declaringClass = ((Member) this).getDeclaringClass(); + if (this instanceof Constructor) { + // append constructor name + Types.appendTypeName(sb, declaringClass); + } else { + // append return type + Types.appendGenericType(sb, Types.getType(info.genericReturnType)); + sb.append(' '); + // append method name + Types.appendTypeName(sb, declaringClass); + sb.append(".").append(((Method) this).getName()); + } + // append parameters + sb.append('('); + Types.appendArrayGenericType(sb, info.genericParameterTypes.getResolvedTypes()); + sb.append(')'); + // append exceptions if any + Type[] genericExceptionTypeArray = + Types.getClonedTypeArray(info.genericExceptionTypes); + if (genericExceptionTypeArray.length > 0) { + sb.append(" throws "); + Types.appendArrayGenericType(sb, genericExceptionTypeArray); + } + return sb.toString(); + } + +} diff --git a/libart/src/main/java/java/lang/reflect/AccessibleObject.java b/libart/src/main/java/java/lang/reflect/AccessibleObject.java new file mode 100644 index 0000000..f626c7e --- /dev/null +++ b/libart/src/main/java/java/lang/reflect/AccessibleObject.java @@ -0,0 +1,93 @@ +/* + * 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; + +/** + * The superclass of fields, constructors and methods. Reflective operations + * like {@link Field#set} and {@link Method#invoke} will fail with an {@link + * IllegalAccessException} when the caller doesn't satisfy the target's access + * modifier (either public, protected, package-private or private) and the + * <i>accessible</i> flag is false. Prevent the exception by setting the + * <i>accessible</i> flag to true with {@link #setAccessible(boolean) + * setAccessible(true)}. + * + * <p>On Android releases up to and including Android 4.0 (Ice Cream Sandwich), + * the <i>accessible</i> flag is false by default. On releases after + * Android 4.0, the <i>accessible</i> flag is true by default and cannot be set + * to false. + */ +// STOPSHIP 'After 4.0' is a guess; identify the release in which 'accessible' was true by default +public class AccessibleObject implements AnnotatedElement { + protected AccessibleObject() { + } + + /** + * Returns true if this object is accessible without access checks. + */ + public boolean isAccessible() { + return true; // always! + } + + /** + * Attempts to set the accessible flag. Setting this to true prevents {@code + * IllegalAccessExceptions}. + */ + public void setAccessible(boolean flag) { + } + + /** + * Attempts to set the accessible flag for all objects in {@code objects}. + * Setting this to true prevents {@code IllegalAccessExceptions}. + */ + public static void setAccessible(AccessibleObject[] objects, boolean flag) { + } + + @Override public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) { + throw new UnsupportedOperationException(); + } + + @Override public Annotation[] getDeclaredAnnotations() { + throw new UnsupportedOperationException(); + } + + @Override public Annotation[] getAnnotations() { + // for all but Class, getAnnotations == getDeclaredAnnotations + return getDeclaredAnnotations(); + } + + @Override public <T extends Annotation> T getAnnotation(Class<T> annotationType) { + throw new UnsupportedOperationException(); + } +} diff --git a/libart/src/main/java/java/lang/reflect/Constructor.java b/libart/src/main/java/java/lang/reflect/Constructor.java new file mode 100644 index 0000000..982653b --- /dev/null +++ b/libart/src/main/java/java/lang/reflect/Constructor.java @@ -0,0 +1,376 @@ +/* + * 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 com.android.dex.Dex; +import com.android.dex.ProtoId; +import com.android.dex.TypeList; + +import java.lang.annotation.Annotation; +import java.util.Comparator; +import java.util.List; +import libcore.reflect.AnnotationAccess; +import libcore.reflect.InternalNames; +import libcore.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 AbstractMethod implements GenericDeclaration, Member { + + private static final Comparator<Method> ORDER_BY_SIGNATURE = null; // Unused; must match Method. + + /** + * Only created by native code. + */ + private Constructor() { + } + + public Annotation[] getAnnotations() { + return super.getAnnotations(); + } + + /** + * Returns the modifiers for this constructor. The {@link Modifier} class + * should be used to decode the result. + */ + @Override public int getModifiers() { + return super.getModifiers(); + } + + /** + * Returns true if this constructor takes a variable number of arguments. + */ + public boolean isVarArgs() { + return super.isVarArgs(); + } + + /** + * Returns true if this constructor is synthetic (artificially introduced by the compiler). + */ + @Override public boolean isSynthetic() { + return super.isSynthetic(); + } + + /** + * Returns the name of this constructor. + */ + @Override public String getName() { + return getDeclaringClass().getName(); + } + + /** + * Returns the index of this constructor's method ID in its dex file. + * + * @hide used by AnnotationAccess + */ + public int getDexMethodIndex() { + return super.getDexMethodIndex(); + } + + /** + * Returns the class that declares this constructor. + */ + @Override public Class<T> getDeclaringClass() { + return (Class<T>) super.getDeclaringClass(); + } + + /** + * Returns the exception types as an array of {@code Class} instances. If + * this constructor has no declared exceptions, an empty array will be + * returned. + */ + @Override public Class<?>[] getExceptionTypes() { + // TODO: use dex cache to speed looking up class + return AnnotationAccess.getExceptions(this); + } + + /** + * 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. + */ + public Class<?>[] getParameterTypes() { + Dex dex = declaringClass.getDex(); + int protoIndex = dex.methodIds().get(methodDexIndex).getProtoIndex(); + ProtoId proto = dex.protoIds().get(protoIndex); + TypeList parametersList = dex.readTypeList(proto.getParametersOffset()); + short[] types = parametersList.getTypes(); + Class<?>[] parametersArray = new Class[types.length]; + for (int i = 0; i < types.length; i++) { + parametersArray[i] = getDexCacheType(dex, types[i]); + } + return parametersArray; + } + + /** + * {@inheritDoc} + * + * <p>Equivalent to {@code getDeclaringClass().getName().hashCode()}. + */ + @Override public int hashCode() { + return getDeclaringClass().getName().hashCode(); + } + + /** + * Returns true if {@code other} has the same declaring class and parameters + * as this constructor. + */ + @Override public boolean equals(Object other) { + return this == other; // exactly one instance of each member in this runtime + } + + @Override public TypeVariable<Constructor<T>>[] getTypeParameters() { + GenericInfo info = getMethodOrConstructorGenericInfo(); + return (TypeVariable<Constructor<T>>[]) info.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() { + return super.toGenericString(); + } + + /** + * 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() { + return super.getGenericParameterTypes(); + } + + /** + * 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() { + return super.getGenericExceptionTypes(); + } + + @Override public Annotation[] getDeclaredAnnotations() { + List<Annotation> result = AnnotationAccess.getDeclaredAnnotations(this); + return result.toArray(new Annotation[result.size()]); + } + + @Override public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + return AnnotationAccess.isDeclaredAnnotationPresent(this, annotationType); + } + + @Override public <A extends Annotation> A getAnnotation(Class<A> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + return AnnotationAccess.getDeclaredAnnotation(this, 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() { + return AnnotationAccess.getParameterAnnotations(this); + } + + /** + * 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") + String getSignature() { + StringBuilder result = new StringBuilder(); + + result.append('('); + Class<?>[] parameterTypes = getParameterTypes(); + for (Class<?> parameterType : parameterTypes) { + result.append(Types.getSignature(parameterType)); + } + result.append(")V"); + + return result.toString(); + } + + /** + * 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 native T newInstance(Object... args) throws InstantiationException, IllegalAccessException, + IllegalArgumentException, 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(getDeclaringClass().getName()); + result.append("("); + Class<?>[] parameterTypes = getParameterTypes(); + result.append(Types.toString(parameterTypes)); + result.append(")"); + Class<?>[] exceptionTypes = getExceptionTypes(); + if (exceptionTypes.length > 0) { + result.append(" throws "); + result.append(Types.toString(exceptionTypes)); + } + + return result.toString(); + } + + /** + * Returns a string from the dex cache, computing the string from the dex file if necessary. + * Note this method replicates {@link java.lang.Class#getDexCacheString(Dex, int)}, but in + * Method we can avoid one indirection. + * + * @hide + */ + String getDexCacheString(Dex dex, int dexStringIndex) { + return super.getDexCacheString(dex, dexStringIndex); + } + + /** + * Returns a resolved type from the dex cache, computing the string from the dex file if + * necessary. Note this method replicates {@link java.lang.Class#getDexCacheType(Dex, int)}, + * but in Method we can avoid one indirection. + * + * @hide + */ + Class<?> getDexCacheType(Dex dex, int dexTypeIndex) { + Class<?> resolvedType = dexCacheResolvedTypes[dexTypeIndex]; + if (resolvedType == null) { + int descriptorIndex = dex.typeIds().get(dexTypeIndex); + String descriptor = getDexCacheString(dex, descriptorIndex); + resolvedType = InternalNames.getClass(declaringClass.getClassLoader(), descriptor); + dexCacheResolvedTypes[dexTypeIndex] = resolvedType; + } + return resolvedType; + } +} diff --git a/libart/src/main/java/java/lang/reflect/Field.java b/libart/src/main/java/java/lang/reflect/Field.java new file mode 100644 index 0000000..2d0570a --- /dev/null +++ b/libart/src/main/java/java/lang/reflect/Field.java @@ -0,0 +1,804 @@ +/* + * 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 com.android.dex.Dex; +import java.lang.annotation.Annotation; +import java.util.Comparator; +import java.util.List; +import libcore.reflect.AnnotationAccess; +import libcore.reflect.GenericSignatureParser; +import libcore.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.getName().compareTo(b.getName()); + if (comparison != 0) { + return comparison; + } + return a.getDeclaringClass().getName().compareTo(b.getDeclaringClass().getName()); + } + }; + + private Class<?> declaringClass; + /** Field access flags (modifiers) */ + private int accessFlags; + /** Index into DexFile's field ids */ + private int fieldDexIndex; + /** Offset of field in object or class */ + private int offset; + + /** + * Only created by native code. + */ + private Field() { + } + + /** + * 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 + */ + @Override public int getModifiers() { + return accessFlags & 0xffff; // mask out bits not used by Java + } + + /** + * 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() { + return (accessFlags & Modifier.ENUM) != 0; + } + + /** + * Indicates whether or not this field is synthetic. + * + * @return {@code true} if this field is synthetic, {@code false} otherwise + */ + @Override public boolean isSynthetic() { + return (accessFlags & Modifier.SYNTHETIC) != 0; + } + + /** + * Returns the name of this field. + * + * @return the name of this field + */ + @Override public String getName() { + if (fieldDexIndex == -1) { + // Proxy classes have 1 synthesized static field with no valid dex index + if (!declaringClass.isProxy()) { + throw new AssertionError(); + } + return "throws"; + } + Dex dex = declaringClass.getDex(); + int nameIndex = dex.fieldIds().get(fieldDexIndex).getNameIndex(); + return declaringClass.getDexCacheString(dex, nameIndex); + } + + /** + * Returns the class that declares this field. + * + * @return the declaring class + */ + @Override public Class<?> getDeclaringClass() { + return declaringClass; + } + + /** + * Return the {@link Class} associated with the type of this field. + * + * @return the type of this field + */ + public Class<?> getType() { + if (fieldDexIndex == -1) { + // The type of the synthesized field in a Proxy class is Class[][] + if (!declaringClass.isProxy()) { + throw new AssertionError(); + } + return Class[][].class; + } + Dex dex = declaringClass.getDex(); + int typeIndex = dex.fieldIds().get(fieldDexIndex).getTypeIndex(); + return declaringClass.getDexCacheType(dex, typeIndex); + } + + /** + * Returns the index of this field's ID in its dex file. + * + * @hide + */ + public int getDexFieldIndex() { + return fieldDexIndex; + } + + /** + * Returns the offset of the field within an instance, or for static fields, the class. + * + * @hide + */ + public int getOffset() { + return offset; + } + + /** + * {@inheritDoc} + * + * <p>Equivalent to {@code getDeclaringClass().getName().hashCode() ^ getName().hashCode()}. + */ + @Override public int hashCode() { + return getDeclaringClass().getName().hashCode() ^ getName().hashCode(); + } + + /** + * Returns true if {@code other} has the same declaring class, name and type + * as this field. + */ + @Override public boolean equals(Object other) { + return this == other; // exactly one instance of each member in this runtime + } + + /** + * 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 + Types.appendGenericType(sb, getGenericType()); + sb.append(' '); + // append full field name + sb.append(getDeclaringClass().getName()).append('.').append(getName()); + return sb.toString(); + } + + /** + * 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() { + String signatureAttribute = AnnotationAccess.getSignature(this); + Class<?> declaringClass = getDeclaringClass(); + ClassLoader cl = declaringClass.getClassLoader(); + GenericSignatureParser parser = new GenericSignatureParser(cl); + parser.parseForField(declaringClass, signatureAttribute); + Type genericType = parser.fieldType; + if (genericType == null) { + genericType = getType(); + } + return genericType; + } + + /** + * 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 + */ + @SuppressWarnings("unused") + private String getSignature() { + return Types.getSignature(getType()); + } + + @Override public Annotation[] getDeclaredAnnotations() { + List<Annotation> result = AnnotationAccess.getDeclaredAnnotations(this); + return result.toArray(new Annotation[result.size()]); + } + + @Override public <A extends Annotation> A getAnnotation(Class<A> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + return AnnotationAccess.getDeclaredAnnotation(this, annotationType); + } + + @Override public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + return AnnotationAccess.isDeclaredAnnotationPresent(this, annotationType); + } + + /** + * 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 native Object get(Object object) throws IllegalAccessException, IllegalArgumentException; + + /** + * 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 native boolean getBoolean(Object object) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native byte getByte(Object object) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native char getChar(Object object) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native double getDouble(Object object) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native float getFloat(Object object) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native int getInt(Object object) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native long getLong(Object object) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native short getShort(Object object) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native void set(Object object, Object value) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native void setBoolean(Object object, boolean value) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native void setByte(Object object, byte value) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native void setChar(Object object, char value) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native void setDouble(Object object, double value) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native void setFloat(Object object, float value) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native void setInt(Object object, int value) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native void setLong(Object object, long value) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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 native void setShort(Object object, short value) throws IllegalAccessException, + IllegalArgumentException; + + /** + * 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(' '); + } + Types.appendTypeName(result, getType()); + result.append(' '); + result.append(getDeclaringClass().getName()); + result.append('.'); + result.append(getName()); + return result.toString(); + } +} diff --git a/libart/src/main/java/java/lang/reflect/Method.java b/libart/src/main/java/java/lang/reflect/Method.java new file mode 100644 index 0000000..555afc9 --- /dev/null +++ b/libart/src/main/java/java/lang/reflect/Method.java @@ -0,0 +1,540 @@ +/* + * 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 com.android.dex.Dex; +import com.android.dex.ProtoId; +import com.android.dex.TypeList; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import libcore.reflect.AnnotationAccess; +import libcore.reflect.InternalNames; +import libcore.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 AbstractMethod implements GenericDeclaration, Member { + + /** + * Orders methods by their name, parameters and return type. + * + * @hide + */ + public static final Comparator<Method> ORDER_BY_SIGNATURE = new Comparator<Method>() { + @Override public int compare(Method a, Method b) { + if (a == b) { + return 0; + } + int comparison = a.getName().compareTo(b.getName()); + if (comparison != 0) { + return comparison; + } + Class<?>[] aParameters = a.getParameterTypes(); + Class<?>[] bParameters = b.getParameterTypes(); + 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()); + } + }; + + /** + * Only created by native code. + */ + private Method() { + } + + public Annotation[] getAnnotations() { + return super.getAnnotations(); + } + + /** + * 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 + */ + @Override public int getModifiers() { + return super.getModifiers(); + } + + /** + * 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() { + return super.isVarArgs(); + } + + /** + * Indicates whether or not this method is a bridge. + * + * @return {@code true} if this method is a bridge, {@code false} otherwise + */ + public boolean isBridge() { + return super.isBridge(); + + } + + /** + * Indicates whether or not this method is synthetic. + * + * @return {@code true} if this method is synthetic, {@code false} otherwise + */ + @Override public boolean isSynthetic() { + return super.isSynthetic(); + } + + /** + * Returns the name of the method represented by this {@code Method} + * instance. + * + * @return the name of this method + */ + @Override public String getName() { + Method method = this; + if (declaringClass.isProxy()) { + // For proxies use their interface method + method = findOverriddenMethod(); + } + Dex dex = method.declaringClass.getDex(); + int nameIndex = dex.methodIds().get(methodDexIndex).getNameIndex(); + // Note, in the case of a Proxy the dex cache strings are equal. + return getDexCacheString(dex, nameIndex); + } + + /** + * Returns the class that declares this method. + */ + @Override public Class<?> getDeclaringClass() { + return super.getDeclaringClass(); + } + + /** + * Returns the index of this method's ID in its dex file. + * + * @hide + */ + public int getDexMethodIndex() { + return super.getDexMethodIndex(); + } + + /** + * 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 (declaringClass.isProxy()) { + return getExceptionTypesNative(); + } else { + // TODO: use dex cache to speed looking up class + return AnnotationAccess.getExceptions(this); + } + } + + private native Class<?>[] getExceptionTypesNative(); + + /** + * 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() { + Method method = this; + if (declaringClass.isProxy()) { + // For proxies use their interface method + method = findOverriddenMethod(); + } + Dex dex = method.declaringClass.getDex(); + int protoIndex = dex.methodIds().get(methodDexIndex).getProtoIndex(); + ProtoId proto = dex.protoIds().get(protoIndex); + TypeList parametersList = dex.readTypeList(proto.getParametersOffset()); + short[] types = parametersList.getTypes(); + Class<?>[] parametersArray = new Class[types.length]; + for (int i = 0; i < types.length; i++) { + // Note, in the case of a Proxy the dex cache types are equal. + parametersArray[i] = getDexCacheType(dex, types[i]); + } + return parametersArray; + } + + /** + * Returns the {@code Class} associated with the return type of this + * method. + * + * @return the return type + */ + public Class<?> getReturnType() { + Method method = this; + if (declaringClass.isProxy()) { + // For proxies use their interface method + method = findOverriddenMethod(); + } + Dex dex = method.declaringClass.getDex(); + int proto_idx = dex.methodIds().get(methodDexIndex).getProtoIndex(); + ProtoId proto = dex.protoIds().get(proto_idx); + int returnTypeIndex = proto.getReturnTypeIndex(); + // Note, in the case of a Proxy the dex cache types are equal. + return getDexCacheType(dex, returnTypeIndex); + } + + /** + * {@inheritDoc} + * + * <p>Equivalent to {@code getDeclaringClass().getName().hashCode() ^ getName().hashCode()}. + */ + @Override public int hashCode() { + return getDeclaringClass().getName().hashCode() ^ getName().hashCode(); + } + + /** + * Returns true if {@code other} has the same declaring class, name, + * parameters and return type as this method. + */ + @Override public boolean equals(Object other) { + return this == other; // exactly one instance of each member in this runtime + } + + /** + * Returns true if this and {@code method} have the same name and the same + * parameters in the same order. Such methods can share implementation if + * one method's return types is assignable to the other. + * + * @hide needed by Proxy + */ + boolean equalNameAndParameters(Method m) { + if (!getName().equals(m.getName())) { + return false; + } + if (!Arrays.equals(getParameterTypes(), m.getParameterTypes())) { + return false; + } + return true; + } + + /** + * Returns the string representation of the method's declaration, including + * the type parameters. + * + * @return the string representation of this method + */ + public String toGenericString() { + return super.toGenericString(); + } + + @Override public TypeVariable<Method>[] getTypeParameters() { + GenericInfo info = getMethodOrConstructorGenericInfo(); + return (TypeVariable<Method>[]) info.formalTypeParameters.clone(); + } + + /** + * 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() { + return Types.getClonedTypeArray(getMethodOrConstructorGenericInfo().genericParameterTypes); + } + + @Override public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + return AnnotationAccess.isDeclaredAnnotationPresent(this, annotationType); + } + + /** + * 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() { + return Types.getClonedTypeArray(getMethodOrConstructorGenericInfo().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() { + return Types.getType(getMethodOrConstructorGenericInfo().genericReturnType); + } + + @Override public Annotation[] getDeclaredAnnotations() { + List<Annotation> result = AnnotationAccess.getDeclaredAnnotations(this); + return result.toArray(new Annotation[result.size()]); + } + + @Override public <A extends Annotation> A getAnnotation(Class<A> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + return AnnotationAccess.getDeclaredAnnotation(this, annotationType); + } + + /** + * 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() { + Method method = this; + if (declaringClass.isProxy()) { + // For proxies use their interface method + method = findOverriddenMethod(); + } + return AnnotationAccess.getParameterAnnotations(method); + } + + /** + * 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 AnnotationAccess.getDefaultValue(this); + } + + /** + * 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 native Object invoke(Object receiver, Object... args) + 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(getReturnType().getName()); + result.append(' '); + result.append(getDeclaringClass().getName()); + result.append('.'); + result.append(getName()); + result.append("("); + Class<?>[] parameterTypes = getParameterTypes(); + result.append(Types.toString(parameterTypes)); + result.append(")"); + Class<?>[] exceptionTypes = getExceptionTypes(); + if (exceptionTypes.length != 0) { + result.append(" throws "); + result.append(Types.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") + String getSignature() { + StringBuilder result = new StringBuilder(); + + result.append('('); + Class<?>[] parameterTypes = getParameterTypes(); + for (Class<?> parameterType : parameterTypes) { + result.append(Types.getSignature(parameterType)); + } + result.append(')'); + result.append(Types.getSignature(getReturnType())); + + return result.toString(); + } + + public void setAccessible(boolean flag) { + super.setAccessible(flag); + } + + /** + * Returns a string from the dex cache, computing the string from the dex file if necessary. + * Note this method replicates {@link java.lang.Class#getDexCacheString(Dex, int)}, but in + * Method we can avoid one indirection. + */ + String getDexCacheString(Dex dex, int dexStringIndex) { + return super.getDexCacheString(dex, dexStringIndex); + } + + /** + * Returns a resolved type from the dex cache, computing the string from the dex file if + * necessary. Note this method replicates {@link java.lang.Class#getDexCacheType(Dex, int)}, + * but in Method we can avoid one indirection. + */ + Class<?> getDexCacheType(Dex dex, int dexTypeIndex) { + Class<?> resolvedType = dexCacheResolvedTypes[dexTypeIndex]; + if (resolvedType == null) { + int descriptorIndex = dex.typeIds().get(dexTypeIndex); + String descriptor = getDexCacheString(dex, descriptorIndex); + resolvedType = InternalNames.getClass(declaringClass.getClassLoader(), descriptor); + dexCacheResolvedTypes[dexTypeIndex] = resolvedType; + } + return resolvedType; + } + + /** + * Returns the {@code Method} that this method overrides. Used to determine the interface + * method overridden by a proxy method (as the proxy method doesn't directly support operations + * such as {@link Method#getName}). This method works for non-proxy methods. + */ + private Method findOverriddenMethod() { + if (declaringClass.isProxy()) { + // Proxy method's declaring class' dex cache refers to that of Proxy. The local cache in + // Method refers to the original interface's dex cache and is ensured to be resolved by + // proxy generation. Short-cut the native call below in this case. + return (Method) dexCacheResolvedMethods[methodDexIndex]; + } else { + return findOverriddenMethodNative(); + } + } + + private native Method findOverriddenMethodNative(); +} diff --git a/libart/src/main/java/java/lang/reflect/Proxy.java b/libart/src/main/java/java/lang/reflect/Proxy.java new file mode 100644 index 0000000..b5e6283 --- /dev/null +++ b/libart/src/main/java/java/lang/reflect/Proxy.java @@ -0,0 +1,373 @@ +/* + * 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.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import libcore.util.EmptyArray; + +/** + * {@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; + + private static int NextClassNameIndex = 0; + + /** + * Orders methods by their name, parameters, return type and inheritance relationship. + * + * @hide + */ + private static final Comparator<Method> ORDER_BY_SIGNATURE_AND_SUBTYPE = new Comparator<Method>() { + @Override public int compare(Method a, Method b) { + int comparison = Method.ORDER_BY_SIGNATURE.compare(a, b); + if (comparison != 0) { + return comparison; + } + Class<?> aClass = a.getDeclaringClass(); + Class<?> bClass = b.getDeclaringClass(); + if (aClass == bClass) { + return 0; + } else if (aClass.isAssignableFrom(bClass)) { + return 1; + } else if (bClass.isAssignableFrom(aClass)) { + return -1; + } else { + return 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 { + if (loader == null) { + loader = ClassLoader.getSystemClassLoader(); + } + + if (interfaces == null) { + throw new NullPointerException("interfaces == null"); + } + + Set<Class<?>> interfacesSet = new CopyOnWriteArraySet<Class<?>>(Arrays.asList(interfaces)); + + Class<?> proxy = loader.proxyCache.get(interfacesSet); + if (proxy != null) { + return proxy; + } + + if (interfacesSet.size() != interfaces.length) { + throw new IllegalArgumentException( + "interfaces has duplicates: " + Arrays.toString(interfaces)); + } + String commonPackageName = null; + for (Class<?> c : interfaces) { + if (c == null) { + throw new NullPointerException("interfaces contained a null element"); + } + if (!c.isInterface()) { + throw new IllegalArgumentException(c + " is not an interface"); + } + if (!isVisibleToClassLoader(loader, c)) { + throw new IllegalArgumentException(c + " is not visible from class loader"); + } + if (!Modifier.isPublic(c.getModifiers())) { + String packageName = c.getPackageName$(); + if (packageName == null) { + packageName = ""; + } + if (commonPackageName != null && !commonPackageName.equals(packageName)) { + throw new IllegalArgumentException( + "non-public interfaces must be in the same package"); + } + commonPackageName = packageName; + } + } + + String baseName = commonPackageName != null && !commonPackageName.isEmpty() + ? commonPackageName + ".$Proxy" + : "$Proxy"; + String name = baseName + NextClassNameIndex++; + + List<Method> methods = getMethods(interfaces); + Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE); + validateReturnTypes(methods); + List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods); + + Method[] methodsArray = methods.toArray(new Method[methods.size()]); + Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]); + Class<?> result = generateProxy(name, interfaces, loader, methodsArray, exceptionsArray); + loader.proxyCache.put(interfacesSet, result); + return result; + } + + private static boolean isVisibleToClassLoader(ClassLoader loader, Class<?> c) { + try { + return loader == c.getClassLoader() || c == Class.forName(c.getName(), false, loader); + } catch (ClassNotFoundException ex) { + return false; + } + } + + /** + * 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 invocationHandler + * 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 invocationHandler) + throws IllegalArgumentException { + + if (invocationHandler == null) { + throw new NullPointerException("invocationHandler == null"); + } + Exception cause; + try { + return getProxyClass(loader, interfaces) + .getConstructor(InvocationHandler.class) + .newInstance(invocationHandler); + } catch (NoSuchMethodException e) { + cause = e; + } catch (IllegalAccessException e) { + cause = e; + } catch (InstantiationException e) { + cause = e; + } catch (InvocationTargetException e) { + cause = e; + } + AssertionError error = new AssertionError(); + error.initCause(cause); + throw error; + } + + /** + * 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) { + return cl.isProxy(); + } + + /** + * 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 { + // TODO: return false for subclasses of Proxy not created by generateProxy() + if (!(proxy instanceof Proxy)) { + throw new IllegalArgumentException("not a proxy instance"); + } + return ((Proxy) proxy).h; + } + + private static List<Method> getMethods(Class<?>[] interfaces) { + List<Method> result = new ArrayList<Method>(); + try { + result.add(Object.class.getMethod("equals", Object.class)); + result.add(Object.class.getMethod("hashCode", EmptyArray.CLASS)); + result.add(Object.class.getMethod("toString", EmptyArray.CLASS)); + } catch (NoSuchMethodException e) { + throw new AssertionError(); + } + + getMethodsRecursive(interfaces, result); + return result; + } + + /** + * Fills {@code proxiedMethods} with the methods of {@code interfaces} and + * the interfaces they extend. May contain duplicates. + */ + private static void getMethodsRecursive(Class<?>[] interfaces, List<Method> methods) { + for (Class<?> i : interfaces) { + getMethodsRecursive(i.getInterfaces(), methods); + Collections.addAll(methods, i.getDeclaredMethods()); + } + } + + /** + * Throws if any two methods in {@code methods} have the same name and + * parameters but incompatible return types. + * + * @param methods the methods to find exceptions for, ordered by name and + * signature. + */ + private static void validateReturnTypes(List<Method> methods) { + Method vs = null; + for (Method method : methods) { + if (vs == null || !vs.equalNameAndParameters(method)) { + vs = method; // this has a different name or parameters + continue; + } + Class<?> returnType = method.getReturnType(); + Class<?> vsReturnType = vs.getReturnType(); + if (returnType.isInterface() && vsReturnType.isInterface()) { + // all interfaces are mutually compatible + } else if (vsReturnType.isAssignableFrom(returnType)) { + vs = method; // the new return type is a subtype; use it instead + } else if (!returnType.isAssignableFrom(vsReturnType)) { + throw new IllegalArgumentException("proxied interface methods have incompatible " + + "return types:\n " + vs + "\n " + method); + } + } + } + + /** + * Remove methods that have the same name, parameters and return type. This + * computes the exceptions of each method; this is the intersection of the + * exceptions of equivalent methods. + * + * @param methods the methods to find exceptions for, ordered by name and + * signature. + */ + private static List<Class<?>[]> deduplicateAndGetExceptions(List<Method> methods) { + List<Class<?>[]> exceptions = new ArrayList<Class<?>[]>(methods.size()); + + for (int i = 0; i < methods.size(); ) { + Method method = methods.get(i); + Class<?>[] exceptionTypes = method.getExceptionTypes(); + + if (i > 0 && Method.ORDER_BY_SIGNATURE.compare(method, methods.get(i - 1)) == 0) { + exceptions.set(i - 1, intersectExceptions(exceptions.get(i - 1), exceptionTypes)); + methods.remove(i); + } else { + exceptions.add(exceptionTypes); + i++; + } + } + return exceptions; + } + + /** + * Returns the exceptions that are declared in both {@code aExceptions} and + * {@code bExceptions}. If an exception type in one array is a subtype of an + * exception from the other, the subtype is included in the intersection. + */ + private static Class<?>[] intersectExceptions(Class<?>[] aExceptions, Class<?>[] bExceptions) { + if (aExceptions.length == 0 || bExceptions.length == 0) { + return EmptyArray.CLASS; + } + if (Arrays.equals(aExceptions, bExceptions)) { + return aExceptions; + } + Set<Class<?>> intersection = new HashSet<Class<?>>(); + for (Class<?> a : aExceptions) { + for (Class<?> b : bExceptions) { + if (a.isAssignableFrom(b)) { + intersection.add(b); + } else if (b.isAssignableFrom(a)) { + intersection.add(a); + } + } + } + return intersection.toArray(new Class<?>[intersection.size()]); + } + + private static native Class<?> generateProxy(String name, Class<?>[] interfaces, + ClassLoader loader, Method[] methods, + Class<?>[][] exceptions); + + /* + * The VM clones this method's descriptor when generating a proxy class. + * There is no implementation. + */ + private static native void constructorPrototype(InvocationHandler h); +} diff --git a/libart/src/main/java/libcore/reflect/AnnotationAccess.java b/libart/src/main/java/libcore/reflect/AnnotationAccess.java new file mode 100644 index 0000000..fe740de --- /dev/null +++ b/libart/src/main/java/libcore/reflect/AnnotationAccess.java @@ -0,0 +1,794 @@ +/* + * 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 libcore.reflect; + +import com.android.dex.ClassDef; +import com.android.dex.Dex; +import com.android.dex.EncodedValueReader; +import com.android.dex.FieldId; +import com.android.dex.MethodId; +import com.android.dex.ProtoId; +import com.android.dex.TypeList; +import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import libcore.util.EmptyArray; + +/** + * Look up annotations from a dex file. + */ +public final class AnnotationAccess { + private AnnotationAccess() { + } + + /* + * Classes like arrays, primitives and proxies will not have a Dex file. + * Such classes never have annotations. + */ + + private static final Class<?>[] NO_ARGUMENTS = null; + @SuppressWarnings("unused") + private static final byte VISIBILITY_BUILD = 0x00; + private static final byte VISIBILITY_RUNTIME = 0x01; + @SuppressWarnings("unused") + private static final byte VISIBILITY_SYSTEM = 0x02; + + /* + * Class annotations. This includes declared class annotations plus + * annotations on the superclass that have @Inherited. + */ + + public static <A extends java.lang.annotation.Annotation> A getAnnotation( + Class<?> c, Class<A> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + + A annotation = getDeclaredAnnotation(c, annotationType); + if (annotation != null) { + return annotation; + } + + if (isInherited(annotationType)) { + for (Class<?> sup = c.getSuperclass(); sup != null; sup = sup.getSuperclass()) { + annotation = getDeclaredAnnotation(sup, annotationType); + if (annotation != null) { + return annotation; + } + } + } + + return null; + } + + /** + * Returns true if {@code annotationType} annotations on the superclass + * apply to subclasses that don't have another annotation of the same + * type. + */ + private static boolean isInherited(Class<? extends Annotation> annotationType) { + return isDeclaredAnnotationPresent(annotationType, Inherited.class); + } + + public static Annotation[] getAnnotations(Class<?> c) { + /* + * 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>(); + for (Annotation declaredAnnotation : getDeclaredAnnotations(c)) { + map.put(declaredAnnotation.annotationType(), declaredAnnotation); + } + for (Class<?> sup = c.getSuperclass(); sup != null; sup = sup.getSuperclass()) { + for (Annotation declaredAnnotation : getDeclaredAnnotations(sup)) { + Class<? extends Annotation> clazz = declaredAnnotation.annotationType(); + if (!map.containsKey(clazz) && isInherited(clazz)) { + map.put(clazz, declaredAnnotation); + } + } + } + + /* convert annotation values from HashMap to array */ + Collection<Annotation> coll = map.values(); + return coll.toArray(new Annotation[coll.size()]); + } + + /** + * Returns true if {@code c} is annotated by {@code annotationType}. + */ + public static boolean isAnnotationPresent( + Class<?> c, Class<? extends Annotation> annotationType) { + if (annotationType == null) { + throw new NullPointerException("annotationType == null"); + } + + if (isDeclaredAnnotationPresent(c, annotationType)) { + return true; + } + + if (isInherited(annotationType)) { + for (Class<?> sup = c.getSuperclass(); sup != null; sup = sup.getSuperclass()) { + if (isDeclaredAnnotationPresent(sup, annotationType)) { + return true; + } + } + } + + return false; + } + + /* + * Class, Field, Method, Constructor and Parameter annotations + */ + + /** + * Returns the annotations on {@code element}. + */ + public static List<Annotation> getDeclaredAnnotations(AnnotatedElement element) { + int offset = getAnnotationSetOffset(element); + return annotationSetToAnnotations(getDexClass(element), offset); + } + + /** + * Returns the annotation if it exists. + */ + public static <A extends Annotation> A getDeclaredAnnotation( + AnnotatedElement element, Class<A> annotationClass) { + com.android.dex.Annotation a = getMethodAnnotation(element, annotationClass); + return a != null + ? toAnnotationInstance(getDexClass(element), annotationClass, a) + : null; + } + + /** + * Returns true if the annotation exists. + */ + public static boolean isDeclaredAnnotationPresent( + AnnotatedElement element, Class<? extends Annotation> annotationClass) { + return getMethodAnnotation(element, annotationClass) != null; + } + + private static com.android.dex.Annotation getMethodAnnotation( + AnnotatedElement element, Class<? extends Annotation> annotationClass) { + Class<?> dexClass = getDexClass(element); + Dex dex = dexClass.getDex(); + int annotationTypeIndex = getTypeIndex(dex, annotationClass); + if (annotationTypeIndex == -1) { + return null; // the dex file doesn't use this annotation + } + + int annotationSetOffset = getAnnotationSetOffset(element); + return getAnnotationFromAnnotationSet(dex, annotationSetOffset, annotationTypeIndex); + } + + /** + * @param element a class, a field, a method or a constructor. + */ + private static int getAnnotationSetOffset(AnnotatedElement element) { + Class<?> dexClass = getDexClass(element); + int directoryOffset = getDirectoryOffset(dexClass); + if (directoryOffset == 0) { + return 0; // nothing on this class has annotations + } + + Dex.Section directoryIn = dexClass.getDex().open(directoryOffset); + int classSetOffset = directoryIn.readInt(); + if (element instanceof Class) { + return classSetOffset; + } + + int fieldsSize = directoryIn.readInt(); + int methodsSize = directoryIn.readInt(); + directoryIn.readInt(); // parameters size + + int fieldIndex = element instanceof Field ? ((Field) element).getDexFieldIndex() : -1; + for (int i = 0; i < fieldsSize; i++) { + int candidateFieldIndex = directoryIn.readInt(); + int annotationSetOffset = directoryIn.readInt(); + if (candidateFieldIndex == fieldIndex) { + return annotationSetOffset; + } + } + // we must read all fields prior to methods, if we were searching for a field then we missed + if (element instanceof Field) { + return 0; + } + + int methodIndex= element instanceof Method ? ((Method) element).getDexMethodIndex() + : ((Constructor<?>) element).getDexMethodIndex(); + for (int i = 0; i < methodsSize; i++) { + int candidateMethodIndex = directoryIn.readInt(); + int annotationSetOffset = directoryIn.readInt(); + if (candidateMethodIndex == methodIndex) { + return annotationSetOffset; + } + } + + return 0; + } + + /** + * Returns {@code element} if it is a class; and the class declaring + * {@code element} otherwise. The dex file of the returned class also + * defines {@code element}. + */ + private static Class<?> getDexClass(AnnotatedElement element) { + return element instanceof Class + ? ((Class<?>) element) + : ((Member) element).getDeclaringClass(); + } + + public static int getFieldIndex(Class<?> declaringClass, Class<?> type, String name) { + Dex dex = declaringClass.getDex(); + int declaringClassIndex = getTypeIndex(dex, declaringClass); + int typeIndex = getTypeIndex(dex, type); + int nameIndex = getStringIndex(dex, name); + FieldId fieldId = new FieldId(dex, declaringClassIndex, typeIndex, nameIndex); + return Collections.binarySearch(dex.fieldIds(), fieldId); + } + + public static int getMethodIndex(Class<?> declaringClass, String name, int protoIndex) { + Dex dex = declaringClass.getDex(); + int declaringClassIndex = getTypeIndex(dex, declaringClass); + int nameIndex = getStringIndex(dex, name); + MethodId methodId = new MethodId(dex, declaringClassIndex, protoIndex, nameIndex); + return Collections.binarySearch(dex.methodIds(), methodId); + } + + /** + * Returns the parameter annotations on {@code member}. + */ + public static Annotation[][] getParameterAnnotations(Member member) { + Class<?> declaringClass = member.getDeclaringClass(); + Dex dex = declaringClass.getDex(); + int methodDexIndex; + if (member instanceof Method) { + methodDexIndex = ((Method) member).getDexMethodIndex(); + } else { + methodDexIndex = ((Constructor<?>) member).getDexMethodIndex(); + } + int protoIndex = dex.methodIds().get(methodDexIndex).getProtoIndex(); + ProtoId proto = dex.protoIds().get(protoIndex); + TypeList parametersList = dex.readTypeList(proto.getParametersOffset()); + short[] types = parametersList.getTypes(); + int typesCount = types.length; + + int directoryOffset = getDirectoryOffset(declaringClass); + if (directoryOffset == 0) { + return new Annotation[typesCount][0]; // nothing on this class has annotations + } + + Dex.Section directoryIn = dex.open(directoryOffset); + directoryIn.readInt(); // class annotations + int fieldsSize = directoryIn.readInt(); + int methodsSize = directoryIn.readInt(); + int parametersSize = directoryIn.readInt(); + + for (int i = 0; i < fieldsSize; i++) { + directoryIn.readInt(); // field_index + directoryIn.readInt(); // annotation_set + } + + for (int i = 0; i < methodsSize; i++) { + directoryIn.readInt(); // method_index + directoryIn.readInt(); // annotation_set + } + + for (int i = 0; i < parametersSize; i++) { + int candidateMethodDexIndex = directoryIn.readInt(); + int annotationSetRefListOffset = directoryIn.readInt(); + if (candidateMethodDexIndex != methodDexIndex) { + continue; + } + + Dex.Section refList = dex.open(annotationSetRefListOffset); + int parameterCount = refList.readInt(); + Annotation[][] result = new Annotation[parameterCount][]; + for (int p = 0; p < parameterCount; p++) { + int annotationSetOffset = refList.readInt(); + List<Annotation> annotations + = annotationSetToAnnotations(declaringClass, annotationSetOffset); + result[p] = annotations.toArray(new Annotation[annotations.size()]); + } + return result; + } + + return new Annotation[typesCount][0]; + } + + /* + * System annotations. + */ + + public static Object getDefaultValue(Method method) { + /* + * Dex represents this with @AnnotationDefault on annotations that have + * default values: + * + * @AnnotationDefault(value=@Foo(a=7)) + * public @interface Foo { + * int a() default 7; + * int b(); + * } + */ + + Class<?> annotationClass = method.getDeclaringClass(); + Dex dex = annotationClass.getDex(); + EncodedValueReader reader = getOnlyAnnotationValue( + dex, annotationClass, "Ldalvik/annotation/AnnotationDefault;"); + if (reader == null) { + return null; + } + + int fieldCount = reader.readAnnotation(); + if (reader.getAnnotationType() != getTypeIndex(dex, annotationClass)) { + throw new AssertionError("annotation value type != annotation class"); + } + + int methodNameIndex = Collections.binarySearch(dex.strings(), method.getName()); + for (int i = 0; i < fieldCount; i++) { + int candidateNameIndex = reader.readAnnotationName(); + if (candidateNameIndex == methodNameIndex) { + Class<?> returnType = method.getReturnType(); + return decodeValue(annotationClass, returnType, dex, reader); + } else { + reader.skipValue(); + } + } + + return null; + } + + /** + * Returns the class of which {@code c} is a direct member. If {@code c} is + * defined in a method or constructor, this is not transitive. + */ + public static Class<?> getDeclaringClass(Class<?> c) { + /* + * public class Bar { + * @EnclosingClass(value=Bar) + * public class Foo {} + * } + */ + Dex dex = c.getDex(); + EncodedValueReader reader = getOnlyAnnotationValue( + dex, c, "Ldalvik/annotation/EnclosingClass;"); + if (reader == null) { + return null; + } + return indexToType(c, dex, reader.readType()); + } + + public static AccessibleObject getEnclosingMethodOrConstructor(Class<?> c) { + /* + * public class Bar { + * public void quux(String s, int i) { + * @EnclosingMethod(value=Bar.quux(String,int)) + * class Foo {} + * } + * } + */ + Dex dex = c.getDex(); + EncodedValueReader reader = getOnlyAnnotationValue( + dex, c, "Ldalvik/annotation/EnclosingMethod;"); + if (reader == null) { + return null; + } + return indexToMethod(c, dex, reader.readMethod()); + } + + public static Class<?>[] getMemberClasses(Class<?> c) { + /* + * @MemberClasses(value=[Bar, Baz]) + * public class Foo { + * class Bar {} + * class Baz {} + * } + */ + Dex dex = c.getDex(); + EncodedValueReader reader = getOnlyAnnotationValue( + dex, c, "Ldalvik/annotation/MemberClasses;"); + if (reader == null) { + return EmptyArray.CLASS; + } + return (Class[]) decodeValue(c, Class[].class, dex, reader); + } + + /** + * @param element a class, a field, a method or a constructor. + */ + public static String getSignature(AnnotatedElement element) { + /* + * @Signature(value=["Ljava/util/List", "<", "Ljava/lang/String;", ">;"]) + * List<String> foo; + */ + Class<?> dexClass = getDexClass(element); + Dex dex = dexClass.getDex(); + EncodedValueReader reader = getOnlyAnnotationValue( + dex, element, "Ldalvik/annotation/Signature;"); + if (reader == null) { + return null; + } + String[] array = (String[]) decodeValue(dexClass, String[].class, dex, reader); + StringBuilder result = new StringBuilder(); + for (String s : array) { + result.append(s); + } + return result.toString(); + } + + /** + * @param element a method or a constructor. + */ + public static Class<?>[] getExceptions(AnnotatedElement element) { + /* + * @Throws(value=[IOException.class]) + * void foo() throws IOException; + */ + Class<?> dexClass = getDexClass(element); + Dex dex = dexClass.getDex(); + EncodedValueReader reader = getOnlyAnnotationValue( + dex, element, "Ldalvik/annotation/Throws;"); + if (reader == null) { + return EmptyArray.CLASS; + } + return (Class<?>[]) decodeValue(dexClass, Class[].class, dex, reader); + } + + public static int getInnerClassFlags(Class<?> c, int defaultValue) { + /* + * @InnerClass(accessFlags=0x01,name="Foo") + * class Foo {}; + */ + Dex dex = c.getDex(); + EncodedValueReader reader = getAnnotationReader( + dex, c, "Ldalvik/annotation/InnerClass;", 2); + if (reader == null) { + return defaultValue; + } + reader.readAnnotationName(); // accessFlags + return reader.readInt(); + } + + public static String getInnerClassName(Class<?> c) { + /* + * @InnerClass(accessFlags=0x01,name="Foo") + * class Foo {}; + */ + Dex dex = c.getDex(); + EncodedValueReader reader = getAnnotationReader( + dex, c, "Ldalvik/annotation/InnerClass;", 2); + if (reader == null) { + return null; + } + reader.readAnnotationName(); // accessFlags + reader.readInt(); + reader.readAnnotationName(); // name + return reader.peek() == EncodedValueReader.ENCODED_NULL + ? null + : (String) decodeValue(c, String.class, dex, reader); + } + + public static boolean isAnonymousClass(Class<?> c) { + /* + * @InnerClass(accessFlags=0x01,name="Foo") + * class Foo {}; + */ + Dex dex = c.getDex(); + EncodedValueReader reader = getAnnotationReader( + dex, c, "Ldalvik/annotation/InnerClass;", 2); + if (reader == null) { + return false; + } + reader.readAnnotationName(); // accessFlags + reader.readInt(); + reader.readAnnotationName(); // name + return reader.peek() == EncodedValueReader.ENCODED_NULL; + } + + /* + * Dex support. + * + * Different classes come from different Dex files. This class is careful + * to guarantee that Dex-relative indices and encoded values are interpreted + * using the Dex that they were read from. Methods that use Dex-relative + * values accept that Dex as a parameter or the class from which that Dex + * was derived. + */ + + private static int getTypeIndex(Dex dex, Class<?> c) { + return dex == c.getDex() ? c.getTypeIndex() : computeTypeIndex(dex, c); + } + + public static int computeTypeIndex(Dex dex, Class<?> c) { + if (dex == null) { + return -1; + } + int typeIndex = Collections.binarySearch(dex.typeNames(), InternalNames.getInternalName(c)); + if (typeIndex < 0) { + typeIndex = -1; + } + return typeIndex; + } + + private static int getStringIndex(Dex dex, String string) { + return Collections.binarySearch(dex.strings(), string); + } + + private static int getDirectoryOffset(Class<?> c) { + return c.getAnnotationDirectoryOffset(); + } + + private static com.android.dex.Annotation getAnnotationFromAnnotationSet( + Dex dex, int annotationSetOffset, int annotationType) { + if (annotationSetOffset == 0) { + return null; // no annotation + } + + Dex.Section setIn = dex.open(annotationSetOffset); // annotation_set_item + for (int i = 0, size = setIn.readInt(); i < size; i++) { + int annotationOffset = setIn.readInt(); + Dex.Section annotationIn = dex.open(annotationOffset); // annotation_item + com.android.dex.Annotation candidate = annotationIn.readAnnotation(); + if (candidate.getTypeIndex() == annotationType) { + return candidate; + } + } + + return null; // this set doesn't carry the annotation + } + + private static EncodedValueReader getAnnotationReader( + Dex dex, AnnotatedElement element, String annotationName, int expectedFieldCount) { + int annotationSetOffset = getAnnotationSetOffset(element); + if (annotationSetOffset == 0) { + return null; // no annotations on the class + } + + int annotationTypeIndex = Collections.binarySearch(dex.typeNames(), annotationName); + com.android.dex.Annotation annotation = getAnnotationFromAnnotationSet( + dex, annotationSetOffset, annotationTypeIndex); + if (annotation == null) { + return null; // no annotation + } + + EncodedValueReader reader = annotation.getReader(); + int fieldCount = reader.readAnnotation(); + if (reader.getAnnotationType() != annotationTypeIndex) { + throw new AssertionError(); + } + if (fieldCount != expectedFieldCount) { + return null; // not the expected values on this annotation; give up + } + + return reader; + } + + /** + * Returns a reader ready to read the only value of the annotation on + * {@code element}, or null if that annotation doesn't exist. + */ + private static EncodedValueReader getOnlyAnnotationValue( + Dex dex, AnnotatedElement element, String annotationName) { + EncodedValueReader reader = getAnnotationReader(dex, element, annotationName, 1); + if (reader == null) { + return null; + } + reader.readAnnotationName(); // skip the name + return reader; + } + + private static Class<? extends Annotation> getAnnotationClass(Class<?> context, Dex dex, + int typeIndex) { + try { + @SuppressWarnings("unchecked") // we do a runtime check + Class<? extends Annotation> result = (Class<? extends Annotation>) indexToType(context, + dex, typeIndex); + if (!result.isAnnotation()) { + throw new IncompatibleClassChangeError("Expected annotation: " + result.getName()); + } + return result; + } catch (NoClassDefFoundError ncdfe) { + return null; + } + } + + private static Class<?> indexToType(Class<?> context, Dex dex, int typeIndex) { + String internalName = dex.typeNames().get(typeIndex); + return InternalNames.getClass(context.getClassLoader(), internalName); + } + + private static AccessibleObject indexToMethod(Class<?> context, Dex dex, int methodIndex) { + MethodId methodId = dex.methodIds().get(methodIndex); + Class<?> declaringClass = indexToType(context, dex, methodId.getDeclaringClassIndex()); + String name = dex.strings().get(methodId.getNameIndex()); + Class<?>[] parametersArray = protoIndexToParameters(context, dex, methodId.getProtoIndex()); + try { + return name.equals("<init>") + ? declaringClass.getDeclaredConstructor(parametersArray) + : declaringClass.getDeclaredMethod(name, parametersArray); + } catch (NoSuchMethodException e) { + throw new IncompatibleClassChangeError("Couldn't find " + declaringClass.getName() + + "." + name + Arrays.toString(parametersArray)); + } + } + + public static Class<?>[] protoIndexToParameters(Class<?> context, Dex dex, int protoIndex) { + ProtoId proto = dex.protoIds().get(protoIndex); + TypeList parametersList = dex.readTypeList(proto.getParametersOffset()); + short[] types = parametersList.getTypes(); + Class<?>[] parametersArray = new Class[types.length]; + for (int i = 0; i < types.length; i++) { + parametersArray[i] = indexToType(context, dex, types[i]); + } + return parametersArray; + } + + public static Class<?>[] typeIndexToInterfaces(Class<?> context, Dex dex, int typeIndex) { + ClassDef def = getClassDef(dex, typeIndex); + if (def == null) { + return EmptyArray.CLASS; + } + short[] interfaces = def.getInterfaces(); + Class<?>[] result = new Class<?>[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) { + result[i] = indexToType(context, dex, interfaces[i]); + } + return result; + } + + public static int typeIndexToAnnotationDirectoryOffset(Dex dex, int typeIndex) { + ClassDef def = getClassDef(dex, typeIndex); + return def == null ? 0 : def.getAnnotationsOffset(); + } + + private static ClassDef getClassDef(Dex dex, int typeIndex) { + if (typeIndex == -1) { + return null; + } + for (ClassDef def : dex.classDefs()) { + if (def.getTypeIndex() == typeIndex) { + return def; + } + } + throw new AssertionError(); + } + + private static List<Annotation> annotationSetToAnnotations(Class<?> context, int offset) { + if (offset == 0) { + return Collections.emptyList(); // no annotations in the set + } + + Dex dex = context.getDex(); + Dex.Section setIn = dex.open(offset); // annotation_set_item + int size = setIn.readInt(); + List<Annotation> result = new ArrayList<Annotation>(size); + + for (int i = 0; i < size; i++) { + int annotationOffset = setIn.readInt(); + Dex.Section annotationIn = dex.open(annotationOffset); // annotation_item + com.android.dex.Annotation annotation = annotationIn.readAnnotation(); + if (annotation.getVisibility() != VISIBILITY_RUNTIME) { + continue; + } + Class<? extends Annotation> annotationClass = + getAnnotationClass(context, dex, annotation.getTypeIndex()); + if (annotationClass != null) { + result.add(toAnnotationInstance(context, dex, annotationClass, annotation.getReader())); + } + } + return result; + } + + private static <A extends Annotation> A toAnnotationInstance(Class<?> context, + Class<A> annotationClass, com.android.dex.Annotation annotation) { + return toAnnotationInstance(context, context.getDex(), annotationClass, + annotation.getReader()); + } + + private static <A extends Annotation> A toAnnotationInstance(Class<?> context, Dex dex, + Class<A> annotationClass, EncodedValueReader reader) { + int fieldCount = reader.readAnnotation(); + if (annotationClass != indexToType(context, dex, reader.getAnnotationType())) { + throw new AssertionError("annotation value type != return type"); + } + AnnotationMember[] members = new AnnotationMember[fieldCount]; + for (int i = 0; i < fieldCount; i++) { + int name = reader.readAnnotationName(); + String nameString = dex.strings().get(name); + Method method; + try { + method = annotationClass.getMethod(nameString, NO_ARGUMENTS); + } catch (NoSuchMethodException e) { + throw new IncompatibleClassChangeError( + "Couldn't find " + annotationClass.getName() + "." + nameString); + } + Class<?> returnType = method.getReturnType(); + Object value = decodeValue(context, returnType, dex, reader); + members[i] = new AnnotationMember(nameString, value, returnType, method); + } + return AnnotationFactory.createAnnotation(annotationClass, members); + } + + private static Object decodeValue(Class<?> context, Class<?> type, + Dex dex, EncodedValueReader reader) { + if (type.isArray()) { + int size = reader.readArray(); + Class<?> componentType = type.getComponentType(); + Object array = Array.newInstance(componentType, size); + for (int i = 0; i < size; i++) { + Array.set(array, i, decodeValue(context, componentType, dex, reader)); + } + return array; + } else if (type.isEnum()) { + int fieldIndex = reader.readEnum(); + FieldId fieldId = dex.fieldIds().get(fieldIndex); + String enumName = dex.strings().get(fieldId.getNameIndex()); + @SuppressWarnings({"unchecked", "rawtypes"}) // Class.isEnum is the runtime check + Class<? extends Enum> enumType = (Class<? extends Enum>) type; + return Enum.valueOf(enumType, enumName); + } else if (type.isAnnotation()) { + @SuppressWarnings("unchecked") // Class.isAnnotation is the runtime check + Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) type; + return toAnnotationInstance(context, dex, annotationClass, reader); + } else if (type == String.class) { + int index = reader.readString(); + return dex.strings().get(index); + } else if (type == Class.class) { + int index = reader.readType(); + return indexToType(context, dex, index); + } else if (type == byte.class) { + return reader.readByte(); + } else if (type == short.class) { + return reader.readShort(); + } else if (type == int.class) { + return reader.readInt(); + } else if (type == long.class) { + return reader.readLong(); + } else if (type == float.class) { + return reader.readFloat(); + } else if (type == double.class) { + return reader.readDouble(); + } else if (type == char.class) { + return reader.readChar(); + } else if (type == boolean.class) { + return reader.readBoolean(); + } else { + // is null legit? + throw new AssertionError("Unexpected annotation value type: " + type); + } + } +} diff --git a/libart/src/main/java/libcore/reflect/AnnotationFactory.java b/libart/src/main/java/libcore/reflect/AnnotationFactory.java new file mode 100644 index 0000000..f14a9e8 --- /dev/null +++ b/libart/src/main/java/libcore/reflect/AnnotationFactory.java @@ -0,0 +1,314 @@ +/* + * 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 libcore.reflect; + +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; + +/** + * 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 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 + */ + @SuppressWarnings("unchecked") // newProxyInstance returns the type of its interfaces + public static <A extends Annotation> A createAnnotation( + Class<A> annotationType, AnnotationMember[] elements) { + AnnotationFactory factory = new AnnotationFactory(annotationType, elements); + return (A) Proxy.newProxyInstance( annotationType.getClassLoader(), + new Class[]{annotationType}, factory); + } + + 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 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 == AnnotationMember.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 == AnnotationMember.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 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/libart/src/main/java/libcore/reflect/GenericSignatureParser.java b/libart/src/main/java/libcore/reflect/GenericSignatureParser.java new file mode 100644 index 0000000..0c94eba --- /dev/null +++ b/libart/src/main/java/libcore/reflect/GenericSignatureParser.java @@ -0,0 +1,496 @@ +/* + * 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 libcore.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 final class GenericSignatureParser { + + // TODO: unify this with InternalNames + + 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 { + Method m = (Method) genericDecl; + this.formalTypeParameters = ListOfVariables.EMPTY; + this.parameterTypes = new ListOfTypes(m.getParameterTypes()); + this.exceptionTypes = new ListOfTypes(m.getExceptionTypes()); + this.returnType = m.getReturnType(); + } + } + + /** + * 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 { + Constructor c = (Constructor) genericDecl; + this.formalTypeParameters = ListOfVariables.EMPTY; + this.parameterTypes = new ListOfTypes(c.getParameterTypes()); + this.exceptionTypes = new ListOfTypes(c.getExceptionTypes()); + } + } + + /** + * 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(); + } + + TypeVariableImpl<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 TypeVariableImpl<GenericDeclaration>(genericDecl, name, bounds); + } + + Type parseFieldTypeSignature() { + // FieldTypeSignature ::= ClassTypeSignature | ArrayTypeSignature + // | TypeVariableSignature. + + switch (symbol) { + case 'L': + return parseClassTypeSignature(); + case '[': + // ArrayTypeSignature ::= "[" TypSignature. + scanSymbol(); + return new GenericArrayTypeImpl(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(); + ParameterizedTypeImpl parentType = + new ParameterizedTypeImpl(null, qualIdent.toString(), typeArgs, loader); + ParameterizedTypeImpl type = parentType; + + while (symbol == '.') { + // Deal with Member Classes: + scanSymbol(); + scanIdentifier(); + qualIdent.append("$").append(identifier); // FIXME: is "$" correct? + typeArgs = parseOptTypeArguments(); + type = new ParameterizedTypeImpl(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 WildcardTypeImpl(extendsBound, superBound); + } + else if (symbol == '+') { + scanSymbol(); + extendsBound.add(parseFieldTypeSignature()); + return new WildcardTypeImpl(extendsBound, superBound); + } + else if (symbol == '-') { + scanSymbol(); + superBound.add(parseFieldTypeSignature()); + extendsBound.add(Object.class); + return new WildcardTypeImpl(extendsBound, superBound); + } + else { + return parseFieldTypeSignature(); + } + } + + TypeVariableImpl<GenericDeclaration> parseTypeVariableSignature() { + // TypeVariableSignature ::= "T" Ident ";". + expect('T'); + scanIdentifier(); + expect(';'); + // Reference to type variable: + // Note: we don't know the declaring GenericDeclaration yet. + return new TypeVariableImpl<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/libart/src/main/java/libcore/reflect/ListOfVariables.java b/libart/src/main/java/libcore/reflect/ListOfVariables.java new file mode 100644 index 0000000..5d96817 --- /dev/null +++ b/libart/src/main/java/libcore/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 libcore.reflect; + +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; + +final class ListOfVariables { + public static final TypeVariable[] EMPTY = new TypeVariableImpl[0]; + + final 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/libart/src/main/java/libcore/reflect/TypeVariableImpl.java b/libart/src/main/java/libcore/reflect/TypeVariableImpl.java new file mode 100644 index 0000000..2eb9827 --- /dev/null +++ b/libart/src/main/java/libcore/reflect/TypeVariableImpl.java @@ -0,0 +1,136 @@ +/* + * 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 libcore.reflect; + +import java.lang.reflect.AccessibleObject; +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 TypeVariableImpl<D extends GenericDeclaration> implements TypeVariable<D> { + private TypeVariableImpl<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 + */ + TypeVariableImpl(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 + */ + TypeVariableImpl(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 = (GenericDeclaration) AnnotationAccess.getEnclosingMethodOrConstructor(cl); + 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 = (TypeVariableImpl<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/libart/src/main/java/sun/misc/Unsafe.java b/libart/src/main/java/sun/misc/Unsafe.java new file mode 100644 index 0000000..aa7b6de --- /dev/null +++ b/libart/src/main/java/sun/misc/Unsafe.java @@ -0,0 +1,338 @@ +/* + * 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 field.getOffset(); + } + + /** + * 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) { + Class<?> component = clazz.getComponentType(); + if (component == null) { + throw new IllegalArgumentException("Valid for array classes only: " + clazz); + } + // TODO: make the following not specific to the object model. + int offset = 12; + if (component == long.class || component == double.class) { + offset += 4; // 4 bytes of padding. + } + return offset; + } + + /** + * 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) { + Class<?> component = clazz.getComponentType(); + if (component == null) { + throw new IllegalArgumentException("Valid for array classes only: " + clazz); + } + // TODO: make the following not specific to the object model. + if (!clazz.isPrimitive()) { + return 4; + } else if (component == long.class || component == double.class) { + return 8; + } else if (component == int.class || component == float.class) { + return 4; + } else if (component == char.class || component == short.class) { + return 2; + } else { + // component == byte.class || component == boolean.class. + return 1; + } + } + + /** + * 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); +} |