/*
 *  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 static org.jikesrvm.classloader.ClassLoaderConstants.ArrayTypeCode;
import static org.jikesrvm.classloader.ClassLoaderConstants.BooleanTypeCode;
import static org.jikesrvm.classloader.ClassLoaderConstants.ByteTypeCode;
import static org.jikesrvm.classloader.ClassLoaderConstants.CharTypeCode;
import static org.jikesrvm.classloader.ClassLoaderConstants.ClassTypeCode;
import static org.jikesrvm.classloader.ClassLoaderConstants.DoubleTypeCode;
import static org.jikesrvm.classloader.ClassLoaderConstants.FloatTypeCode;
import static org.jikesrvm.classloader.ClassLoaderConstants.IntTypeCode;
import static org.jikesrvm.classloader.ClassLoaderConstants.LongTypeCode;
import static org.jikesrvm.classloader.ClassLoaderConstants.ShortTypeCode;
import static org.jikesrvm.classloader.ClassLoaderConstants.VoidTypeCode;

import java.io.UTFDataFormatException;
import java.lang.ref.WeakReference;
import java.util.WeakHashMap;

import org.jikesrvm.VM;
import org.jikesrvm.runtime.Statics;
import org.jikesrvm.util.ImmutableEntryHashMapRVM;
import org.jikesrvm.util.StringUtilities;
import org.vmmagic.pragma.Pure;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Offset;

/**
 * An  utf8-encoded byte string.
 *
 * Atom's are interned (canonicalized)
 * so they may be compared for equality using the "==" operator.
 *
 * Atoms are used to represent names, descriptors, and string literals
 * appearing in a class's constant pool.
 *
 * There is almost always a zero-length Atom, since any class which
 * contains statements like:
 *          return "";
 * will have one in its constant pool.
 */
public final class Atom {

  /**
   * Used to canonicalize Atoms: possibly non-canonical Atom => Atom
   */
  private static final ImmutableEntryHashMapRVM<Atom, Atom> dictionary =
    new ImmutableEntryHashMapRVM<Atom, Atom>(12000);

  /**
   * 2^LOG_ROW_SIZE is the number of elements per row
   */
  private static final int LOG_ROW_SIZE = 10;
  /**
   * Mask to ascertain row from id number
   */
  private static final int ROW_MASK = (1 << LOG_ROW_SIZE)-1;
  /**
   * Dictionary of all Atom instances.
   */
  private static Atom[][] atoms = new Atom[36][1 << LOG_ROW_SIZE];

  /**
   * Used to assign ids. Don't use id 0 to allow clients to use id 0 as a 'null'.
   */
  private static int nextId = 1;

  /**
   * A reference to either a unicode String encoding the atom, an offset in the
   * JTOC holding a unicode string encoding the atom or null.
   */
  private Object unicodeStringOrJTOCoffset;

  /**
   * The utf8 value this atom represents
   */
  private final byte[] val;

  /**
   * 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
   */
  @Pure
  public static Atom findOrCreateUnicodeAtom(String str) {
    return findOrCreate(null, true, str);
  }

  /**
   * 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 Atom findUnicodeAtom(String str) {
    return findOrCreate(null, false, str);
  }

  /**
   * 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
   */
  @Pure
  public static Atom findOrCreateAsciiAtom(String str) {
    return findOrCreate(null, true, str);
  }

  /**
   * 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 Atom findAsciiAtom(String str) {
    return findOrCreate(null, false, str);
  }

  /**
   * Find or create an atom.
   * @param utf8 atom value, as utf8 encoded bytes
   * @return atom
   */
  @Pure
  public static Atom findOrCreateUtf8Atom(byte[] utf8) {
    return findOrCreate(utf8, true, null);
  }

  /**
   * Find an atom.
   * @param utf8 atom value, as utf8 encoded bytes
   * @return atom or null it it doesn't already exist
   */
  public static Atom findUtf8Atom(byte[] utf8) {
    return findOrCreate(utf8, false, null);
  }

