1/*
2 * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.jdi;
27
28import com.sun.jdi.*;
29
30import java.util.*;
31import java.lang.ref.SoftReference;
32
33public abstract class ReferenceTypeImpl extends TypeImpl
34implements ReferenceType {
35    protected long ref;
36    private String signature = null;
37    private String genericSignature = null;
38    private boolean genericSignatureGotten = false;
39    private String baseSourceName = null;
40    private String baseSourceDir = null;
41    private String baseSourcePath = null;
42    protected int modifiers = -1;
43    private SoftReference<List<Field>> fieldsRef = null;
44    private SoftReference<List<Method>> methodsRef = null;
45    private SoftReference<SDE> sdeRef = null;
46
47    private boolean isClassLoaderCached = false;
48    private ClassLoaderReference classLoader = null;
49    private ClassObjectReference classObject = null;
50    private ModuleReference module = null;
51
52    private int status = 0;
53    private boolean isPrepared = false;
54
55
56    private boolean versionNumberGotten = false;
57    private int majorVersion;
58    private int minorVersion;
59
60    private boolean constantPoolInfoGotten = false;
61    private int constanPoolCount;
62    private byte[] constantPoolBytes;
63    private SoftReference<byte[]> constantPoolBytesRef = null;
64
65    /* to mark a SourceFile request that returned a genuine JDWP.Error.ABSENT_INFORMATION */
66    private static final String ABSENT_BASE_SOURCE_NAME = "**ABSENT_BASE_SOURCE_NAME**";
67
68    /* to mark when no info available */
69    static final SDE NO_SDE_INFO_MARK = new SDE();
70
71    // bits set when initialization was attempted (succeeded or failed)
72    private static final int INITIALIZED_OR_FAILED =
73        JDWP.ClassStatus.INITIALIZED | JDWP.ClassStatus.ERROR;
74
75
76    protected ReferenceTypeImpl(VirtualMachine aVm, long aRef) {
77        super(aVm);
78        ref = aRef;
79        genericSignatureGotten = false;
80    }
81
82    void noticeRedefineClass() {
83        //Invalidate information previously fetched and cached.
84        //These will be refreshed later on demand.
85        baseSourceName = null;
86        baseSourcePath = null;
87        modifiers = -1;
88        fieldsRef = null;
89        methodsRef = null;
90        sdeRef = null;
91        versionNumberGotten = false;
92        constantPoolInfoGotten = false;
93    }
94
95    Method getMethodMirror(long ref) {
96        if (ref == 0) {
97            // obsolete method
98            return new ObsoleteMethodImpl(vm, this);
99        }
100        // Fetch all methods for the class, check performance impact
101        // Needs no synchronization now, since methods() returns
102        // unmodifiable local data
103        Iterator<Method> it = methods().iterator();
104        while (it.hasNext()) {
105            MethodImpl method = (MethodImpl)it.next();
106            if (method.ref() == ref) {
107                return method;
108            }
109        }
110        throw new IllegalArgumentException("Invalid method id: " + ref);
111    }
112
113    Field getFieldMirror(long ref) {
114        // Fetch all fields for the class, check performance impact
115        // Needs no synchronization now, since fields() returns
116        // unmodifiable local data
117        Iterator<Field>it = fields().iterator();
118        while (it.hasNext()) {
119            FieldImpl field = (FieldImpl)it.next();
120            if (field.ref() == ref) {
121                return field;
122            }
123        }
124        throw new IllegalArgumentException("Invalid field id: " + ref);
125    }
126
127    public boolean equals(Object obj) {
128        if ((obj != null) && (obj instanceof ReferenceTypeImpl)) {
129            ReferenceTypeImpl other = (ReferenceTypeImpl)obj;
130            return (ref() == other.ref()) &&
131                (vm.equals(other.virtualMachine()));
132        } else {
133            return false;
134        }
135    }
136
137    public int hashCode() {
138        return(int)ref();
139    }
140
141    public int compareTo(ReferenceType object) {
142        /*
143         * Note that it is critical that compareTo() == 0
144         * implies that equals() == true. Otherwise, TreeSet
145         * will collapse classes.
146         *
147         * (Classes of the same name loaded by different class loaders
148         * or in different VMs must not return 0).
149         */
150        ReferenceTypeImpl other = (ReferenceTypeImpl)object;
151        int comp = name().compareTo(other.name());
152        if (comp == 0) {
153            long rf1 = ref();
154            long rf2 = other.ref();
155            // optimize for typical case: refs equal and VMs equal
156            if (rf1 == rf2) {
157                // sequenceNumbers are always positive
158                comp = vm.sequenceNumber -
159                 ((VirtualMachineImpl)(other.virtualMachine())).sequenceNumber;
160            } else {
161                comp = (rf1 < rf2)? -1 : 1;
162            }
163        }
164        return comp;
165    }
166
167    public String signature() {
168        if (signature == null) {
169            // Does not need synchronization, since worst-case
170            // static info is fetched twice
171            if (vm.canGet1_5LanguageFeatures()) {
172                /*
173                 * we might as well get both the signature and the
174                 * generic signature.
175                 */
176                genericSignature();
177            } else {
178                try {
179                    signature = JDWP.ReferenceType.Signature.
180                        process(vm, this).signature;
181                } catch (JDWPException exc) {
182                    throw exc.toJDIException();
183                }
184            }
185        }
186        return signature;
187    }
188
189    public String genericSignature() {
190        // This gets both the signature and the generic signature
191        if (vm.canGet1_5LanguageFeatures() && !genericSignatureGotten) {
192            // Does not need synchronization, since worst-case
193            // static info is fetched twice
194            JDWP.ReferenceType.SignatureWithGeneric result;
195            try {
196                result = JDWP.ReferenceType.SignatureWithGeneric.
197                    process(vm, this);
198            } catch (JDWPException exc) {
199                throw exc.toJDIException();
200            }
201            signature = result.signature;
202            setGenericSignature(result.genericSignature);
203        }
204        return genericSignature;
205    }
206
207    public ClassLoaderReference classLoader() {
208        if (!isClassLoaderCached) {
209            // Does not need synchronization, since worst-case
210            // static info is fetched twice
211            try {
212                classLoader = (ClassLoaderReference)
213                    JDWP.ReferenceType.ClassLoader.
214                    process(vm, this).classLoader;
215                isClassLoaderCached = true;
216            } catch (JDWPException exc) {
217                throw exc.toJDIException();
218            }
219        }
220        return classLoader;
221    }
222
223    public ModuleReference module() {
224        if (module != null) {
225            return module;
226        }
227        // Does not need synchronization, since worst-case
228        // static info is fetched twice
229        try {
230            ModuleReferenceImpl m = JDWP.ReferenceType.Module.
231                process(vm, this).module;
232            module = vm.getModule(m.ref());
233        } catch (JDWPException exc) {
234            throw exc.toJDIException();
235        }
236        return module;
237    }
238
239    public boolean isPublic() {
240        if (modifiers == -1)
241            getModifiers();
242
243        return((modifiers & VMModifiers.PUBLIC) > 0);
244    }
245
246    public boolean isProtected() {
247        if (modifiers == -1)
248            getModifiers();
249
250        return((modifiers & VMModifiers.PROTECTED) > 0);
251    }
252
253    public boolean isPrivate() {
254        if (modifiers == -1)
255            getModifiers();
256
257        return((modifiers & VMModifiers.PRIVATE) > 0);
258    }
259
260    public boolean isPackagePrivate() {
261        return !isPublic() && !isPrivate() && !isProtected();
262    }
263
264    public boolean isAbstract() {
265        if (modifiers == -1)
266            getModifiers();
267
268        return((modifiers & VMModifiers.ABSTRACT) > 0);
269    }
270
271    public boolean isFinal() {
272        if (modifiers == -1)
273            getModifiers();
274
275        return((modifiers & VMModifiers.FINAL) > 0);
276    }
277
278    public boolean isStatic() {
279        if (modifiers == -1)
280            getModifiers();
281
282        return((modifiers & VMModifiers.STATIC) > 0);
283    }
284
285    public boolean isPrepared() {
286        // This ref type may have been prepared before we were getting
287        // events, so get it once.  After that,
288        // this status flag is updated through the ClassPrepareEvent,
289        // there is no need for the expense of a JDWP query.
290        if (status == 0) {
291            updateStatus();
292        }
293        return isPrepared;
294    }
295
296    public boolean isVerified() {
297        // Once true, it never resets, so we don't need to update
298        if ((status & JDWP.ClassStatus.VERIFIED) == 0) {
299            updateStatus();
300        }
301        return (status & JDWP.ClassStatus.VERIFIED) != 0;
302    }
303
304    public boolean isInitialized() {
305        // Once initialization succeeds or fails, it never resets,
306        // so we don't need to update
307        if ((status & INITIALIZED_OR_FAILED) == 0) {
308            updateStatus();
309        }
310        return (status & JDWP.ClassStatus.INITIALIZED) != 0;
311    }
312
313    public boolean failedToInitialize() {
314        // Once initialization succeeds or fails, it never resets,
315        // so we don't need to update
316        if ((status & INITIALIZED_OR_FAILED) == 0) {
317            updateStatus();
318        }
319        return (status & JDWP.ClassStatus.ERROR) != 0;
320    }
321
322    public List<Field> fields() {
323        List<Field> fields = (fieldsRef == null) ? null : fieldsRef.get();
324        if (fields == null) {
325            if (vm.canGet1_5LanguageFeatures()) {
326                JDWP.ReferenceType.FieldsWithGeneric.FieldInfo[] jdwpFields;
327                try {
328                    jdwpFields = JDWP.ReferenceType.FieldsWithGeneric.
329                        process(vm, this).declared;
330                } catch (JDWPException exc) {
331                    throw exc.toJDIException();
332                }
333                fields = new ArrayList<Field>(jdwpFields.length);
334                for (int i=0; i<jdwpFields.length; i++) {
335                    JDWP.ReferenceType.FieldsWithGeneric.FieldInfo fi
336                        = jdwpFields[i];
337
338                    Field field = new FieldImpl(vm, this, fi.fieldID,
339                                                fi.name, fi.signature,
340                                                fi.genericSignature,
341                                                fi.modBits);
342                    fields.add(field);
343                }
344            } else {
345                JDWP.ReferenceType.Fields.FieldInfo[] jdwpFields;
346                try {
347                    jdwpFields = JDWP.ReferenceType.Fields.
348                        process(vm, this).declared;
349                } catch (JDWPException exc) {
350                    throw exc.toJDIException();
351                }
352                fields = new ArrayList<Field>(jdwpFields.length);
353                for (int i=0; i<jdwpFields.length; i++) {
354                    JDWP.ReferenceType.Fields.FieldInfo fi = jdwpFields[i];
355
356                    Field field = new FieldImpl(vm, this, fi.fieldID,
357                                            fi.name, fi.signature,
358                                            null,
359                                            fi.modBits);
360                    fields.add(field);
361                }
362            }
363
364            fields = Collections.unmodifiableList(fields);
365            fieldsRef = new SoftReference<List<Field>>(fields);
366        }
367        return fields;
368    }
369
370    abstract List<? extends ReferenceType> inheritedTypes();
371
372    void addVisibleFields(List<Field> visibleList, Map<String, Field> visibleTable, List<String> ambiguousNames) {
373        for (Field field : visibleFields()) {
374            String name = field.name();
375            if (!ambiguousNames.contains(name)) {
376                Field duplicate = visibleTable.get(name);
377                if (duplicate == null) {
378                    visibleList.add(field);
379                    visibleTable.put(name, field);
380                } else if (!field.equals(duplicate)) {
381                    ambiguousNames.add(name);
382                    visibleTable.remove(name);
383                    visibleList.remove(duplicate);
384                } else {
385                    // identical field from two branches; do nothing
386                }
387            }
388        }
389    }
390
391    public List<Field> visibleFields() {
392        /*
393         * Maintain two different collections of visible fields. The
394         * list maintains a reasonable order for return. The
395         * hash map provides an efficient way to lookup visible fields
396         * by name, important for finding hidden or ambiguous fields.
397         */
398        List<Field> visibleList = new ArrayList<Field>();
399        Map<String, Field>  visibleTable = new HashMap<String, Field>();
400
401        /* Track fields removed from above collection due to ambiguity */
402        List<String> ambiguousNames = new ArrayList<String>();
403
404        /* Add inherited, visible fields */
405        List<? extends ReferenceType> types = inheritedTypes();
406        Iterator<? extends ReferenceType> iter = types.iterator();
407        while (iter.hasNext()) {
408            /*
409             * TO DO: Be defensive and check for cyclic interface inheritance
410             */
411            ReferenceTypeImpl type = (ReferenceTypeImpl)iter.next();
412            type.addVisibleFields(visibleList, visibleTable, ambiguousNames);
413        }
414
415        /*
416         * Insert fields from this type, removing any inherited fields they
417         * hide.
418         */
419        List<Field> retList = new ArrayList<Field>(fields());
420        for (Field field : retList) {
421            Field hidden = visibleTable.get(field.name());
422            if (hidden != null) {
423                visibleList.remove(hidden);
424            }
425        }
426        retList.addAll(visibleList);
427        return retList;
428    }
429
430    void addAllFields(List<Field> fieldList, Set<ReferenceType> typeSet) {
431        /* Continue the recursion only if this type is new */
432        if (!typeSet.contains(this)) {
433            typeSet.add((ReferenceType)this);
434
435            /* Add local fields */
436            fieldList.addAll(fields());
437
438            /* Add inherited fields */
439            List<? extends ReferenceType> types = inheritedTypes();
440            Iterator<? extends ReferenceType> iter = types.iterator();
441            while (iter.hasNext()) {
442                ReferenceTypeImpl type = (ReferenceTypeImpl)iter.next();
443                type.addAllFields(fieldList, typeSet);
444            }
445        }
446    }
447    public List<Field> allFields() {
448        List<Field> fieldList = new ArrayList<Field>();
449        Set<ReferenceType> typeSet = new HashSet<ReferenceType>();
450        addAllFields(fieldList, typeSet);
451        return fieldList;
452    }
453
454    public Field fieldByName(String fieldName) {
455        List<Field> searchList = visibleFields();
456
457        for (int i=0; i<searchList.size(); i++) {
458            Field f = searchList.get(i);
459
460            if (f.name().equals(fieldName)) {
461                return f;
462            }
463        }
464        //throw new NoSuchFieldException("Field '" + fieldName + "' not found in " + name());
465        return null;
466    }
467
468    public List<Method> methods() {
469        List<Method> methods = (methodsRef == null) ? null : methodsRef.get();
470        if (methods == null) {
471            if (!vm.canGet1_5LanguageFeatures()) {
472                methods = methods1_4();
473            } else {
474                JDWP.ReferenceType.MethodsWithGeneric.MethodInfo[] declared;
475                try {
476                    declared = JDWP.ReferenceType.MethodsWithGeneric.
477                        process(vm, this).declared;
478                } catch (JDWPException exc) {
479                    throw exc.toJDIException();
480                }
481                methods = new ArrayList<Method>(declared.length);
482                for (int i=0; i<declared.length; i++) {
483                    JDWP.ReferenceType.MethodsWithGeneric.MethodInfo
484                        mi = declared[i];
485
486                    Method method = MethodImpl.createMethodImpl(vm, this,
487                                                         mi.methodID,
488                                                         mi.name, mi.signature,
489                                                         mi.genericSignature,
490                                                         mi.modBits);
491                    methods.add(method);
492                }
493            }
494            methods = Collections.unmodifiableList(methods);
495            methodsRef = new SoftReference<List<Method>>(methods);
496        }
497        return methods;
498    }
499
500    private List<Method> methods1_4() {
501        List<Method> methods;
502        JDWP.ReferenceType.Methods.MethodInfo[] declared;
503        try {
504            declared = JDWP.ReferenceType.Methods.
505                process(vm, this).declared;
506        } catch (JDWPException exc) {
507            throw exc.toJDIException();
508        }
509        methods = new ArrayList<Method>(declared.length);
510        for (int i=0; i<declared.length; i++) {
511            JDWP.ReferenceType.Methods.MethodInfo mi = declared[i];
512
513            Method method = MethodImpl.createMethodImpl(vm, this,
514                                                        mi.methodID,
515                                                        mi.name, mi.signature,
516                                                        null,
517                                                        mi.modBits);
518            methods.add(method);
519        }
520        return methods;
521    }
522
523    /*
524     * Utility method used by subclasses to build lists of visible
525     * methods.
526     */
527    void addToMethodMap(Map<String, Method> methodMap, List<Method> methodList) {
528        for (Method method : methodList)
529            methodMap.put(method.name().concat(method.signature()), method);
530        }
531
532    abstract void addVisibleMethods(Map<String, Method> methodMap, Set<InterfaceType> seenInterfaces);
533
534    public List<Method> visibleMethods() {
535        /*
536         * Build a collection of all visible methods. The hash
537         * map allows us to do this efficiently by keying on the
538         * concatenation of name and signature.
539         */
540        Map<String, Method> map = new HashMap<String, Method>();
541        addVisibleMethods(map, new HashSet<InterfaceType>());
542
543        /*
544         * ... but the hash map destroys order. Methods should be
545         * returned in a sensible order, as they are in allMethods().
546         * So, start over with allMethods() and use the hash map
547         * to filter that ordered collection.
548         */
549        List<Method> list = allMethods();
550        list.retainAll(new HashSet<Method>(map.values()));
551        return list;
552    }
553
554    abstract public List<Method> allMethods();
555
556    public List<Method> methodsByName(String name) {
557        List<Method> methods = visibleMethods();
558        ArrayList<Method> retList = new ArrayList<Method>(methods.size());
559        for (Method candidate : methods) {
560            if (candidate.name().equals(name)) {
561                retList.add(candidate);
562            }
563        }
564        retList.trimToSize();
565        return retList;
566    }
567
568    public List<Method> methodsByName(String name, String signature) {
569        List<Method> methods = visibleMethods();
570        ArrayList<Method> retList = new ArrayList<Method>(methods.size());
571        for (Method candidate : methods) {
572            if (candidate.name().equals(name) &&
573                candidate.signature().equals(signature)) {
574                retList.add(candidate);
575            }
576        }
577        retList.trimToSize();
578        return retList;
579    }
580
581    List<InterfaceType> getInterfaces() {
582        InterfaceTypeImpl[] intfs;
583        try {
584            intfs = JDWP.ReferenceType.Interfaces.
585                                         process(vm, this).interfaces;
586        } catch (JDWPException exc) {
587            throw exc.toJDIException();
588        }
589        return Arrays.asList((InterfaceType[])intfs);
590    }
591
592    public List<ReferenceType> nestedTypes() {
593        List<ReferenceType> all = vm.allClasses();
594        List<ReferenceType> nested = new ArrayList<ReferenceType>();
595        String outername = name();
596        int outerlen = outername.length();
597        Iterator<ReferenceType> iter = all.iterator();
598        while (iter.hasNext()) {
599            ReferenceType refType = iter.next();
600            String name = refType.name();
601            int len = name.length();
602            /* The separator is historically '$' but could also be '#' */
603            if ( len > outerlen && name.startsWith(outername) ) {
604                char c = name.charAt(outerlen);
605                if ( c =='$' || c== '#' ) {
606                    nested.add(refType);
607                }
608            }
609        }
610        return nested;
611    }
612
613    public Value getValue(Field sig) {
614        List<Field> list = new ArrayList<Field>(1);
615        list.add(sig);
616        Map<Field, Value> map = getValues(list);
617        return map.get(sig);
618    }
619
620
621    void validateFieldAccess(Field field) {
622        /*
623         * Field must be in this object's class, a superclass, or
624         * implemented interface
625         */
626        ReferenceTypeImpl declType = (ReferenceTypeImpl)field.declaringType();
627        if (!declType.isAssignableFrom(this)) {
628            throw new IllegalArgumentException("Invalid field");
629        }
630    }
631
632    void validateFieldSet(Field field) {
633        validateFieldAccess(field);
634        if (field.isFinal()) {
635            throw new IllegalArgumentException("Cannot set value of final field");
636        }
637    }
638
639    /**
640     * Returns a map of field values
641     */
642    public Map<Field,Value> getValues(List<? extends Field> theFields) {
643        validateMirrors(theFields);
644
645        int size = theFields.size();
646        JDWP.ReferenceType.GetValues.Field[] queryFields =
647                         new JDWP.ReferenceType.GetValues.Field[size];
648
649        for (int i=0; i<size; i++) {
650            FieldImpl field = (FieldImpl)theFields.get(i);
651
652            validateFieldAccess(field);
653
654            // Do more validation specific to ReferenceType field getting
655            if (!field.isStatic()) {
656                throw new IllegalArgumentException(
657                     "Attempt to use non-static field with ReferenceType");
658            }
659            queryFields[i] = new JDWP.ReferenceType.GetValues.Field(
660                                         field.ref());
661        }
662
663        Map<Field, Value> map = new HashMap<Field, Value>(size);
664
665        ValueImpl[] values;
666        try {
667            values = JDWP.ReferenceType.GetValues.
668                                     process(vm, this, queryFields).values;
669        } catch (JDWPException exc) {
670            throw exc.toJDIException();
671        }
672
673        if (size != values.length) {
674            throw new InternalException(
675                         "Wrong number of values returned from target VM");
676        }
677        for (int i=0; i<size; i++) {
678            FieldImpl field = (FieldImpl)theFields.get(i);
679            map.put(field, values[i]);
680        }
681
682        return map;
683    }
684
685    public ClassObjectReference classObject() {
686        if (classObject == null) {
687            // Are classObjects unique for an Object, or
688            // created each time? Is this spec'ed?
689            synchronized(this) {
690                if (classObject == null) {
691                    try {
692                        classObject = JDWP.ReferenceType.ClassObject.
693                            process(vm, this).classObject;
694                    } catch (JDWPException exc) {
695                        throw exc.toJDIException();
696                    }
697                }
698            }
699        }
700        return classObject;
701    }
702
703    SDE.Stratum stratum(String stratumID) {
704        SDE sde = sourceDebugExtensionInfo();
705        if (!sde.isValid()) {
706            sde = NO_SDE_INFO_MARK;
707        }
708        return sde.stratum(stratumID);
709    }
710
711    public String sourceName() throws AbsentInformationException {
712        return sourceNames(vm.getDefaultStratum()).get(0);
713    }
714
715    public List<String> sourceNames(String stratumID)
716                                throws AbsentInformationException {
717        SDE.Stratum stratum = stratum(stratumID);
718        if (stratum.isJava()) {
719            List<String> result = new ArrayList<String>(1);
720            result.add(baseSourceName());
721            return result;
722        }
723        return stratum.sourceNames(this);
724    }
725
726    public List<String> sourcePaths(String stratumID)
727                                throws AbsentInformationException {
728        SDE.Stratum stratum = stratum(stratumID);
729        if (stratum.isJava()) {
730            List<String> result = new ArrayList<String>(1);
731            result.add(baseSourceDir() + baseSourceName());
732            return result;
733        }
734        return stratum.sourcePaths(this);
735    }
736
737    String baseSourceName() throws AbsentInformationException {
738        String bsn = baseSourceName;
739        if (bsn == null) {
740            // Does not need synchronization, since worst-case
741            // static info is fetched twice
742            try {
743                bsn = JDWP.ReferenceType.SourceFile.
744                    process(vm, this).sourceFile;
745            } catch (JDWPException exc) {
746                if (exc.errorCode() == JDWP.Error.ABSENT_INFORMATION) {
747                    bsn = ABSENT_BASE_SOURCE_NAME;
748                } else {
749                    throw exc.toJDIException();
750                }
751            }
752            baseSourceName = bsn;
753        }
754        if (bsn == ABSENT_BASE_SOURCE_NAME) {
755            throw new AbsentInformationException();
756        }
757        return bsn;
758    }
759
760    String baseSourcePath() throws AbsentInformationException {
761        String bsp = baseSourcePath;
762        if (bsp == null) {
763            bsp = baseSourceDir() + baseSourceName();
764            baseSourcePath = bsp;
765        }
766        return bsp;
767    }
768
769    String baseSourceDir() {
770        if (baseSourceDir == null) {
771            String typeName = name();
772            StringBuilder sb = new StringBuilder(typeName.length() + 10);
773            int index = 0;
774            int nextIndex;
775
776            while ((nextIndex = typeName.indexOf('.', index)) > 0) {
777                sb.append(typeName.substring(index, nextIndex));
778                sb.append(java.io.File.separatorChar);
779                index = nextIndex + 1;
780            }
781            baseSourceDir = sb.toString();
782        }
783        return baseSourceDir;
784    }
785
786    public String sourceDebugExtension()
787                           throws AbsentInformationException {
788        if (!vm.canGetSourceDebugExtension()) {
789            throw new UnsupportedOperationException();
790        }
791        SDE sde = sourceDebugExtensionInfo();
792        if (sde == NO_SDE_INFO_MARK) {
793            throw new AbsentInformationException();
794        }
795        return sde.sourceDebugExtension;
796    }
797
798    private SDE sourceDebugExtensionInfo() {
799        if (!vm.canGetSourceDebugExtension()) {
800            return NO_SDE_INFO_MARK;
801        }
802        SDE sde = (sdeRef == null) ?  null : sdeRef.get();
803        if (sde == null) {
804            String extension = null;
805            try {
806                extension = JDWP.ReferenceType.SourceDebugExtension.
807                    process(vm, this).extension;
808            } catch (JDWPException exc) {
809                if (exc.errorCode() != JDWP.Error.ABSENT_INFORMATION) {
810                    sdeRef = new SoftReference<SDE>(NO_SDE_INFO_MARK);
811                    throw exc.toJDIException();
812                }
813            }
814            if (extension == null) {
815                sde = NO_SDE_INFO_MARK;
816            } else {
817                sde = new SDE(extension);
818            }
819            sdeRef = new SoftReference<SDE>(sde);
820        }
821        return sde;
822    }
823
824    public List<String> availableStrata() {
825        SDE sde = sourceDebugExtensionInfo();
826        if (sde.isValid()) {
827            return sde.availableStrata();
828        } else {
829            List<String> strata = new ArrayList<String>();
830            strata.add(SDE.BASE_STRATUM_NAME);
831            return strata;
832        }
833    }
834
835    /**
836     * Always returns non-null stratumID
837     */
838    public String defaultStratum() {
839        SDE sdei = sourceDebugExtensionInfo();
840        if (sdei.isValid()) {
841            return sdei.defaultStratumId;
842        } else {
843            return SDE.BASE_STRATUM_NAME;
844        }
845    }
846
847    public int modifiers() {
848        if (modifiers == -1)
849            getModifiers();
850
851        return modifiers;
852    }
853
854    public List<Location> allLineLocations()
855                            throws AbsentInformationException {
856        return allLineLocations(vm.getDefaultStratum(), null);
857    }
858
859    public List<Location> allLineLocations(String stratumID, String sourceName)
860                            throws AbsentInformationException {
861        boolean someAbsent = false; // A method that should have info, didn't
862        SDE.Stratum stratum = stratum(stratumID);
863        List<Location> list = new ArrayList<Location>();  // location list
864
865        for (Iterator<Method> iter = methods().iterator(); iter.hasNext(); ) {
866            MethodImpl method = (MethodImpl)iter.next();
867            try {
868                list.addAll(
869                   method.allLineLocations(stratum, sourceName));
870            } catch(AbsentInformationException exc) {
871                someAbsent = true;
872            }
873        }
874
875        // If we retrieved no line info, and at least one of the methods
876        // should have had some (as determined by an
877        // AbsentInformationException being thrown) then we rethrow
878        // the AbsentInformationException.
879        if (someAbsent && list.size() == 0) {
880            throw new AbsentInformationException();
881        }
882        return list;
883    }
884
885    public List<Location> locationsOfLine(int lineNumber)
886                           throws AbsentInformationException {
887        return locationsOfLine(vm.getDefaultStratum(),
888                               null,
889                               lineNumber);
890    }
891
892    public List<Location> locationsOfLine(String stratumID,
893                                String sourceName,
894                                int lineNumber)
895                           throws AbsentInformationException {
896        // A method that should have info, didn't
897        boolean someAbsent = false;
898        // A method that should have info, did
899        boolean somePresent = false;
900        List<Method> methods = methods();
901        SDE.Stratum stratum = stratum(stratumID);
902
903        List<Location> list = new ArrayList<Location>();
904
905        Iterator<Method> iter = methods.iterator();
906        while(iter.hasNext()) {
907            MethodImpl method = (MethodImpl)iter.next();
908            // eliminate native and abstract to eliminate
909            // false positives
910            if (!method.isAbstract() &&
911                !method.isNative()) {
912                try {
913                    list.addAll(
914                       method.locationsOfLine(stratum,
915                                              sourceName,
916                                              lineNumber));
917                    somePresent = true;
918                } catch(AbsentInformationException exc) {
919                    someAbsent = true;
920                }
921            }
922        }
923        if (someAbsent && !somePresent) {
924            throw new AbsentInformationException();
925        }
926        return list;
927    }
928
929    public List<ObjectReference> instances(long maxInstances) {
930        if (!vm.canGetInstanceInfo()) {
931            throw new UnsupportedOperationException(
932                "target does not support getting instances");
933        }
934
935        if (maxInstances < 0) {
936            throw new IllegalArgumentException("maxInstances is less than zero: "
937                                              + maxInstances);
938        }
939        int intMax = (maxInstances > Integer.MAX_VALUE)?
940            Integer.MAX_VALUE: (int)maxInstances;
941        // JDWP can't currently handle more than this (in mustang)
942
943        try {
944            return Arrays.asList(
945                (ObjectReference[])JDWP.ReferenceType.Instances.
946                        process(vm, this, intMax).instances);
947        } catch (JDWPException exc) {
948            throw exc.toJDIException();
949        }
950    }
951
952    private void getClassFileVersion() {
953        if (!vm.canGetClassFileVersion()) {
954            throw new UnsupportedOperationException();
955        }
956        JDWP.ReferenceType.ClassFileVersion classFileVersion;
957        if (versionNumberGotten) {
958            return;
959        } else {
960            try {
961                classFileVersion = JDWP.ReferenceType.ClassFileVersion.process(vm, this);
962            } catch (JDWPException exc) {
963                if (exc.errorCode() == JDWP.Error.ABSENT_INFORMATION) {
964                    majorVersion = 0;
965                    minorVersion = 0;
966                    versionNumberGotten = true;
967                    return;
968                } else {
969                    throw exc.toJDIException();
970                }
971            }
972            majorVersion = classFileVersion.majorVersion;
973            minorVersion = classFileVersion.minorVersion;
974            versionNumberGotten = true;
975        }
976    }
977
978    public int majorVersion() {
979        try {
980            getClassFileVersion();
981        } catch (RuntimeException exc) {
982            throw exc;
983        }
984        return majorVersion;
985    }
986
987    public int minorVersion() {
988        try {
989            getClassFileVersion();
990        } catch (RuntimeException exc) {
991            throw exc;
992        }
993        return minorVersion;
994    }
995
996    private byte[] getConstantPoolInfo() {
997        JDWP.ReferenceType.ConstantPool jdwpCPool;
998        if (!vm.canGetConstantPool()) {
999            throw new UnsupportedOperationException();
1000        }
1001        if (constantPoolInfoGotten) {
1002            if (constantPoolBytesRef == null) {
1003                return null;
1004            }
1005            byte[] cpbytes = constantPoolBytesRef.get();
1006            if (cpbytes != null) {
1007                return cpbytes;
1008            }
1009        }
1010
1011        try {
1012            jdwpCPool = JDWP.ReferenceType.ConstantPool.process(vm, this);
1013        } catch (JDWPException exc) {
1014            if (exc.errorCode() == JDWP.Error.ABSENT_INFORMATION) {
1015                constanPoolCount = 0;
1016                constantPoolBytesRef = null;
1017                constantPoolInfoGotten = true;
1018                return null;
1019            } else {
1020                throw exc.toJDIException();
1021            }
1022        }
1023        byte[] cpbytes;
1024        constanPoolCount = jdwpCPool.count;
1025        cpbytes = jdwpCPool.bytes;
1026        constantPoolBytesRef = new SoftReference<byte[]>(cpbytes);
1027        constantPoolInfoGotten = true;
1028        return cpbytes;
1029    }
1030
1031    public int constantPoolCount() {
1032        try {
1033            getConstantPoolInfo();
1034        } catch (RuntimeException exc) {
1035            throw exc;
1036        }
1037        return constanPoolCount;
1038    }
1039
1040    public byte[] constantPool() {
1041        byte[] cpbytes;
1042        try {
1043            cpbytes = getConstantPoolInfo();
1044        } catch (RuntimeException exc) {
1045            throw exc;
1046        }
1047        if (cpbytes != null) {
1048            /*
1049             * Arrays are always modifiable, so it is a little unsafe
1050             * to return the cached bytecodes directly; instead, we
1051             * make a clone at the cost of using more memory.
1052             */
1053            return cpbytes.clone();
1054        } else {
1055            return null;
1056        }
1057    }
1058
1059    // Does not need synchronization, since worst-case
1060    // static info is fetched twice
1061    void getModifiers() {
1062        if (modifiers != -1) {
1063            return;
1064        }
1065        try {
1066            modifiers = JDWP.ReferenceType.Modifiers.
1067                                  process(vm, this).modBits;
1068        } catch (JDWPException exc) {
1069            throw exc.toJDIException();
1070        }
1071    }
1072
1073    void decodeStatus(int status) {
1074        this.status = status;
1075        if ((status & JDWP.ClassStatus.PREPARED) != 0) {
1076            isPrepared = true;
1077        }
1078    }
1079
1080    void updateStatus() {
1081        try {
1082            decodeStatus(JDWP.ReferenceType.Status.process(vm, this).status);
1083        } catch (JDWPException exc) {
1084            throw exc.toJDIException();
1085        }
1086    }
1087
1088    void markPrepared() {
1089        isPrepared = true;
1090    }
1091
1092    long ref() {
1093        return ref;
1094    }
1095
1096    int indexOf(Method method) {
1097        // Make sure they're all here - the obsolete method
1098        // won't be found and so will have index -1
1099        return methods().indexOf(method);
1100    }
1101
1102    int indexOf(Field field) {
1103        // Make sure they're all here
1104        return fields().indexOf(field);
1105    }
1106
1107    /*
1108     * Return true if an instance of this type
1109     * can be assigned to a variable of the given type
1110     */
1111    abstract boolean isAssignableTo(ReferenceType type);
1112
1113    boolean isAssignableFrom(ReferenceType type) {
1114        return ((ReferenceTypeImpl)type).isAssignableTo(this);
1115    }
1116
1117    boolean isAssignableFrom(ObjectReference object) {
1118        return object == null ||
1119               isAssignableFrom(object.referenceType());
1120    }
1121
1122    void setStatus(int status) {
1123        decodeStatus(status);
1124    }
1125
1126    void setSignature(String signature) {
1127        this.signature = signature;
1128    }
1129
1130    void setGenericSignature(String signature) {
1131        if (signature != null && signature.length() == 0) {
1132            this.genericSignature = null;
1133        } else{
1134            this.genericSignature = signature;
1135        }
1136        this.genericSignatureGotten = true;
1137    }
1138
1139    private static boolean isPrimitiveArray(String signature) {
1140        int i = signature.lastIndexOf('[');
1141        /*
1142         * TO DO: Centralize JNI signature knowledge.
1143         *
1144         * Ref:
1145         *  jdk1.4/doc/guide/jpda/jdi/com/sun/jdi/doc-files/signature.html
1146         */
1147        boolean isPA;
1148        if (i < 0) {
1149            isPA = false;
1150        } else {
1151            char c = signature.charAt(i + 1);
1152            isPA = (c != 'L');
1153        }
1154        return isPA;
1155    }
1156
1157    Type findType(String signature) throws ClassNotLoadedException {
1158        Type type;
1159        if (signature.length() == 1) {
1160            /* OTI FIX: Must be a primitive type or the void type */
1161            char sig = signature.charAt(0);
1162            if (sig == 'V') {
1163                type = vm.theVoidType();
1164            } else {
1165                type = vm.primitiveTypeMirror((byte)sig);
1166            }
1167        } else {
1168            // Must be a reference type.
1169            ClassLoaderReferenceImpl loader =
1170                       (ClassLoaderReferenceImpl)classLoader();
1171            if ((loader == null) ||
1172                (isPrimitiveArray(signature)) //Work around 4450091
1173                ) {
1174                // Caller wants type of boot class field
1175                type = vm.findBootType(signature);
1176            } else {
1177                // Caller wants type of non-boot class field
1178                type = loader.findType(signature);
1179            }
1180        }
1181        return type;
1182    }
1183
1184    String loaderString() {
1185        if (classLoader() != null) {
1186            return "loaded by " + classLoader().toString();
1187        } else {
1188            return "no class loader";
1189        }
1190    }
1191
1192}
1193