001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.debugger.jpda.models;
043:
044: import com.sun.jdi.ArrayReference;
045: import com.sun.jdi.ArrayType;
046: import com.sun.jdi.CharValue;
047: import com.sun.jdi.ClassType;
048: import com.sun.jdi.Method;
049: import com.sun.jdi.ObjectCollectedException;
050: import com.sun.jdi.ObjectReference;
051: import com.sun.jdi.PrimitiveValue;
052: import com.sun.jdi.ReferenceType;
053: import com.sun.jdi.StringReference;
054: import com.sun.jdi.Type;
055: import com.sun.jdi.VMDisconnectedException;
056: import com.sun.jdi.Value;
057: import java.beans.PropertyChangeEvent;
058: import java.beans.PropertyChangeListener;
059: import java.util.AbstractList;
060: import java.util.ArrayList;
061: import java.util.Collections;
062: import java.util.HashSet;
063: import java.util.List;
064: import java.util.Set;
065: import java.io.PushbackReader;
066: import java.io.StringReader;
067: import java.io.IOException;
068: import java.util.logging.Level;
069: import java.util.logging.Logger;
070: import org.netbeans.api.debugger.jpda.InvalidExpressionException;
071: import org.netbeans.api.debugger.jpda.JPDAClassType;
072: import org.netbeans.api.debugger.jpda.Field;
073: import org.netbeans.api.debugger.jpda.JPDADebugger;
074: import org.netbeans.api.debugger.jpda.ObjectVariable;
075: import org.netbeans.api.debugger.jpda.Super;
076: import org.netbeans.api.debugger.jpda.Variable;
077: import org.netbeans.modules.debugger.jpda.JPDADebuggerImpl;
078: import org.netbeans.modules.debugger.jpda.Java6Methods;
079: import org.openide.util.NbBundle;
080: import org.openide.util.WeakListeners;
081:
082: /**
083: * @author Jan Jancura
084: */
085: class AbstractObjectVariable extends AbstractVariable implements
086: ObjectVariable {
087: // Customized for add/removePropertyChangeListener
088: // Cloneable for fixed watches
089:
090: private static final Logger logger = Logger
091: .getLogger("org.netbeans.modules.debugger.jpda.getValue"); // NOI8N
092:
093: private String genericType;
094: private Field[] fields;
095: private Field[] staticFields;
096: private Field[] inheritedFields;
097: private volatile boolean refreshFields;
098:
099: private DebuggetStateListener stateChangeListener = new DebuggetStateListener();
100:
101: AbstractObjectVariable(JPDADebuggerImpl debugger, Value value,
102: String id) {
103: super (debugger, value, id);
104: debugger.addPropertyChangeListener(WeakListeners
105: .propertyChange(stateChangeListener, debugger));
106: }
107:
108: AbstractObjectVariable(JPDADebuggerImpl debugger, Value value,
109: String genericSignature, String id) {
110: this (debugger, value, id);
111: try {
112: if (genericSignature != null) {
113: this .genericType = getTypeDescription(new PushbackReader(
114: new StringReader(genericSignature), 1));
115: }
116: } catch (IOException e) {
117: /// invalid signature
118: }
119: }
120:
121: // public interface ........................................................
122:
123: /**
124: * Returns string representation of type of this variable.
125: *
126: * @return string representation of type of this variable.
127: */
128: public int getFieldsCount() {
129: Value v = getInnerValue();
130: if (v == null)
131: return 0;
132: if (v instanceof ArrayReference) {
133: try {
134: return ((ArrayReference) v).length();
135: } catch (ObjectCollectedException ocex) {
136: return 0;
137: }
138: } else {
139: if (fields == null || refreshFields) {
140: initFields();
141: }
142: return fields.length;
143: }
144: }
145:
146: /**
147: * Returns field defined in this object.
148: *
149: * @param name a name of field to be returned
150: *
151: * @return field defined in this object
152: */
153: public Field getField(String name) {
154: if (getInnerValue() == null)
155: return null;
156: com.sun.jdi.Field f;
157: try {
158: f = ((ReferenceType) this .getInnerValue().type())
159: .fieldByName(name);
160: } catch (ObjectCollectedException ocex) {
161: return null;
162: }
163: if (f == null)
164: return null;
165: return this .getField(f, (ObjectReference) getInnerValue(),
166: getID());
167: }
168:
169: /**
170: * Returns all fields declared in this type that are in interval
171: * <<code>from</code>, <code>to</code>).
172: */
173: public Field[] getFields(int from, int to) {
174: Value v = getInnerValue();
175: if (v == null)
176: return new Field[] {};
177: try {
178: if (v instanceof ArrayReference
179: && (from > 0 || to < ((ArrayReference) v).length())) {
180: // compute only requested elements
181: Type type = v.type();
182: ReferenceType rt = (ReferenceType) type;
183: if (to == 0)
184: to = ((ArrayReference) v).length();
185: Field[] elements = getFieldsOfArray((ArrayReference) v,
186: ((ArrayType) rt).componentTypeName(), this
187: .getID(), from, to);
188: return elements;
189: } else {
190: //either the fields are cached or we have to init them
191: if (fields == null || refreshFields) {
192: initFields();
193: }
194: if (to != 0) {
195: to = Math.min(fields.length, to);
196: from = Math.min(fields.length, from);
197: Field[] fv = new Field[to - from];
198: System.arraycopy(fields, from, fv, 0, to - from);
199: return fv;
200: }
201: return fields;
202: }
203: } catch (ObjectCollectedException ocex) {
204: return new Field[] {};
205: }
206: }
207:
208: /**
209: * Return all static fields.
210: *
211: * @return all static fields
212: */
213: public Field[] getAllStaticFields(int from, int to) {
214: Value v = getInnerValue();
215: if (v == null || v instanceof ArrayReference) {
216: return new Field[] {};
217: }
218: if (fields == null || refreshFields) {
219: initFields();
220: }
221: if (to != 0) {
222: to = Math.min(staticFields.length, to);
223: from = Math.min(staticFields.length, from);
224: FieldVariable[] fv = new FieldVariable[to - from];
225: System.arraycopy(staticFields, from, fv, 0, to - from);
226: return fv;
227: }
228: return staticFields;
229: }
230:
231: /**
232: * Return all inherited fields.
233: *
234: * @return all inherited fields
235: */
236: public Field[] getInheritedFields(int from, int to) {
237: Value v = getInnerValue();
238: if (v == null || v instanceof ArrayReference) {
239: return new Field[] {};
240: }
241: if (fields == null || refreshFields) {
242: initFields();
243: }
244: if (to != 0) {
245: to = Math.min(inheritedFields.length, to);
246: from = Math.min(inheritedFields.length, from);
247: FieldVariable[] fv = new FieldVariable[to - from];
248: System.arraycopy(inheritedFields, from, fv, 0, to - from);
249: return fv;
250: }
251: return inheritedFields;
252: }
253:
254: public Super getSuper() {
255: if (getInnerValue() == null)
256: return null;
257: try {
258: Type t = this .getInnerValue().type();
259: if (!(t instanceof ClassType))
260: return null;
261: ClassType super Type = ((ClassType) t).super class();
262: if (super Type == null)
263: return null;
264: return new SuperVariable(getDebugger(),
265: (ObjectReference) this .getInnerValue(), super Type,
266: getID());
267: } catch (ObjectCollectedException ocex) {
268: return null;
269: }
270: }
271:
272: /**
273: * Calls {@link java.lang.Object#toString} in debugged JVM and returns
274: * its value.
275: *
276: * @return toString () value of this instance
277: */
278: public String getToStringValue() throws InvalidExpressionException {
279: Value v = getInnerValue();
280: return getToStringValue(v, getDebugger());
281: }
282:
283: static String getToStringValue(Value v, JPDADebuggerImpl debugger)
284: throws InvalidExpressionException {
285: if (v == null)
286: return null;
287: try {
288: if (!(v.type() instanceof ClassType))
289: return AbstractVariable.getValue(v);
290: if (v instanceof CharValue)
291: return "\'" + v.toString() + "\'";
292: if (v instanceof StringReference)
293: return "\"" + ((StringReference) v).value() + "\"";
294: Method toStringMethod = ((ClassType) v.type())
295: .concreteMethodByName("toString",
296: "()Ljava/lang/String;");
297: StringReference sr = (StringReference) debugger
298: .invokeMethod((ObjectReference) v, toStringMethod,
299: new Value[0]);
300: if (sr == null) {
301: return null;
302: } else {
303: return sr.value();
304: }
305: } catch (VMDisconnectedException ex) {
306: return NbBundle.getMessage(AbstractVariable.class,
307: "MSG_Disconnected");
308: } catch (ObjectCollectedException ocex) {
309: return NbBundle.getMessage(AbstractVariable.class,
310: "MSG_ObjCollected");
311: }
312: }
313:
314: /**
315: * Calls given method in debugged JVM on this instance and returns
316: * its value.
317: *
318: * @param methodName a name of method to be called
319: * @param signature a signature of method to be called
320: * @param arguments a arguments to be used
321: *
322: * @return value of given method call on this instance
323: */
324: public Variable invokeMethod(String methodName, String signature,
325: Variable[] arguments) throws NoSuchMethodException,
326: InvalidExpressionException {
327: try {
328:
329: // 1) find corrent method
330: if (this .getInnerValue() == null)
331: return null;
332: Method method = null;
333: if (signature != null)
334: method = ((ClassType) this .getInnerValue().type())
335: .concreteMethodByName(methodName, signature);
336: else {
337: List l = ((ClassType) this .getInnerValue().type())
338: .methodsByName(methodName);
339: int j, jj = l.size();
340: for (j = 0; j < jj; j++)
341: if (!((Method) l.get(j)).isAbstract()
342: && ((Method) l.get(j)).argumentTypeNames()
343: .size() == 0) {
344: method = (Method) l.get(j);
345: break;
346: }
347: }
348:
349: // 2) method not found => print all method signatures
350: if (method == null) {
351: List l = ((ClassType) this .getInnerValue().type())
352: .methodsByName(methodName);
353: int j, jj = l.size();
354: for (j = 0; j < jj; j++)
355: System.out.println(((Method) l.get(j)).signature());
356: throw new NoSuchMethodException(this .getInnerValue()
357: .type().name()
358: + "." + methodName + " : " + signature);
359: }
360:
361: // 3) call this method
362: Value[] vs = new Value[arguments.length];
363: int i, k = arguments.length;
364: for (i = 0; i < k; i++)
365: vs[i] = ((AbstractVariable) arguments[i])
366: .getInnerValue();
367: Value v = getDebugger().invokeMethod(
368: (ObjectReference) this .getInnerValue(), method, vs);
369:
370: // 4) encapsulate result
371: if (v instanceof ObjectReference)
372: return new AbstractObjectVariable( // It's also ObjectVariable
373: getDebugger(), (ObjectReference) v, getID()
374: + method + "^");
375: return new AbstractVariable(getDebugger(), v, getID()
376: + method);
377: } catch (VMDisconnectedException ex) {
378: return null;
379: } catch (ObjectCollectedException ocex) {
380: return null;
381: }
382: }
383:
384: /**
385: * Declared type of this local.
386: *
387: * @return declared type of this local
388: */
389: public String getType() {
390: if (genericType != null)
391: return genericType;
392: if (getInnerValue() == null)
393: return "";
394: try {
395: return this .getInnerValue().type().name();
396: } catch (VMDisconnectedException vmdex) {
397: // The session is gone.
398: return NbBundle.getMessage(AbstractVariable.class,
399: "MSG_Disconnected");
400: } catch (ObjectCollectedException ocex) {
401: // The object is gone.
402: return NbBundle.getMessage(AbstractVariable.class,
403: "MSG_ObjCollected");
404: }
405: }
406:
407: public JPDAClassType getClassType() {
408: Value value = getInnerValue();
409: if (value == null)
410: return null;
411: com.sun.jdi.Type type = value.type();
412: if (type instanceof ReferenceType) {
413: return new JPDAClassTypeImpl(getDebugger(),
414: (ReferenceType) type);
415: } else {
416: return null;
417: }
418: }
419:
420: public boolean equals(Object o) {
421: return (o instanceof AbstractObjectVariable)
422: && (getID()
423: .equals(((AbstractObjectVariable) o).getID()));
424: }
425:
426: // other methods............................................................
427:
428: protected void setInnerValue(Value v) {
429: super .setInnerValue(v);
430: fields = null;
431: staticFields = null;
432: inheritedFields = null;
433: }
434:
435: private static String getTypeDescription(PushbackReader signature)
436: throws IOException {
437: int c = signature.read();
438: switch (c) {
439: case 'Z':
440: return "boolean";
441: case 'B':
442: return "byte";
443: case 'C':
444: return "char";
445: case 'S':
446: return "short";
447: case 'I':
448: return "int";
449: case 'J':
450: return "long";
451: case 'F':
452: return "float";
453: case 'D':
454: return "double";
455: case '[': {
456: int arrayCount = 1;
457: for (;; arrayCount++) {
458: if ((c = signature.read()) != '[') {
459: signature.unread(c);
460: break;
461: }
462: }
463: return getTypeDescription(signature) + " "
464: + brackets(arrayCount);
465: }
466: case 'L': {
467: StringBuffer typeName = new StringBuffer(50);
468: for (;;) {
469: c = signature.read();
470: if (c == ';') {
471: int idx = typeName.lastIndexOf("/");
472: return idx == -1 ? typeName.toString() : typeName
473: .substring(idx + 1);
474: } else if (c == '<') {
475: int idx = typeName.lastIndexOf("/");
476: if (idx != -1)
477: typeName.delete(0, idx + 1);
478: typeName.append("<");
479: for (;;) {
480: String td = getTypeDescription(signature);
481: typeName.append(td);
482: c = signature.read();
483: if (c == '>')
484: break;
485: signature.unread(c);
486: typeName.append(',');
487: }
488: signature.read(); // should be a semicolon
489: typeName.append(">");
490: return typeName.toString();
491: }
492: typeName.append((char) c);
493: }
494: }
495: }
496: throw new IOException();
497: }
498:
499: private static String brackets(int arrayCount) {
500: StringBuffer sb = new StringBuffer(arrayCount * 2);
501: do {
502: sb.append("[]");
503: } while (--arrayCount > 0);
504: return sb.toString();
505: }
506:
507: private void initFields() {
508: refreshFields = false;
509: Value value = getInnerValue();
510: Type type;
511: if (value != null) {
512: try {
513: type = getInnerValue().type();
514: } catch (ObjectCollectedException ocex) {
515: type = null;
516: }
517: } else {
518: type = null;
519: }
520: if (!(getInnerValue() instanceof ObjectReference)
521: || !(type instanceof ReferenceType)) {
522: this .fields = new Field[0];
523: this .staticFields = new Field[0];
524: this .inheritedFields = new Field[0];
525: } else {
526: try {
527: ObjectReference or = (ObjectReference) this
528: .getInnerValue();
529: ReferenceType rt = (ReferenceType) type;
530: if (or instanceof ArrayReference) {
531: this .fields = getFieldsOfArray((ArrayReference) or,
532: ((ArrayType) rt).componentTypeName(), this
533: .getID(), 0, ((ArrayReference) or)
534: .length());
535: this .staticFields = new Field[0];
536: this .inheritedFields = new Field[0];
537: } else {
538: initFieldsOfClass(or, rt, this .getID());
539: }
540: } catch (ObjectCollectedException ocex) {
541: // The object is gone => no fields
542: }
543: }
544: }
545:
546: private Field[] getFieldsOfArray(ArrayReference ar,
547: String componentType, String parentID, int from, int to) {
548: List l;
549: try {
550: l = ar.getValues(from, to - from);
551: } catch (ObjectCollectedException ocex) {
552: l = java.util.Collections.EMPTY_LIST;
553: }
554: int i, k = l.size();
555: Field[] ch = new Field[k];
556: for (i = 0; i < k; i++) {
557: Value v = (Value) l.get(i);
558: ch[i] = (v instanceof ObjectReference) ? new ObjectArrayFieldVariable(
559: getDebugger(), (ObjectReference) v, componentType,
560: ar, from + i, to - 1, parentID)
561: : new ArrayFieldVariable(getDebugger(),
562: (PrimitiveValue) v, componentType, ar, from
563: + i, to - 1, parentID);
564: }
565: return ch;
566: }
567:
568: private void initFieldsOfClass(ObjectReference or,
569: ReferenceType rt, String parentID) {
570: List<Field> fields = new ArrayList<Field>();
571: List<Field> staticFields = new ArrayList<Field>();
572: List<Field> allInheretedFields = new ArrayList<Field>();
573:
574: List<com.sun.jdi.Field> l = rt.allFields();
575: Set<com.sun.jdi.Field> s = new HashSet<com.sun.jdi.Field>(rt
576: .fields());
577:
578: int i, k = l.size();
579: for (i = 0; i < k; i++) {
580: com.sun.jdi.Field f = l.get(i);
581: Field field = this .getField(f, or, this .getID());
582: if (f.isStatic())
583: staticFields.add(field);
584: else {
585: if (s.contains(f))
586: fields.add(field);
587: else
588: allInheretedFields.add(field);
589: }
590: }
591: this .fields = fields.toArray(new Field[fields.size()]);
592: this .inheritedFields = allInheretedFields
593: .toArray(new Field[allInheretedFields.size()]);
594: this .staticFields = staticFields.toArray(new Field[staticFields
595: .size()]);
596: }
597:
598: org.netbeans.api.debugger.jpda.Field getField(com.sun.jdi.Field f,
599: ObjectReference or, String parentID) {
600: Value v;
601: try {
602: if (logger.isLoggable(Level.FINE)) {
603: logger.fine("STARTED : " + or + ".getValue(" + f + ")");
604: }
605: v = or.getValue(f);
606: } catch (ObjectCollectedException ocex) {
607: v = null;
608: }
609: if (logger.isLoggable(Level.FINE)) {
610: logger.fine("FINISHED: " + or + ".getValue(" + f + ") = "
611: + v);
612: }
613: if ((v == null) || (v instanceof ObjectReference))
614: return new ObjectFieldVariable(getDebugger(),
615: (ObjectReference) v, f, parentID, JPDADebuggerImpl
616: .getGenericSignature(f), or);
617: return new FieldVariable(getDebugger(), (PrimitiveValue) v, f,
618: parentID, or);
619: }
620:
621: public List<ObjectVariable> getReferringObjects(long maxReferrers) {
622: Value v = getJDIValue();
623: if (v instanceof ObjectReference) {
624: if (Java6Methods.isJDK6()) {
625: final String name = Long.toString(getUniqueID());
626: final List<ObjectReference> referrers = Java6Methods
627: .referringObjects((ObjectReference) v,
628: maxReferrers);
629: return new AbstractList<ObjectVariable>() {
630: public ObjectVariable get(int i) {
631: ObjectReference obj = referrers.get(i);
632: return new AbstractObjectVariable(
633: getDebugger(), obj, name + " referrer "
634: + i);
635: }
636:
637: public int size() {
638: return referrers.size();
639: }
640: };
641: } else {
642: return Collections.emptyList();
643: }
644: } else {
645: return Collections.emptyList();
646: }
647: }
648:
649: public long getUniqueID() {
650: Value value = getJDIValue();
651: if (!(value instanceof ObjectReference)) { // null or anything else than Object
652: return 0L;
653: } else {
654: return ((ObjectReference) value).uniqueID();
655: }
656: }
657:
658: private int cloneNumber = 1;
659:
660: public Variable clone() {
661: AbstractObjectVariable clon = new AbstractObjectVariable(
662: getDebugger(), getJDIValue(), getID() + "_clone"
663: + (cloneNumber++));
664: clon.genericType = this .genericType;
665: return clon;
666: }
667:
668: public String toString() {
669: return "ObjectVariable ";
670: }
671:
672: /* Uncomment when needed. Was used to create "readable" String and Char values.
673: private static String convertToStringInitializer (String s) {
674: StringBuffer sb = new StringBuffer ();
675: int i, k = s.length ();
676: for (i = 0; i < k; i++)
677: switch (s.charAt (i)) {
678: case '\b':
679: sb.append ("\\b");
680: break;
681: case '\f':
682: sb.append ("\\f");
683: break;
684: case '\\':
685: sb.append ("\\\\");
686: break;
687: case '\t':
688: sb.append ("\\t");
689: break;
690: case '\r':
691: sb.append ("\\r");
692: break;
693: case '\n':
694: sb.append ("\\n");
695: break;
696: case '\"':
697: sb.append ("\\\"");
698: break;
699: default:
700: sb.append (s.charAt (i));
701: }
702: return sb.toString();
703: }
704:
705: private static String convertToCharInitializer (String s) {
706: StringBuffer sb = new StringBuffer ();
707: int i, k = s.length ();
708: for (i = 0; i < k; i++)
709: switch (s.charAt (i)) {
710: case '\b':
711: sb.append ("\\b");
712: break;
713: case '\f':
714: sb.append ("\\f");
715: break;
716: case '\\':
717: sb.append ("\\\\");
718: break;
719: case '\t':
720: sb.append ("\\t");
721: break;
722: case '\r':
723: sb.append ("\\r");
724: break;
725: case '\n':
726: sb.append ("\\n");
727: break;
728: case '\'':
729: sb.append ("\\\'");
730: break;
731: default:
732: sb.append (s.charAt (i));
733: }
734: return sb.toString();
735: }
736: */
737:
738: private class DebuggetStateListener extends Object implements
739: PropertyChangeListener {
740:
741: public void propertyChange(PropertyChangeEvent evt) {
742: if (JPDADebugger.PROP_STATE.equals(evt.getPropertyName())) {
743: Object newValue = evt.getNewValue();
744: if (newValue instanceof Integer
745: && JPDADebugger.STATE_RUNNING == ((Integer) newValue)
746: .intValue()) {
747: AbstractObjectVariable.this .refreshFields = true;
748: }
749: }
750: }
751:
752: }
753:
754: }
|