  /**
   * Find an atom from the subsequence of another
   * @param utf8 byte backing of atom
   * @param off offset of new atom
   * @param len length of new atom
   * @param str possible string encoding of atom or null
   * @return atom
   */
  private static Atom findOrCreate(byte[] utf8, int off, int len, String str) {
    if (str != null) {
      // string substring is cheap, so try to find using this if possible
      Atom val = new Atom(null, -1, str.substring(off, off+len));
      val = dictionary.get(val);
      if (val != null) return val;
    }
    byte[] val = new byte[len];
    for (int i = 0; i < len; ++i) {
      val[i] = utf8[off++];
    }
    return findOrCreate(val, true, null);
  }

  /**
   * This is the findOrCreate() method through which all Atoms are
   * ultimately created.   The constructor for 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 Atom findOrCreate(byte[] bytes, boolean create, String str) {
    Atom val = new Atom(bytes, -1, str);
    val = dictionary.get(val);
    if (val != null || !create) return val;
    synchronized(Atom.class) {
      val = new Atom(bytes, nextId++, str);
      int column = val.id >> LOG_ROW_SIZE;
      if (column == atoms.length) {
        Atom[][] tmp = new Atom[column+1][];
        for (int i=0; i < column; i++) {
          tmp[i] = atoms[i];
        }
        atoms = tmp;
        atoms[column] = new Atom[1 << LOG_ROW_SIZE];
      }
      atoms[column][val.id & ROW_MASK] = val;
      dictionary.put(val, val);
    }
    return val;
  }

  /**
   * @param id the id of an Atom
   * @return the Atom whose id was given
   */
  @Pure
  @Uninterruptible
  public static Atom getAtom(int id) {
    return atoms[id >> LOG_ROW_SIZE][id & ROW_MASK];
  }

  //-------------//
  // conversions //
  //-------------//

  /**
   * Return printable representation of "this" atom.
   * Does not correctly handle UTF8 translation.
   */
  @Pure
  public String toString() {
    return StringUtilities.asciiBytesToString(val);
  }

  /**
   * Get at a string-like representation without doing any heap allocation.
   * Hideous but necessary.  We will use it in the PrintContainer class.
   */
  @Uninterruptible
  public byte[] toByteArray() {
    return val;
  }

  /**
   * Return atom as a string literal
   */
  @Pure
  public synchronized String toUnicodeString() throws java.io.UTFDataFormatException {
    if (unicodeStringOrJTOCoffset == null) {
      String s = UTF8Convert.fromUTF8(val);
      if (VM.runningVM) {
        s = InternedStrings.internUnfoundString(s);
        unicodeStringOrJTOCoffset = s;
      } else if (!VM.writingImage) {
        s = s.intern();
        int offset = Statics.findOrCreateObjectLiteral(s);
        unicodeStringOrJTOCoffset = offset;
      }
      return s;
    } else if (unicodeStringOrJTOCoffset instanceof String) {
      return (String)unicodeStringOrJTOCoffset;
    } else {
      if (VM.runningVM) {
        return (String)Statics.getSlotContentsAsObject(Offset.fromIntSignExtend((Integer)unicodeStringOrJTOCoffset));
      } else {
        return UTF8Convert.fromUTF8(val).intern();
      }
    }
  }

  /**
   * Atom as string literal or null if atom hasn't been converted
   */
  private synchronized String toUnicodeStringInternal() {
    if (unicodeStringOrJTOCoffset == null) {
      return null;
    } else if (unicodeStringOrJTOCoffset instanceof String) {
      return (String)unicodeStringOrJTOCoffset;
    } else {
      if (VM.runningVM) {
        Object result = Statics.getSlotContentsAsObject(Offset.fromIntSignExtend((Integer)unicodeStringOrJTOCoffset));
        return (String)result;
      } else {
        try {
          return UTF8Convert.fromUTF8(val).intern();
        } catch (UTFDataFormatException e) {
          throw new Error("Error in UTF data encoding: ", e);
        }
      }
    }
  }

