/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Common Public License (CPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/cpl1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.classloader;
import org.jikesrvm.VM;
import org.jikesrvm.util.VM_HashMap;
import org.jikesrvm.util.VM_StringUtilities;
import org.vmmagic.pragma.Uninterruptible;
/**
* An utf8-encoded byte string.
*
* VM_Atom's are interned (canonicalized)
* so they may be compared for equality using the "==" operator.
*
* VM_Atoms are used to represent names, descriptors, and string literals
* appearing in a class's constant pool.
*
* There is almost always a zero-length VM_Atom, since any class which
* contains statements like:
* return "";
* will have one in its constant pool.
*/
public final class VM_Atom implements VM_ClassLoaderConstants {
/**
* Used to canonicalize VM_Atoms: Key => VM_Atom
*/
private static final VM_HashMap<Key, VM_Atom> dictionary = new VM_HashMap<Key, VM_Atom>();
/**
* Dictionary of all VM_Atom instances.
*/
private static VM_Atom[] atoms = new VM_Atom[16000];
/**
* Used to assign ids. Don't use id 0 to allow clients to use id 0 as a 'null'.
*/
private static int nextId = 1;
/**
* The utf8 value this atom represents
*/
private final byte[] val;
/**
* Cached hash code for this atom.
*/
private final int hash;
/**
* The id of this atom
*/
private final int id;
/**
*@return the id of this atom.
*/
int getId() { return id; }
/**
* Find or create an atom.
* @param str atom value, as string literal whose characters are unicode
* @return atom
*/
public static VM_Atom findOrCreateUnicodeAtom(String str) {
byte[] utf8 = VM_UTF8Convert.toUTF8(str);
return findOrCreate(utf8, true);
}
/**
* Find an atom.
* @param str atom value, as string literal whose characters are unicode
* @return atom or null if it doesn't already exist
*/
public static VM_Atom findUnicodeAtom(String str) {
byte[] utf8 = VM_UTF8Convert.toUTF8(str);
return findOrCreate(utf8, false);
}
/**
* Find or create an atom.
* @param str atom value, as string literal whose characters are from
* ascii subset of unicode (not including null)
* @return atom
*/
public static VM_Atom findOrCreateAsciiAtom(String str) {
return findOrCreate(VM_StringUtilities.stringToBytes(str), true);
}
/**
* Find an atom.
* @param str atom value, as string literal whose characters are from
* ascii subset of unicode (not including null)
* @return atom or null if it doesn't already exist
*/
public static VM_Atom findAsciiAtom(String str) {
return findOrCreate(VM_StringUtilities.stringToBytes(str), false);
}
/**
* Find or create an atom.
* @param utf8 atom value, as utf8 encoded bytes
* @return atom
*/
public static VM_Atom findOrCreateUtf8Atom(byte[] utf8) {
return findOrCreate(utf8, true);
}
/**
* Find an atom.
* @param utf8 atom value, as utf8 encoded bytes
* @return atom or null it it doesn't already exist
*/
public static VM_Atom findUtf8Atom(byte[] utf8) {
return findOrCreate(utf8, false);
}
/**
* @param id the id of an Atom
* @return the VM_Atom whose id was given
*/
@Uninterruptible
public static VM_Atom getAtom(int id) {
return atoms[id];
}
private static VM_Atom findOrCreate(byte[] utf8, int off, int len) {
byte[] val = new byte[len];
for (int i = 0; i < len; ++i) {
val[i] = utf8[off++];
}
return findOrCreate(val, true);
}
/** This is the findOrCreate() method through which all VM_Atoms are
* ultimately created. The constructor for VM_Atom is a private method, so
* someone has to call one of the public findOrCreate() methods to get a new
* one. And they all feed through here. */
private static synchronized VM_Atom findOrCreate(byte[] bytes, boolean create) {
Key key = new Key(bytes);
VM_Atom val = dictionary.get(key);
if (val != null || !create) return val;
val = new VM_Atom(key, nextId++);
if (val.id == atoms.length) {
VM_Atom[] tmp = new VM_Atom[atoms.length + 1000];
System.arraycopy(atoms, 0, tmp, 0, atoms.length);
atoms = tmp;
}
atoms[val.id] = val;
dictionary.put(key, val);
return val;
}
//-------------//
// conversions //
//-------------//
/**
* Return printable representation of "this" atom.
* Does not correctly handle UTF8 translation.
*/
public String toString() {
return VM_StringUtilities.asciiBytesToString(val);
}
/** Get at a string-like representation without doing any heap allocation.
* Hideous but necessary. We will use it in the VM_PrintContainer class. */
@Uninterruptible
public byte[] toByteArray() {
return val;
}
/**
* Return printable representation of "this" atom.
*/
public String toUnicodeString() throws java.io.UTFDataFormatException {
return VM_UTF8Convert.fromUTF8(val);
}
/**
* Return array descriptor corresponding to "this" array-element descriptor.
* this: array-element descriptor - something like "I" or "Ljava/lang/Object;"
* @return array descriptor - something like "[I" or "[Ljava/lang/Object;"
*/
VM_Atom arrayDescriptorFromElementDescriptor() {
if (VM.VerifyAssertions) {
VM._assert(val.length > 0);
}
byte[] sig = new byte[1 + val.length];
sig[0] = (byte) '[';
for (int i = 0, n = val.length; i < n; ++i) {
sig[i + 1] = val[i];
}
return findOrCreate(sig, true);
}
/**
* Return class descriptor corresponding to "this" class name.
* this: class name - something like "java/lang/Object"
* @return class descriptor - something like "Ljava/lang/Object;"
*/
public VM_Atom descriptorFromClassName() {
if (VM.VerifyAssertions) {
VM._assert(val.length > 0);
}
if (val[0] == '[') return this;
byte[] sig = new byte[1 + val.length + 1];
sig[0] = (byte) 'L';
for (int i = 0, n = val.length; i < n; ++i) {
sig[i + 1] = val[i];
}
sig[sig.length - 1] = (byte) ';';
return findOrCreate(sig, true);
}
/**
* Return class name corresponding to "this" class descriptor.
* this: class descriptor - something like "Ljava/lang/String;"
* @return class name - something like "java.lang.String"
*/
public String classNameFromDescriptor() {
if (VM.VerifyAssertions) {
VM._assert(val.length > 0);
VM._assert(val[0] == 'L' && val[val.length - 1] == ';');
}
return VM_StringUtilities.asciiBytesToString(val, 1, val.length - 2).replace('/', '.');
}
/**
* Return name of class file corresponding to "this" class descriptor.
* this: class descriptor - something like "Ljava/lang/String;"
* @return class file name - something like "java/lang/String.class"
*/
public String classFileNameFromDescriptor() {
if (VM.VerifyAssertions) {
VM._assert(val.length > 0);
VM._assert(val[0] == 'L' && val[val.length - 1] == ';');
}
return VM_StringUtilities.asciiBytesToString(val, 1, val.length - 2) + ".class";
}
//----------------//
// classification //
//----------------//
/**
* Is "this" atom a reserved member name?
* Note: Sun has reserved all member names starting with '<' for future use.
* At present, only <init> and <clinit> are used.
*/
@Uninterruptible
public boolean isReservedMemberName() {
if (VM.VerifyAssertions) VM._assert(val.length > 0);
return val[0] == '<';
}
/**
* Is "this" atom a class descriptor?
*/
@Uninterruptible
public boolean isClassDescriptor() {
if (VM.VerifyAssertions) VM._assert(val.length > 0);
return val[0] == 'L';
}
/**
* Is "this" atom an array descriptor?
*/
@Uninterruptible
public boolean isArrayDescriptor() {
if (VM.VerifyAssertions) VM._assert(val.length > 0);
return val[0] == '[';
}
/**
* Is "this" atom a method descriptor?
*/
@Uninterruptible
public boolean isMethodDescriptor() {
if (VM.VerifyAssertions) VM._assert(val.length > 0);
return val[0] == '(';
}
//--------------------//
// descriptor parsing //
//--------------------//
/**
* Parse "this" method descriptor to obtain description of method's
* return type.
* this: method descriptor - something like "(III)V"
* @return type description
*/
public VM_TypeReference parseForReturnType(ClassLoader cl) {
if (VM.VerifyAssertions) {
VM._assert(val.length > 0);
VM._assert(val[0] == '(', "Method descriptors start with `(`");
}
int i = 0;
while (val[i++] != ')') {
if (VM.VerifyAssertions) {
VM._assert(i < val.length, "Method descriptor missing closing ')'");
}
}
if (VM.VerifyAssertions) {
VM._assert(i < val.length, "Method descriptor missing type after closing ')'");
}
switch (val[i]) {
case VoidTypeCode:
return VM_TypeReference.Void;
case BooleanTypeCode:
return VM_TypeReference.Boolean;
case ByteTypeCode:
return VM_TypeReference.Byte;
case ShortTypeCode:
return VM_TypeReference.Short;
case IntTypeCode:
return VM_TypeReference.Int;
case LongTypeCode:
return VM_TypeReference.Long;
case FloatTypeCode:
return VM_TypeReference.Float;
case DoubleTypeCode:
return VM_TypeReference.Double;
case CharTypeCode:
return VM_TypeReference.Char;
case ClassTypeCode: // fall through
case ArrayTypeCode:
return VM_TypeReference.findOrCreate(cl, findOrCreate(val, i, val.length - i));
default:
if (VM.VerifyAssertions) {
VM._assert(false,
"Need a valid method descriptor; got \"" +
this +
"\"; can't parse the character '" +
byteToString(val[i]) +
"'");
}
return null; // NOTREACHED
}
}
private String byteToString(byte b) {
return Character.toString((char) b);
}
/**
* Parse "this" method descriptor to obtain descriptions of method's
* parameters.
* this: method descriptor - something like "(III)V"
* @return parameter descriptions
*/
public VM_TypeReference[] parseForParameterTypes(ClassLoader cl) {
if (VM.VerifyAssertions) {
VM._assert(val.length > 0);
VM._assert(val[0] == '(', "Method descriptors start with `(`");
}
VM_TypeReferenceVector sigs = new VM_TypeReferenceVector();
int i = 1;
while (true) {
if (VM.VerifyAssertions) {
VM._assert(i < val.length, "Method descriptor missing closing `)`");
}
switch (val[i++]) {
case VoidTypeCode:
sigs.addElement(VM_TypeReference.Void);
continue;
case BooleanTypeCode:
sigs.addElement(VM_TypeReference.Boolean);
continue;
case ByteTypeCode:
sigs.addElement(VM_TypeReference.Byte);
continue;
case ShortTypeCode:
sigs.addElement(VM_TypeReference.Short);
continue;
case IntTypeCode:
sigs.addElement(VM_TypeReference.Int);
continue;
case LongTypeCode:
sigs.addElement(VM_TypeReference.Long);
continue;
case FloatTypeCode:
sigs.addElement(VM_TypeReference.Float);
continue;
case DoubleTypeCode:
sigs.addElement(VM_TypeReference.Double);
continue;
case CharTypeCode:
sigs.addElement(VM_TypeReference.Char);
continue;
case ClassTypeCode: {
int off = i - 1;
while (val[i++] != ';') {
if (VM.VerifyAssertions) {
VM._assert(i < val.length, "class descriptor missing a final ';'");
}
}
sigs.addElement(VM_TypeReference
.findOrCreate(cl, findOrCreate(val, off, i - off)));
continue;
}
case ArrayTypeCode: {
int off = i - 1;
while (val[i] == ArrayTypeCode) {
if (VM.VerifyAssertions) {
VM._assert(i < val.length, "malformed array descriptor");
}
++i;
}
if (val[i++] == ClassTypeCode) while (val[i++] != ';') ;
sigs.addElement(VM_TypeReference.findOrCreate(cl, findOrCreate(val, off, i - off)));
continue;
}
case(byte) ')': // end of parameter list
return sigs.finish();
default:
if (VM.VerifyAssertions) {
VM._assert(false,
"The class descriptor \"" +
this +
"\" contains the illegal" +
" character '" +
byteToString(val[i]) +
"'");
}
}
}
}
/**
* Return the underlying set of bytes for the VM_Atom. This can be used
* to perform comparisons without requiring the allocation of a string.
*/
@Uninterruptible
public byte[] getBytes() {
return val;
}
/**
* Parse "this" field, parameter, or return descriptor to obtain its
* type code.
* this: descriptor - something like "Ljava/lang/String;" or "[I" or "I"
* @return type code - something like ObjectTypeCode, ArrayTypeCode, or
* IntTypeCode
*
* The type code will be one of the following constants:
*
* <pre>
* constant value
* ---------------- -----
* ClassTypeCode 'L'
* ArrayTypeCode '['
* VoidTypeCode 'V'
* BooleanTypeCode 'Z'
* ByteTypeCode 'B'
* ShortTypeCode 'S'
* IntTypeCode 'I'
* LongTypeCode 'J'
* FloatTypeCode 'F'
* DoubleTypeCode 'D'
* CharTypeCode 'C'
* </pre>
*/
public byte parseForTypeCode() throws IllegalArgumentException {
if (VM.VerifyAssertions) {
VM._assert(val.length > 0);
}
return val[0];
}
/**
* Parse "this" array descriptor to obtain number of dimensions in
* corresponding array type.
* this: descriptor - something like "[Ljava/lang/String;" or "[[I"
* @return dimensionality - something like "1" or "2"
*/
public int parseForArrayDimensionality() {
if (VM.VerifyAssertions) {
VM._assert(val.length > 1, "An array descriptor has at least two characters");
VM._assert(val[0] == '[', "An array descriptor must start with '['");
}
for (int i = 0; ; ++i) {
if (VM.VerifyAssertions) {
VM._assert(i < val.length, "Malformed array descriptor: it can't just have [ characters");
}
if (val[i] != '[') {
return i;
}
}
}
/**
* Parse "this" array descriptor to obtain type code for its element type.
* this: descriptor - something like "[Ljava/lang/String;" or "[I"
* @return type code - something like VM.ObjectTypeCode or VM.IntTypeCode
* The type code will be one of the constants appearing in the table above.
*
* Implementation note: This is supposed to be uninterruptible, since another
* allegedly uninterruptible method (VM_Array.getLogElementSize()) calls it.
*/
@Uninterruptible
public byte parseForArrayElementTypeCode() {
if (VM.VerifyAssertions) {
VM._assert(val.length > 1, "An array descriptor has at least two characters");
VM._assert(val[0] == '[', "An array descriptor must start with '['");
}
return val[1];
}
/**
* Return the innermost element type reference for an array
*/
public VM_Atom parseForInnermostArrayElementDescriptor() {
if (VM.VerifyAssertions) {
VM._assert(val.length > 1, "An array descriptor has at least two characters");
VM._assert(val[0] == '[', "An array descriptor must start with '['");
}
int i = 0;
while (val[i] == '[') {
if (VM.VerifyAssertions) {
VM._assert(i < val.length, "Malformed array descriptor: it can't just have [ characters");
}
i++;
}
return findOrCreate(val, i, val.length - i);
}
/**
* Parse "this" array descriptor to obtain descriptor for array's element
* type.
* this: array descriptor - something like "[I"
* @return array element descriptor - something like "I"
*/
public VM_Atom parseForArrayElementDescriptor() {
if (VM.VerifyAssertions) {
VM._assert(val.length > 1, "An array descriptor has at least two characters");
VM._assert(val[0] == '[', "An array descriptor must start with '['");
}
return findOrCreate(val, 1, val.length - 1);
}
/**
* The set of class prefixes that MUST be loaded by bootstrap classloader.
* @see #isBootstrapClassDescriptor()
*/
private static final byte[][] BOOTSTRAP_CLASS_PREFIX_SET =
{"Ljava/".getBytes(),
"Lorg/jikesrvm/".getBytes(),
"Lgnu/java/".getBytes(),
"Lgnu/classpath/debug/".getBytes(),
"Lgnu/classpath/jdwp/".getBytes(),
"Lgnu/classpath/NotImplementedException".getBytes(),
"Lgnu/classpath/Pair".getBytes(),
"Lgnu/classpath/Pointer".getBytes(),
"Lgnu/classpath/Pointer32".getBytes(),
"Lgnu/classpath/Pointer64".getBytes(),
"Lgnu/classpath/ServiceFactory".getBytes(),
"Lgnu/classpath/ServiceProviderLoadingAction".getBytes(),
"Lgnu/classpath/SystemProperties".getBytes(),
"Lorg/vmmagic/".getBytes(),
"Lorg/mmtk/".getBytes()};
/**
* The set of class prefixes that MUST NOT be loaded by bootstrap classloader.
* @see #isBootstrapClassDescriptor()
*/
private static final byte[][] NON_BOOTSTRAP_CLASS_PREFIX_SET =
{"Lorg/jikesrvm/tools/ant/".getBytes(),
"Lorg/jikesrvm/tools/apt/".getBytes(),
"Lorg/jikesrvm/tools/template/".getBytes()};
/**
* The set of class prefixes for core RVM classes.
* @see #isRVMDescriptor()
*/
private static final byte[][] RVM_CLASS_PREFIXES =
{"Lorg/jikesrvm/".getBytes(), "Lorg/vmmagic/".getBytes(), "Lorg/mmtk/".getBytes()};
/**
* @return true if this is a class descriptor of a bootstrap class
* (ie a class that must be loaded by the bootstrap class loader)
*/
public boolean isBootstrapClassDescriptor() {
non_bootstrap_outer:
for (final byte[] test : NON_BOOTSTRAP_CLASS_PREFIX_SET) {
if (test.length > val.length) continue;
for (int j = 0; j < test.length; j++) {
if (val[j] != test[j]) {
continue non_bootstrap_outer;
}
}
return false;
}
bootstrap_outer:
for (final byte[] test : BOOTSTRAP_CLASS_PREFIX_SET) {
if (test.length > val.length) continue;
for (int j = 0; j < test.length; j++) {
if (val[j] != test[j]) {
continue bootstrap_outer;
}
}
return true;
}
return false;
}
/**
* @return true if this is a class descriptor of a RVM core class. This is
* defined as one that it would be unwise to invalidate, since invalidating
* it might make it impossible to recompile.
*/
public boolean isRVMDescriptor() {
outer:
for (final byte[] test : RVM_CLASS_PREFIXES) {
if (test.length > val.length) continue;
for (int j = 0; j < test.length; j++) {
if (val[j] != test[j]) {
continue outer;
}
}
return true;
}
return false;
}
//-------------//
// annotations //
//-------------//
/**
* Create an annotation name from a class name. For example
* Lfoo.bar; becomes Lfoo.bar$$; NB in Sun VMs the annotation name
* of the first annotation is $Proxy1. Classpath may later rely on
* this to implement serialization correctly.
*/
public VM_Atom annotationInterfaceToAnnotationClass() {
byte[] annotationClassName_tmp = new byte[val.length + 2];
System.arraycopy(val, 0, annotationClassName_tmp, 0, val.length - 1);
annotationClassName_tmp[val.length - 1] = '$';
annotationClassName_tmp[val.length] = '$';
annotationClassName_tmp[val.length + 1] = ';';
return VM_Atom.findOrCreateUtf8Atom(annotationClassName_tmp);
}
/**
* Create a class name from a type name. For example Lfoo.bar$$;
* becomes the string foo.bar
*/
public String annotationClassToAnnotationInterface() {
if (VM.VerifyAssertions) {
VM._assert(val.length > 0);
VM._assert(val[0] == 'L' && val[val.length - 1] == ';', toString());
}
return VM_StringUtilities.asciiBytesToString(val, 1, val.length - 4).replace('/', '.');
}
/**
* Is this an annotation class name of the form Lfoo.bar$$;
*/
public boolean isAnnotationClass() {
return (val.length > 4) && (val[val.length - 3] == '$') && (val[val.length - 2] == '$');
}
//-----------//
// debugging //
//-----------//
@Uninterruptible
public void sysWrite() {
for (int i = 0, n = val.length; i < n; ++i) {
VM.sysWrite((char) val[i]);
}
}
@Uninterruptible
public int length() {
return val.length;
}
/**
* Create atom from the key that maps to it.
*/
private VM_Atom(Key key, int id) {
this.val = key.val;
this.hash = key.hashCode();
this.id = id;
}
public int hashCode() {
return hash;
}
/*
* We canonicalize VM_Atoms, therefore we can use == for equals
*/
@Uninterruptible
public boolean equals(Object other) {
return this == other;
}
/**
* A Key into the atom dictionary.
* We do this to enable VM_Atom.equals to be efficient (==).
*/
private static class Key {
final byte[] val;
Key(byte[] utf8) {
val = utf8;
}
public final int hashCode() {
int tmp = 99989;
for (int i = val.length; --i >= 0;) {
tmp = 99991 * tmp + val[i];
}
return tmp;
}
public final boolean equals(Object other) {
if (this == other) return true;
if (other instanceof Key) {
Key that = (Key) other;
if (val.length != that.val.length) return false;
for (int i = 0; i < val.length; i++) {
if (val[i] != that.val[i]) return false;
}
return true;
} else {
return false;
}
}
}
}