/*
 * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.tools.jdi;

import com.sun.jdi.*;

import java.util.*;
import java.lang.ref.SoftReference;

public abstract class ReferenceTypeImpl extends TypeImpl
implements ReferenceType {
    protected long ref;
    private String signature = null;
    private String genericSignature = null;
    private boolean genericSignatureGotten = false;
    private String baseSourceName = null;
    private String baseSourceDir = null;
    private String baseSourcePath = null;
    protected int modifiers = -1;
    private SoftReference<List<Field>> fieldsRef = null;
    private SoftReference<List<Method>> methodsRef = null;
    private SoftReference<SDE> sdeRef = null;

    private boolean isClassLoaderCached = false;
    private ClassLoaderReference classLoader = null;
    private ClassObjectReference classObject = null;

    private int status = 0;
    private boolean isPrepared = false;


    private boolean versionNumberGotten = false;
    private int majorVersion;
    private int minorVersion;

    private boolean constantPoolInfoGotten = false;
    private int constanPoolCount;
    private byte[] constantPoolBytes;
    private SoftReference<byte[]> constantPoolBytesRef = null;

    /* to mark a SourceFile request that returned a genuine JDWP.Error.ABSENT_INFORMATION */
    private static final String ABSENT_BASE_SOURCE_NAME = "**ABSENT_BASE_SOURCE_NAME**";

    /* to mark when no info available */
    static final SDE NO_SDE_INFO_MARK = new SDE();

    // bits set when initialization was attempted (succeeded or failed)
    private static final int INITIALIZED_OR_FAILED =
        JDWP.ClassStatus.INITIALIZED | JDWP.ClassStatus.ERROR;


    protected ReferenceTypeImpl(VirtualMachine aVm, long aRef) {
        super(aVm);
        ref = aRef;
        genericSignatureGotten = false;
    }

    void noticeRedefineClass() {
        //Invalidate information previously fetched and cached.
        //These will be refreshed later on demand.
        baseSourceName = null;
        baseSourcePath = null;
        modifiers = -1;
        fieldsRef = null;
        methodsRef = null;
        sdeRef = null;
        versionNumberGotten = false;
        constantPoolInfoGotten = false;
    }

    Method getMethodMirror(long ref) {
        if (ref == 0) {
            // obsolete method
            return new ObsoleteMethodImpl(vm, this);
        }
        // Fetch all methods for the class, check performance impact
        // Needs no synchronization now, since methods() returns
        // unmodifiable local data
        Iterator it = methods().iterator();
        while (it.hasNext()) {
            MethodImpl method = (MethodImpl)it.next();
            if (method.ref() == ref) {
                return method;
            }
        }
        throw new IllegalArgumentException("Invalid method id: " + ref);
    }

    Field getFieldMirror(long ref) {
        // Fetch all fields for the class, check performance impact
        // Needs no synchronization now, since fields() returns
        // unmodifiable local data
        Iterator it = fields().iterator();
        while (it.hasNext()) {
            FieldImpl field = (FieldImpl)it.next();
            if (field.ref() == ref) {
                return field;
            }
        }
        throw new IllegalArgumentException("Invalid field id: " + ref);
    }

    public boolean equals(Object obj) {
        if ((obj != null) && (obj instanceof ReferenceTypeImpl)) {
            ReferenceTypeImpl other = (ReferenceTypeImpl)obj;
            return (ref() == other.ref()) &&
                (vm.equals(other.virtualMachine()));
        } else {
            return false;
        }
    }

    public int hashCode() {
        return(int)ref();
    }

    public int compareTo(ReferenceType object) {
        /*
         * Note that it is critical that compareTo() == 0
         * implies that equals() == true. Otherwise, TreeSet
         * will collapse classes.
         *
         * (Classes of the same name loaded by different class loaders
         * or in different VMs must not return 0).
         */
        ReferenceTypeImpl other = (ReferenceTypeImpl)object;
        int comp = name().compareTo(other.name());
        if (comp == 0) {
            long rf1 = ref();
            long rf2 = other.ref();
            // optimize for typical case: refs equal and VMs equal
            if (rf1 == rf2) {
                // sequenceNumbers are always positive
                comp = vm.sequenceNumber -
                 ((VirtualMachineImpl)(other.virtualMachine())).sequenceNumber;
            } else {
                comp = (rf1 < rf2)? -1 : 1;
            }
        }
        return comp;
    }

    public String signature() {
        if (signature == null) {
            // Does not need synchronization, since worst-case
            // static info is fetched twice
            if (vm.canGet1_5LanguageFeatures()) {
                /*
                 * we might as well get both the signature and the
                 * generic signature.
                 */
                genericSignature();
            } else {
                try {
                    signature = JDWP.ReferenceType.Signature.
                        process(vm, this).signature;
                } catch (JDWPException exc) {
                    throw exc.toJDIException();
                }
            }
        }
        return signature;
    }

    public String genericSignature() {
        // This gets both the signature and the generic signature
        if (vm.canGet1_5LanguageFeatures() && !genericSignatureGotten) {
            // Does not need synchronization, since worst-case
            // static info is fetched twice
            JDWP.ReferenceType.SignatureWithGeneric result;
            try {
                result = JDWP.ReferenceType.SignatureWithGeneric.
                    process(vm, this);
            } catch (JDWPException exc) {
                throw exc.toJDIException();
            }
            signature = result.signature;
            setGenericSignature(result.genericSignature);
        }
        return genericSignature;
    }

    public ClassLoaderReference classLoader() {
        if (!isClassLoaderCached) {
            // Does not need synchronization, since worst-case
            // static info is fetched twice
            try {
                classLoader = (ClassLoaderReference)
                    JDWP.ReferenceType.ClassLoader.
                    process(vm, this).classLoader;
                isClassLoaderCached = true;
            } catch (JDWPException exc) {
                throw exc.toJDIException();
            }
        }
        return classLoader;
    }

    public boolean isPublic() {
        if (modifiers == -1)
            getModifiers();

        return((modifiers & VMModifiers.PUBLIC) > 0);
    }

    public boolean isProtected() {
        if (modifiers == -1)
            getModifiers();

        return((modifiers & VMModifiers.PROTECTED) > 0);
    }

    public boolean isPrivate() {
        if (modifiers == -1)
            getModifiers();

        return((modifiers & VMModifiers.PRIVATE) > 0);
    }

    public boolean isPackagePrivate() {
        return !isPublic() && !isPrivate() && !isProtected();
    }

    public boolean isAbstract() {
        if (modifiers == -1)
            getModifiers();

        return((modifiers & VMModifiers.ABSTRACT) > 0);
    }

    public boolean isFinal() {
        if (modifiers == -1)
            getModifiers();

        return((modifiers & VMModifiers.FINAL) > 0);
    }

    public boolean isStatic() {
        if (modifiers == -1)
            getModifiers();

        return((modifiers & VMModifiers.STATIC) > 0);
    }

    public boolean isPrepared() {
        // This ref type may have been prepared before we were getting
        // events, so get it once.  After that,
        // this status flag is updated through the ClassPrepareEvent,
        // there is no need for the expense of a JDWP query.
        if (status == 0) {
            updateStatus();
        }
        return isPrepared;
    }

    public boolean isVerified() {
        // Once true, it never resets, so we don't need to update
        if ((status & JDWP.ClassStatus.VERIFIED) == 0) {
            updateStatus();
        }
        return (status & JDWP.ClassStatus.VERIFIED) != 0;
    }

    public boolean isInitialized() {
        // Once initialization succeeds or fails, it never resets,
        // so we don't need to update
        if ((status & INITIALIZED_OR_FAILED) == 0) {
            updateStatus();
        }
        return (status & JDWP.ClassStatus.INITIALIZED) != 0;
    }

    public boolean failedToInitialize() {
        // Once initialization succeeds or fails, it never resets,
        // so we don't need to update
        if ((status & INITIALIZED_OR_FAILED) == 0) {
            updateStatus();
        }
        return (status & JDWP.ClassStatus.ERROR) != 0;
    }

    public List<Field> fields() {
        List<Field> fields = (fieldsRef == null) ? null : fieldsRef.get();
        if (fields == null) {
            if (vm.canGet1_5LanguageFeatures()) {
                JDWP.ReferenceType.FieldsWithGeneric.FieldInfo[] jdwpFields;
                try {
                    jdwpFields = JDWP.ReferenceType.FieldsWithGeneric.process(vm, this).declared;
                } catch (JDWPException exc) {
                    throw exc.toJDIException();
                }
                fields = new ArrayList<Field>(jdwpFields.length);
                for (int i=0; i<jdwpFields.length; i++) {
                    JDWP.ReferenceType.FieldsWithGeneric.FieldInfo fi
                        = jdwpFields[i];

                    Field field = new FieldImpl(vm, this, fi.fieldID,
                                                fi.name, fi.signature,
                                                fi.genericSignature,
                                                fi.modBits);
                    fields.add(field);
                }
            } else {
                JDWP.ReferenceType.Fields.FieldInfo[] jdwpFields;
                try {
                    jdwpFields = JDWP.ReferenceType.Fields.
                        process(vm, this).declared;
                } catch (JDWPException exc) {
                    throw exc.toJDIException();
                }
                fields = new ArrayList<Field>(jdwpFields.length);
                for (int i=0; i<jdwpFields.length; i++) {
                    JDWP.ReferenceType.Fields.FieldInfo fi = jdwpFields[i];

                    Field field = new FieldImpl(vm, this, fi.fieldID,
                                            fi.name, fi.signature,
                                            null,
                                            fi.modBits);
                    fields.add(field);
                }
            }

            fields = Collections.unmodifiableList(fields);
            fieldsRef = new SoftReference<List<Field>>(fields);
        }
        return fields;
    }

    abstract List<? extends ReferenceType> inheritedTypes();

    void addVisibleFields(List<Field> visibleList, Map<String, Field> visibleTable, List<String> ambiguousNames) {
        for (Field field : visibleFields()) {
            String name = field.name();
            if (!ambiguousNames.contains(name)) {
                Field duplicate = visibleTable.get(name);
                if (duplicate == null) {
                    visibleList.add(field);
                    visibleTable.put(name, field);
                } else if (!field.equals(duplicate)) {
                    ambiguousNames.add(name);
                    visibleTable.remove(name);
                    visibleList.remove(duplicate);
                } else {
                    // identical field from two branches; do nothing
                }
            }
        }
    }

    public List<Field> visibleFields() {
        /*
         * Maintain two different collections of visible fields. The
         * list maintains a reasonable order for return. The
         * hash map provides an efficient way to lookup visible fields
         * by name, important for finding hidden or ambiguous fields.
         */
        List<Field> visibleList = new ArrayList<Field>();
        Map<String, Field>  visibleTable = new HashMap<String, Field>();

        /* Track fields removed from above collection due to ambiguity */
        List<String> ambiguousNames = new ArrayList<String>();

        /* Add inherited, visible fields */
        List<? extends ReferenceType> types = inheritedTypes();
        Iterator iter = types.iterator();
        while (iter.hasNext()) {
            /*
             * TO DO: Be defensive and check for cyclic interface inheritance
             */
            ReferenceTypeImpl type = (ReferenceTypeImpl)iter.next();
            type.addVisibleFields(visibleList, visibleTable, ambiguousNames);
        }

        /*
         * Insert fields from this type, removing any inherited fields they
         * hide.
         */
        List<Field> retList = new ArrayList<Field>(fields());
        for (Field field : retList) {
            Field hidden = visibleTable.get(field.name());
            if (hidden != null) {
                visibleList.remove(hidden);
            }
        }
        retList.addAll(visibleList);
        return retList;
    }

    void addAllFields(List<Field> fieldList, Set<ReferenceType> typeSet) {
        /* Continue the recursion only if this type is new */
        if (!typeSet.contains(this)) {
            typeSet.add((ReferenceType)this);

            /* Add local fields */
            fieldList.addAll(fields());

            /* Add inherited fields */
            List<? extends ReferenceType> types = inheritedTypes();
            Iterator iter = types.iterator();
            while (iter.hasNext()) {
                ReferenceTypeImpl type = (ReferenceTypeImpl)iter.next();
                type.addAllFields(fieldList, typeSet);
            }
        }
    }
    public List<Field> allFields() {
        List<Field> fieldList = new ArrayList<Field>();
        Set<ReferenceType> typeSet = new HashSet<ReferenceType>();
        addAllFields(fieldList, typeSet);
        return fieldList;
    }

    public Field fieldByName(String fieldName) {
        java.util.List searchList;
        Field f;

        searchList = visibleFields();

        for (int i=0; i<searchList.size(); i++) {
            f = (Field)searchList.get(i);

            if (f.name().equals(fieldName)) {
                return f;
            }
        }
        //throw new NoSuchFieldException("Field '" + fieldName + "' not found in " + name());
        return null;
    }

    public List<Method> methods() {
        List<Method> methods = (methodsRef == null) ? null : methodsRef.get();
        if (methods == null) {
            if (!vm.canGet1_5LanguageFeatures()) {
                methods = methods1_4();
            } else {
                JDWP.ReferenceType.MethodsWithGeneric.MethodInfo[] declared;
                try {
                    declared = JDWP.ReferenceType.MethodsWithGeneric.
                        process(vm, this).declared;
                } catch (JDWPException exc) {
                    throw exc.toJDIException();
                }
                methods = new ArrayList<Method>(declared.length);
                for (int i=0; i<declared.length; i++) {
                    JDWP.ReferenceType.MethodsWithGeneric.MethodInfo
                        mi = declared[i];

                    Method method = MethodImpl.createMethodImpl(vm, this,
                                                         mi.methodID,
                                                         mi.name, mi.signature,
                                                         mi.genericSignature,
                                                         mi.modBits);
                    methods.add(method);
                }
            }
            methods = Collections.unmodifiableList(methods);
            methodsRef = new SoftReference<List<Method>>(methods);
        }
        return methods;
    }

    private List<Method> methods1_4() {
        List<Method> methods;
        JDWP.ReferenceType.Methods.MethodInfo[] declared;
        try {
            declared = JDWP.ReferenceType.Methods.
                process(vm, this).declared;
        } catch (JDWPException exc) {
            throw exc.toJDIException();
        }
        methods = new ArrayList<Method>(declared.length);
        for (int i=0; i<declared.length; i++) {
            JDWP.ReferenceType.Methods.MethodInfo mi = declared[i];

            Method method = MethodImpl.createMethodImpl(vm, this,
                                                        mi.methodID,
                                                        mi.name, mi.signature,
                                                        null,
                                                        mi.modBits);
            methods.add(method);
        }
        return methods;
    }

    /*
     * Utility method used by subclasses to build lists of visible
     * methods.
     */
    void addToMethodMap(Map<String, Method> methodMap, List<Method> methodList) {
        for (Method method : methodList)
            methodMap.put(method.name().concat(method.signature()), method);
        }

    abstract void addVisibleMethods(Map<String, Method> methodMap);

    public List<Method> visibleMethods() {
        /*
         * Build a collection of all visible methods. The hash
         * map allows us to do this efficiently by keying on the
         * concatenation of name and signature.
         */
        Map<String, Method> map = new HashMap<String, Method>();
        addVisibleMethods(map);

        /*
         * ... but the hash map destroys order. Methods should be
         * returned in a sensible order, as they are in allMethods().
         * So, start over with allMethods() and use the hash map
         * to filter that ordered collection.
         */
        List<Method> list = allMethods();
        list.retainAll(map.values());
        return list;
    }

    abstract public List<Method> allMethods();

    public List<Method> methodsByName(String name) {
        List<Method> methods = visibleMethods();
        ArrayList<Method> retList = new ArrayList<Method>(methods.size());
        for (Method candidate : methods) {
            if (candidate.name().equals(name)) {
                retList.add(candidate);
            }
        }
        retList.trimToSize();
        return retList;
    }

    public List<Method> methodsByName(String name, String signature) {
        List<Method> methods = visibleMethods();
        ArrayList<Method> retList = new ArrayList<Method>(methods.size());
        for (Method candidate : methods) {
            if (candidate.name().equals(name) &&
                candidate.signature().equals(signature)) {
                retList.add(candidate);
            }
        }
        retList.trimToSize();
        return retList;
    }

    List<InterfaceType> getInterfaces() {
        InterfaceTypeImpl[] intfs;
        try {
            intfs = JDWP.ReferenceType.Interfaces.
                                         process(vm, this).interfaces;
        } catch (JDWPException exc) {
            throw exc.toJDIException();
        }
        return Arrays.asList((InterfaceType[])intfs);
    }

    public List<ReferenceType> nestedTypes() {
        List all = vm.allClasses();
        List<ReferenceType> nested = new ArrayList<ReferenceType>();
        String outername = name();
        int outerlen = outername.length();
        Iterator iter = all.iterator();
        while (iter.hasNext()) {
            ReferenceType refType = (ReferenceType)iter.next();
            String name = refType.name();
            int len = name.length();
            /* The separator is historically '$' but could also be '#' */
            if ( len > outerlen && name.startsWith(outername) ) {
                char c = name.charAt(outerlen);
                if ( c =='$' || c== '#' ) {
                    nested.add(refType);
                }
            }
        }
        return nested;
    }

    public Value getValue(Field sig) {
        List<Field> list = new ArrayList<Field>(1);
        list.add(sig);
        Map map = getValues(list);
        return(Value)map.get(sig);
    }


    void validateFieldAccess(Field field) {
        /*
         * Field must be in this object's class, a superclass, or
         * implemented interface
         */
        ReferenceTypeImpl declType = (ReferenceTypeImpl)field.declaringType();
        if (!declType.isAssignableFrom(this)) {
            throw new IllegalArgumentException("Invalid field");
        }
    }

    void validateFieldSet(Field field) {
        validateFieldAccess(field);
        if (field.isFinal()) {
            throw new IllegalArgumentException("Cannot set value of final field");
        }
    }

    /**
     * Returns a map of field values
     */
    public Map<Field,Value> getValues(List<? extends Field> theFields) {
        validateMirrors(theFields);

        int size = theFields.size();
        JDWP.ReferenceType.GetValues.Field[] queryFields =
                         new JDWP.ReferenceType.GetValues.Field[size];

        for (int i=0; i<size; i++) {
            FieldImpl field = (FieldImpl)theFields.get(i);

            validateFieldAccess(field);

            // Do more validation specific to ReferenceType field getting
            if (!field.isStatic()) {
                throw new IllegalArgumentException(
                     "Attempt to use non-static field with ReferenceType");
            }
            queryFields[i] = new JDWP.ReferenceType.GetValues.Field(
                                         field.ref());
        }

        Map<Field, Value> map = new HashMap<Field, Value>(size);

        ValueImpl[] values;
        try {
            values = JDWP.ReferenceType.GetValues.
                                     process(vm, this, queryFields).values;
        } catch (JDWPException exc) {
            throw exc.toJDIException();
        }

        if (size != values.length) {
            throw new InternalException(
                         "Wrong number of values returned from target VM");
        }
        for (int i=0; i<size; i++) {
            FieldImpl field = (FieldImpl)theFields.get(i);
            map.put(field, values[i]);
        }

        return map;
    }

    public ClassObjectReference classObject() {
        if (classObject == null) {
            // Are classObjects unique for an Object, or
            // created each time? Is this spec'ed?
            synchronized(this) {
                if (classObject == null) {
                    try {
                        classObject = JDWP.ReferenceType.ClassObject.
                            process(vm, this).classObject;
                    } catch (JDWPException exc) {
                        throw exc.toJDIException();
                    }
                }
            }
        }
        return classObject;
    }

    SDE.Stratum stratum(String stratumID) {
        SDE sde = sourceDebugExtensionInfo();
        if (!sde.isValid()) {
            sde = NO_SDE_INFO_MARK;
        }
        return sde.stratum(stratumID);
    }

    public String sourceName() throws AbsentInformationException {
        return sourceNames(vm.getDefaultStratum()).get(0);
    }

    public List<String> sourceNames(String stratumID)
                                throws AbsentInformationException {
        SDE.Stratum stratum = stratum(stratumID);
        if (stratum.isJava()) {
            List<String> result = new ArrayList<String>(1);
            result.add(baseSourceName());
            return result;
        }
        return stratum.sourceNames(this);
    }

    public List<String> sourcePaths(String stratumID)
                                throws AbsentInformationException {
        SDE.Stratum stratum = stratum(stratumID);
        if (stratum.isJava()) {
            List<String> result = new ArrayList<String>(1);
            result.add(baseSourceDir() + baseSourceName());
            return result;
        }
        return stratum.sourcePaths(this);
    }

    String baseSourceName() throws AbsentInformationException {
        String bsn = baseSourceName;
        if (bsn == null) {
            // Does not need synchronization, since worst-case
            // static info is fetched twice
            try {
                bsn = JDWP.ReferenceType.SourceFile.
                    process(vm, this).sourceFile;
            } catch (JDWPException exc) {
                if (exc.errorCode() == JDWP.Error.ABSENT_INFORMATION) {
                    bsn = ABSENT_BASE_SOURCE_NAME;
                } else {
                    throw exc.toJDIException();
                }
            }
            baseSourceName = bsn;
        }
        if (bsn == ABSENT_BASE_SOURCE_NAME) {
            throw new AbsentInformationException();
        }
        return bsn;
    }

    String baseSourcePath() throws AbsentInformationException {
        String bsp = baseSourcePath;
        if (bsp == null) {
            bsp = baseSourceDir() + baseSourceName();
            baseSourcePath = bsp;
        }
        return bsp;
    }

    String baseSourceDir() {
        if (baseSourceDir == null) {
            String typeName = name();
            StringBuffer sb = new StringBuffer(typeName.length() + 10);
            int index = 0;
            int nextIndex;

            while ((nextIndex = typeName.indexOf('.', index)) > 0) {
                sb.append(typeName.substring(index, nextIndex));
                sb.append(java.io.File.separatorChar);
                index = nextIndex + 1;
            }
            baseSourceDir = sb.toString();
        }
        return baseSourceDir;
    }

    public String sourceDebugExtension()
                           throws AbsentInformationException {
        if (!vm.canGetSourceDebugExtension()) {
            throw new UnsupportedOperationException();
        }
        SDE sde = sourceDebugExtensionInfo();
        if (sde == NO_SDE_INFO_MARK) {
            throw new AbsentInformationException();
        }
        return sde.sourceDebugExtension;
    }

    private SDE sourceDebugExtensionInfo() {
        if (!vm.canGetSourceDebugExtension()) {
            return NO_SDE_INFO_MARK;
        }
        SDE sde = (sdeRef == null) ?  null : sdeRef.get();
        if (sde == null) {
            String extension = null;
            try {
                extension = JDWP.ReferenceType.SourceDebugExtension.
                    process(vm, this).extension;
            } catch (JDWPException exc) {
                if (exc.errorCode() != JDWP.Error.ABSENT_INFORMATION) {
                    sdeRef = new SoftReference<SDE>(NO_SDE_INFO_MARK);
                    throw exc.toJDIException();
                }
            }
            if (extension == null) {
                sde = NO_SDE_INFO_MARK;
            } else {
                sde = new SDE(extension);
            }
            sdeRef = new SoftReference<SDE>(sde);
        }
        return sde;
    }

    public List<String> availableStrata() {
        SDE sde = sourceDebugExtensionInfo();
        if (sde.isValid()) {
            return sde.availableStrata();
        } else {
            List<String> strata = new ArrayList<String>();
            strata.add(SDE.BASE_STRATUM_NAME);
            return strata;
        }
    }

    /**
     * Always returns non-null stratumID
     */
    public String defaultStratum() {
        SDE sdei = sourceDebugExtensionInfo();
        if (sdei.isValid()) {
            return sdei.defaultStratumId;
        } else {
            return SDE.BASE_STRATUM_NAME;
        }
    }

    public int modifiers() {
        if (modifiers == -1)
            getModifiers();

        return modifiers;
    }

    public List<Location> allLineLocations()
                            throws AbsentInformationException {
        return allLineLocations(vm.getDefaultStratum(), null);
    }

    public List<Location> allLineLocations(String stratumID, String sourceName)
                            throws AbsentInformationException {
        boolean someAbsent = false; // A method that should have info, didn't
        SDE.Stratum stratum = stratum(stratumID);
        List<Location> list = new ArrayList<Location>();  // location list

        for (Iterator iter = methods().iterator(); iter.hasNext(); ) {
            MethodImpl method = (MethodImpl)iter.next();
            try {
                list.addAll(
                   method.allLineLocations(stratum, sourceName));
            } catch(AbsentInformationException exc) {
                someAbsent = true;
            }
        }

        // If we retrieved no line info, and at least one of the methods
        // should have had some (as determined by an
        // AbsentInformationException being thrown) then we rethrow
        // the AbsentInformationException.
        if (someAbsent && list.size() == 0) {
            throw new AbsentInformationException();
        }
        return list;
    }

    public List<Location> locationsOfLine(int lineNumber)
                           throws AbsentInformationException {
        return locationsOfLine(vm.getDefaultStratum(),
                               null,
                               lineNumber);
    }

    public List<Location> locationsOfLine(String stratumID,
                                String sourceName,
                                int lineNumber)
                           throws AbsentInformationException {
        // A method that should have info, didn't
        boolean someAbsent = false;
        // A method that should have info, did
        boolean somePresent = false;
        List<Method> methods = methods();
        SDE.Stratum stratum = stratum(stratumID);

        List<Location> list = new ArrayList<Location>();

        Iterator iter = methods.iterator();
        while(iter.hasNext()) {
            MethodImpl method = (MethodImpl)iter.next();
            // eliminate native and abstract to eliminate
            // false positives
            if (!method.isAbstract() &&
                !method.isNative()) {
                try {
                    list.addAll(
                       method.locationsOfLine(stratum,
                                              sourceName,
                                              lineNumber));
                    somePresent = true;
                } catch(AbsentInformationException exc) {
                    someAbsent = true;
                }
            }
        }
        if (someAbsent && !somePresent) {
            throw new AbsentInformationException();
        }
        return list;
    }

    public List<ObjectReference> instances(long maxInstances) {
        if (!vm.canGetInstanceInfo()) {
            throw new UnsupportedOperationException(
                "target does not support getting instances");
        }

        if (maxInstances < 0) {
            throw new IllegalArgumentException("maxInstances is less than zero: "
                                              + maxInstances);
        }
        int intMax = (maxInstances > Integer.MAX_VALUE)?
            Integer.MAX_VALUE: (int)maxInstances;
        // JDWP can't currently handle more than this (in mustang)

        try {
            return Arrays.asList(
                (ObjectReference[])JDWP.ReferenceType.Instances.
                        process(vm, this, intMax).instances);
        } catch (JDWPException exc) {
            throw exc.toJDIException();
        }
    }

    private void getClassFileVersion() {
        if (!vm.canGetClassFileVersion()) {
            throw new UnsupportedOperationException();
        }
        JDWP.ReferenceType.ClassFileVersion classFileVersion;
        if (versionNumberGotten) {
            return;
        } else {
            try {
                classFileVersion = JDWP.ReferenceType.ClassFileVersion.process(vm, this);
            } catch (JDWPException exc) {
                if (exc.errorCode() == JDWP.Error.ABSENT_INFORMATION) {
                    majorVersion = 0;
                    minorVersion = 0;
                    versionNumberGotten = true;
                    return;
                } else {
                    throw exc.toJDIException();
                }
            }
            majorVersion = classFileVersion.majorVersion;
            minorVersion = classFileVersion.minorVersion;
            versionNumberGotten = true;
        }
    }

    public int majorVersion() {
        try {
            getClassFileVersion();
        } catch (RuntimeException exc) {
            throw exc;
        }
        return majorVersion;
    }

    public int minorVersion() {
        try {
            getClassFileVersion();
        } catch (RuntimeException exc) {
            throw exc;
        }
        return minorVersion;
    }

    private void getConstantPoolInfo() {
        JDWP.ReferenceType.ConstantPool jdwpCPool;
        if (!vm.canGetConstantPool()) {
            throw new UnsupportedOperationException();
        }
        if (constantPoolInfoGotten) {
            return;
        } else {
            try {
                jdwpCPool = JDWP.ReferenceType.ConstantPool.process(vm, this);
            } catch (JDWPException exc) {
                if (exc.errorCode() == JDWP.Error.ABSENT_INFORMATION) {
                    constanPoolCount = 0;
                    constantPoolBytesRef = null;
                    constantPoolInfoGotten = true;
                    return;
                } else {
                    throw exc.toJDIException();
                }
            }
            byte[] cpbytes;
            constanPoolCount = jdwpCPool.count;
            cpbytes = jdwpCPool.bytes;
            constantPoolBytesRef = new SoftReference<byte[]>(cpbytes);
            constantPoolInfoGotten = true;
        }
    }

    public int constantPoolCount() {
        try {
            getConstantPoolInfo();
        } catch (RuntimeException exc) {
            throw exc;
        }
        return constanPoolCount;
    }

    public byte[] constantPool() {
        try {
            getConstantPoolInfo();
        } catch (RuntimeException exc) {
            throw exc;
        }
        if (constantPoolBytesRef != null) {
            byte[] cpbytes = constantPoolBytesRef.get();
            /*
             * Arrays are always modifiable, so it is a little unsafe
             * to return the cached bytecodes directly; instead, we
             * make a clone at the cost of using more memory.
             */
            return cpbytes.clone();
        } else {
            return null;
        }
    }

    // Does not need synchronization, since worst-case
    // static info is fetched twice
    void getModifiers() {
        if (modifiers != -1) {
            return;
        }
        try {
            modifiers = JDWP.ReferenceType.Modifiers.
                                  process(vm, this).modBits;
        } catch (JDWPException exc) {
            throw exc.toJDIException();
        }
    }

    void decodeStatus(int status) {
        this.status = status;
        if ((status & JDWP.ClassStatus.PREPARED) != 0) {
            isPrepared = true;
        }
    }

    void updateStatus() {
        try {
            decodeStatus(JDWP.ReferenceType.Status.process(vm, this).status);
        } catch (JDWPException exc) {
            throw exc.toJDIException();
        }
    }

    void markPrepared() {
        isPrepared = true;
    }

    long ref() {
        return ref;
    }

    int indexOf(Method method) {
        // Make sure they're all here - the obsolete method
        // won't be found and so will have index -1
        return methods().indexOf(method);
    }

    int indexOf(Field field) {
        // Make sure they're all here
        return fields().indexOf(field);
    }

    /*
     * Return true if an instance of this type
     * can be assigned to a variable of the given type
     */
    abstract boolean isAssignableTo(ReferenceType type);

    boolean isAssignableFrom(ReferenceType type) {
        return ((ReferenceTypeImpl)type).isAssignableTo(this);
    }

    boolean isAssignableFrom(ObjectReference object) {
        return object == null ||
               isAssignableFrom(object.referenceType());
    }

    void setStatus(int status) {
        decodeStatus(status);
    }

    void setSignature(String signature) {
        this.signature = signature;
    }

    void setGenericSignature(String signature) {
        if (signature != null && signature.length() == 0) {
            this.genericSignature = null;
        } else{
            this.genericSignature = signature;
        }
        this.genericSignatureGotten = true;
    }

    private static boolean isPrimitiveArray(String signature) {
        int i = signature.lastIndexOf('[');
        /*
         * TO DO: Centralize JNI signature knowledge.
         *
         * Ref:
         *  jdk1.4/doc/guide/jpda/jdi/com/sun/jdi/doc-files/signature.html
         */
        boolean isPA;
        if (i < 0) {
            isPA = false;
        } else {
            char c = signature.charAt(i + 1);
            isPA = (c != 'L');
        }
        return isPA;
    }

    Type findType(String signature) throws ClassNotLoadedException {
        Type type;
        if (signature.length() == 1) {
            /* OTI FIX: Must be a primitive type or the void type */
            char sig = signature.charAt(0);
            if (sig == 'V') {
                type = vm.theVoidType();
            } else {
                type = vm.primitiveTypeMirror((byte)sig);
            }
        } else {
            // Must be a reference type.
            ClassLoaderReferenceImpl loader =
                       (ClassLoaderReferenceImpl)classLoader();
            if ((loader == null) ||
                (isPrimitiveArray(signature)) //Work around 4450091
                ) {
                // Caller wants type of boot class field
                type = vm.findBootType(signature);
            } else {
                // Caller wants type of non-boot class field
                type = loader.findType(signature);
            }
        }
        return type;
    }

    String loaderString() {
        if (classLoader() != null) {
            return "loaded by " + classLoader().toString();
        } else {
            return "no class loader";
        }
    }

}