  /**
   * Offset of an atom's string in the JTOC, for string literals
   * @return Offset of string literal in JTOC
   * @throws java.io.UTFDataFormatException
   */
  public synchronized int getStringLiteralOffset() throws java.io.UTFDataFormatException {
    if (unicodeStringOrJTOCoffset == null) {
      String s = UTF8Convert.fromUTF8(val);
      if (VM.runningVM) {
        s = InternedStrings.internUnfoundString(s);
      } else {
        s = s.intern();
      }
      int offset = Statics.findOrCreateObjectLiteral(s);
      unicodeStringOrJTOCoffset = offset;
      return offset;
    } else if (unicodeStringOrJTOCoffset instanceof String) {
      int offset = Statics.findOrCreateObjectLiteral(unicodeStringOrJTOCoffset);
      unicodeStringOrJTOCoffset = offset;
      return offset;
    } else {
      return (Integer)unicodeStringOrJTOCoffset;
    }
  }

  /**
   * 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;"
   */
  @Pure
  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, null);
  }

  /**
   * Return class descriptor corresponding to "this" class name.
   * this: class name       - something like "java.lang.Object"
   * @return class descriptor - something like "Ljava/lang/Object;"
   */
  @Pure
  public 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) {
      byte b = val[i];
      if (b == '.') b = '/';
      sig[i + 1] = b;
    }
    sig[sig.length - 1] = (byte) ';';
    return findOrCreate(sig, true, null);
  }

  /**
   * Return class name corresponding to "this" class descriptor.
   * this: class descriptor - something like "Ljava/lang/String;"
   * @return class name - something like "java.lang.String"
   */
  @Pure
  public String classNameFromDescriptor() {
    if (VM.VerifyAssertions) {
      VM._assert(val.length > 0);
      VM._assert(val[0] == 'L' && val[val.length - 1] == ';');
    }
    if (unicodeStringOrJTOCoffset == null) {
      return StringUtilities.asciiBytesToString(val, 1, val.length - 2).replace('/', '.');
    } else {
      return toUnicodeStringInternal().substring(1, val.length-1).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"
   */
  @Pure
  public String classFileNameFromDescriptor() {
    if (VM.VerifyAssertions) {
      VM._assert(val.length > 0);
      VM._assert(val[0] == 'L' && val[val.length - 1] == ';');
    }
    if (unicodeStringOrJTOCoffset == null) {
      return StringUtilities.asciiBytesToString(val, 1, val.length - 2) + ".class";
    } else {
      return toUnicodeStringInternal().substring(1, val.length-1) + ".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
  @Pure
  public boolean isReservedMemberName() {
    if (VM.VerifyAssertions) VM._assert(val.length > 0);
    return val[0] == '<';
  }

  /**
   * Is "this" atom a class descriptor?
   */
  @Uninterruptible
  @Pure
  public boolean isClassDescriptor() {
    if (VM.VerifyAssertions) VM._assert(val.length > 0);
    return val[0] == 'L';
  }

  /**
   * Is "this" atom an array descriptor?
   */
  @Uninterruptible
  @Pure
  public boolean isArrayDescriptor() {
    if (VM.VerifyAssertions) VM._assert(val.length > 0);
    return val[0] == '[';
  }

  /**
   * Is "this" atom a method descriptor?
   */
  @Uninterruptible
  @Pure
  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
   */
  @Pure
  public 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 TypeReference.Void;
      case BooleanTypeCode:
        return TypeReference.Boolean;
      case ByteTypeCode:
        return TypeReference.Byte;
      case ShortTypeCode:
        return TypeReference.Short;
      case IntTypeCode:
        return TypeReference.Int;
      case LongTypeCode:
        return TypeReference.Long;
      case FloatTypeCode:
        return TypeReference.Float;
      case DoubleTypeCode:
        return TypeReference.Double;
      case CharTypeCode:
        return TypeReference.Char;
      case ClassTypeCode:   // fall through
      case ArrayTypeCode:
        return TypeReference.findOrCreate(cl, findOrCreate(val, i, val.length - i, toUnicodeStringInternal()));
      default:
        if (VM.VerifyAssertions) {
          VM._assert(false,
                     "Need a valid method descriptor; got \"" +
                     this +
                     "\"; can't parse the character '" +
                     ((char)val[i]) +
                     "'");
        }
        return null;            // NOTREACHED
    }
  }

  /**
   * Parse "this" method descriptor to obtain descriptions of method's
   * parameters.
   * this: method descriptor     - something like "(III)V"
   * @return parameter descriptions
   */
  @Pure
  public TypeReference[] parseForParameterTypes(ClassLoader cl) {
    if (VM.VerifyAssertions) {
      VM._assert(val.length > 0);
      VM._assert(val[0] == '(', "Method descriptors start with `(`");
    }
    TypeReferenceVector sigs = new 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(TypeReference.Void);
          continue;
        case BooleanTypeCode:
          sigs.addElement(TypeReference.Boolean);
          continue;
        case ByteTypeCode:
          sigs.addElement(TypeReference.Byte);
          continue;
        case ShortTypeCode:
          sigs.addElement(TypeReference.Short);
          continue;
        case IntTypeCode:
          sigs.addElement(TypeReference.Int);
          continue;
        case LongTypeCode:
          sigs.addElement(TypeReference.Long);
          continue;
        case FloatTypeCode:
          sigs.addElement(TypeReference.Float);
          continue;
        case DoubleTypeCode:
          sigs.addElement(TypeReference.Double);
          continue;
        case CharTypeCode:
          sigs.addElement(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(TypeReference
              .findOrCreate(cl, findOrCreate(val, off, i - off, toUnicodeStringInternal())));
          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(TypeReference.findOrCreate(cl, findOrCreate(val, off, i - off, toUnicodeStringInternal())));
          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 '" +
                       ((char)val[i]) +
                       "'");
          }
      }
    }
  }

  /**
   * Parse "this" method descriptor to obtain descriptions of method's
   * parameters as classes.
   * this: method descriptor     - something like "(III)V"
   * @return parameter classes
   */
  @Pure
  public Class<?>[] parseForParameterClasses(ClassLoader cl) {
    TypeReference[] typeRefs = this.parseForParameterTypes(cl);
    Class<?>[] classes = new Class<?>[typeRefs.length];
    for (int i=0; i < typeRefs.length; i++) {
      TypeReference t = typeRefs[i];
      classes[i] = t.resolve().getClassForType();
    }
    return classes;
  }

  /**
   * Return the underlying set of bytes for the 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>
   */
  @Pure
  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"
   */
  @Pure
  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 (RVMArray.getLogElementSize()) calls it.
   */
  @Uninterruptible
  @Pure
  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
   */
  @Pure
  public 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, toUnicodeStringInternal());
  }

  /**
   * 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"
   */
  @Pure
  public 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, toUnicodeStringInternal());
  }

  /**
   * 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)
   */
  @Pure
  // DIFC: made uninterruptible
  @Uninterruptible
  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.
   */
  @Pure
  // DIFC: made uninterruptible
  @Uninterruptible
  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.
   */
  @Pure
  public 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 Atom.findOrCreateUtf8Atom(annotationClassName_tmp);
  }

  /**
   * Create a class name from a type name. For example Lfoo.bar$$;
   * becomes the string foo.bar
   */
  @Pure
  public String annotationClassToAnnotationInterface() {
    if (VM.VerifyAssertions) {
      VM._assert(val.length > 0);
      VM._assert(val[0] == 'L' && val[val.length - 1] == ';', toString());
    }
    return StringUtilities.asciiBytesToString(val, 1, val.length - 4).replace('/', '.');
  }

  /**
   * Is this an annotation class name of the form Lfoo.bar$$;
   */
  @Pure
  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 Atom(byte[] val, int id, String str) {
    this.id = id;
    this.unicodeStringOrJTOCoffset = str;
    if ((val == null) && (id != -1)) {
      this.val = UTF8Convert.toUTF8(str);
    } else {
      this.val = val;
    }
  }

  /*
   * Hash table utilities
   */
  /**
   * Return the hashCode of an atom, this equals the unicode string encoding of
   * the atom
   */
  public int hashCode() {
    try {
      if (unicodeStringOrJTOCoffset != null) {
        return toUnicodeStringInternal().hashCode();
      } else {
        return UTF8Convert.computeStringHashCode(val);
      }
    } catch (UTFDataFormatException e) {
      return 0;
    }
  }

  /**
   * Outside of this class atoms are canonical and should be compared using ==.
   * This method is used to maintain atoms in internal hash tables and shouldn't
   * be used externally.
   */
  @Pure
  public boolean equals(Object other) {
    // quick test as atoms are generally canonical
    if (this == other) {
      return true;
    } else {
      if (other instanceof Atom) {
        Atom that = (Atom)other;
        // if the atoms are well formed then their identifiers are unique
        if ((that.id != -1) && (this.id != -1)) {
          return that.id == this.id;
        }
        // one atom isn't well formed, can we do a string comparison to work out equality?
        if ((this.unicodeStringOrJTOCoffset != null) && (that.unicodeStringOrJTOCoffset != null)) {
          return toUnicodeStringInternal().equals(that.toUnicodeStringInternal());
        }
        try {
          // perform byte by byte comparison
          byte[] val1;
          if (that.val != null) {
            val1 = that.val;
          } else {
            val1 = UTF8Convert.toUTF8(that.toUnicodeString());
          }
          byte[] val2;
          if (this.val != null) {
            val2 = this.val;
          } else {
            val2 = UTF8Convert.toUTF8(toUnicodeString());
          }
          if (val1.length == val2.length) {
            for (int i = 0; i < val1.length; i++) {
              if (val1[i] != val2[i]) return false;
            }
            return true;
          }
        } catch (UTFDataFormatException e) {
          throw new Error("Error in UTF data encoding: ",e);
        }
      }
      return false;
    }
  }


  /**
   * Inner class responsible for string interning. This class' initializer is
   * run during booting.
   */
  private static class InternedStrings {
    /**
     * Look up for interned strings.
     */
    private static final WeakHashMap<String,WeakReference<String>> internedStrings =
      new WeakHashMap<String,WeakReference<String>>();

    /**
     * Find an interned string but don't create it if not found
     * @param str string to lookup
     * @return the interned string or null if it isn't interned
     */
    static synchronized String findInternedString(String str) {
      WeakReference<String> ref;
      ref = internedStrings.get(str);
      if (ref != null) {
        String s = ref.get();
        if (s != null) {
          return s;
        }
      }
      return null;
    }

    /**
     * Find a string literal from an atom
     * @param str string to find
     * @return the string literal or null
     */
    static String findAtomString(String str) {
      Atom atom = findUnicodeAtom(str);
      if (atom != null) {
        try {
          return atom.toUnicodeString();
        } catch (UTFDataFormatException e) {
          throw new Error("Error in UTF data encoding: ", e);
        }
      }
      return null;
    }

    /**
     * Intern a string that is not an atom or already interned string
     * @param str string to intern
     * @return interned string
     */
    static synchronized String internUnfoundString(String str) {
      // double check string isn't found as we're holding the lock on the class
      String s = findInternedString(str);
      if (s != null) return s;
      // If we get to here, then there is no interned version of the String.
      // So we make one.
      WeakReference<String> ref = new WeakReference<String>(str);
      internedStrings.put(str, ref);
      return str;
    }
  }

  /**
   * External string intern method called from String.intern. This method should
   * return a canonical string encoding for the given string and this string
   * should also be canonical with string literals.
   * @param str string to intern
   * @return interned version of string
   */
  public static String internString(String str) {
    // Has the string already been interned
    String s = InternedStrings.findInternedString(str);
    if (s != null) return s;

    // Check to see if this is a StringLiteral:
    s = InternedStrings.findAtomString(str);
    if (s != null) return s;

    // Intern this string
    return InternedStrings.internUnfoundString(str);
  }
}
