/******************************************************************************* * Copyright (c) 2000, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdi.internal; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.jdi.internal.jdwp.JdwpCommandPacket; import org.eclipse.jdi.internal.jdwp.JdwpID; import org.eclipse.jdi.internal.jdwp.JdwpObjectID; import org.eclipse.jdi.internal.jdwp.JdwpReplyPacket; import com.sun.jdi.ArrayReference; import com.sun.jdi.ClassNotLoadedException; import com.sun.jdi.InternalException; import com.sun.jdi.InvalidTypeException; import com.sun.jdi.Mirror; import com.sun.jdi.ObjectCollectedException; import com.sun.jdi.Type; import com.sun.jdi.Value; /** * this class implements the corresponding interfaces declared by the JDI * specification. See the com.sun.jdi package for more information. * */ public class ArrayReferenceImpl extends ObjectReferenceImpl implements ArrayReference { /** JDWP Tag. */ public static final byte tag = JdwpID.ARRAY_TAG; private int fLength = -1; /** * Creates new ArrayReferenceImpl. * @param vmImpl the VM * @param objectID the object ID */ public ArrayReferenceImpl(VirtualMachineImpl vmImpl, JdwpObjectID objectID) { super("ArrayReference", vmImpl, objectID); //$NON-NLS-1$ } /** * @returns tag. */ @Override public byte getTag() { return tag; } /** * Returns the {@link Value} from the given index * @param index the index * @return the {@link Value} at the given index * @throws IndexOutOfBoundsException if the index is outside the bounds of the array * @returns Returns an array component value. */ @Override public Value getValue(int index) throws IndexOutOfBoundsException { return getValues(index, 1).get(0); } /** * @return all of the components in this array. */ @Override public List<Value> getValues() { return getValues(0, -1); } /** * Gets all of the values starting at firstIndex and ending at firstIndex+length * * @param firstIndex the start * @param length the number of values to return * @return the list of {@link Value}s * @throws IndexOutOfBoundsException if the index is outside the bounds of the array * @returns Returns a range of array components. */ @Override public List<Value> getValues(int firstIndex, int length) throws IndexOutOfBoundsException { int arrayLength = length(); if (firstIndex < 0 || firstIndex >= arrayLength) { throw new IndexOutOfBoundsException( JDIMessages.ArrayReferenceImpl_Invalid_index_1); } if (length == -1) { // length == -1 means all elements to the end. length = arrayLength - firstIndex; } else if (length < -1) { throw new IndexOutOfBoundsException( JDIMessages.ArrayReferenceImpl_Invalid_number_of_value_to_get_from_array_1); } else if (firstIndex + length > arrayLength) { throw new IndexOutOfBoundsException( JDIMessages.ArrayReferenceImpl_Attempted_to_get_more_values_from_array_than_length_of_array_2); } // Note that this information should not be cached. initJdwpRequest(); try { ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); DataOutputStream outData = new DataOutputStream(outBytes); write(this, outData); // arrayObject writeInt(firstIndex, "firstIndex", outData); //$NON-NLS-1$ writeInt(length, "length", outData); //$NON-NLS-1$ JdwpReplyPacket replyPacket = requestVM( JdwpCommandPacket.AR_GET_VALUES, outBytes); switch (replyPacket.errorCode()) { case JdwpReplyPacket.INVALID_INDEX: throw new IndexOutOfBoundsException( JDIMessages.ArrayReferenceImpl_Invalid_index_of_array_reference_given_1); } defaultReplyErrorHandler(replyPacket.errorCode()); DataInputStream replyData = replyPacket.dataInStream(); /* * NOTE: The JDWP documentation is not clear on this: it turns out * that the following is received from the VM: - type tag; - length * of array; - values of elements. */ int type = readByte("type", JdwpID.tagMap(), replyData); //$NON-NLS-1$ int readLength = readInt("length", replyData); //$NON-NLS-1$ // See also ValueImpl. switch (type) { // Multidimensional array. case ArrayReferenceImpl.tag: // Object references. case ClassLoaderReferenceImpl.tag: case ClassObjectReferenceImpl.tag: case StringReferenceImpl.tag: case ObjectReferenceImpl.tag: case ThreadGroupReferenceImpl.tag: case ThreadReferenceImpl.tag: return readObjectSequence(readLength, replyData); // Primitive type. case BooleanValueImpl.tag: case ByteValueImpl.tag: case CharValueImpl.tag: case DoubleValueImpl.tag: case FloatValueImpl.tag: case IntegerValueImpl.tag: case LongValueImpl.tag: case ShortValueImpl.tag: return readPrimitiveSequence(readLength, type, replyData); case VoidValueImpl.tag: case 0: default: throw new InternalException( JDIMessages.ArrayReferenceImpl_Invalid_ArrayReference_Value_tag_encountered___2 + type); } } catch (IOException e) { defaultIOExceptionHandler(e); return null; } finally { handledJdwpRequest(); } } /** * Reads the given length of objects from the given stream * @param length the number of objects to read * @param in the stream to read from * @return the {@link List} of {@link ValueImpl}s * @throws IOException if the reading fails * @returns Returns sequence of object reference values. */ private List<Value> readObjectSequence(int length, DataInputStream in) throws IOException { List<Value> elements = new ArrayList<>(length); for (int i = 0; i < length; i++) { ValueImpl value = ObjectReferenceImpl .readObjectRefWithTag(this, in); elements.add(value); } return elements; } /** * @param length * the number of primitives to read * @param type * the type * @param in * the input stream * @return Returns sequence of values of primitive type. * @throws IOException * if reading from the stream encounters a problem */ private List<Value> readPrimitiveSequence(int length, int type, DataInputStream in) throws IOException { List<Value> elements = new ArrayList<>(length); for (int i = 0; i < length; i++) { ValueImpl value = ValueImpl.readWithoutTag(this, type, in); elements.add(value); } return elements; } /** * @return Returns the number of components in this array. */ @Override public int length() { if (fLength == -1) { initJdwpRequest(); try { JdwpReplyPacket replyPacket = requestVM( JdwpCommandPacket.AR_LENGTH, this); defaultReplyErrorHandler(replyPacket.errorCode()); DataInputStream replyData = replyPacket.dataInStream(); fLength = readInt("length", replyData); //$NON-NLS-1$ } catch (IOException e) { defaultIOExceptionHandler(e); return 0; } finally { handledJdwpRequest(); } } return fLength; } /** * Replaces an array component with another value. * * @param index * the index to set the value in * @param value * the new value * @throws InvalidTypeException * thrown if the types of the replacements do not match the * underlying type of the {@link ArrayReference} * @throws ClassNotLoadedException * thrown if the class type for the {@link ArrayReference} is * not loaded or has been GC'd * @see #setValues(int, List, int, int) */ @Override public void setValue(int index, Value value) throws InvalidTypeException, ClassNotLoadedException { ArrayList<Value> list = new ArrayList<>(1); list.add(value); setValues(index, list, 0, 1); } /** * Replaces all array components with other values. * * @param values * the new values to set in the array * @throws InvalidTypeException * thrown if the types of the replacements do not match the * underlying type of the {@link ArrayReference} * @throws ClassNotLoadedException * thrown if the class type for the {@link ArrayReference} is * not loaded or has been GC'd * @see #setValues(int, List, int, int) */ @Override public void setValues(List<? extends Value> values) throws InvalidTypeException, ClassNotLoadedException { setValues(0, values, 0, -1); } /** * Replaces a range of array components with other values. * * @param index * offset in this array to start replacing values at * @param values * replacement values * @param srcIndex * the first offset where values are copied from the given * replacement values * @param length * the number of values to replace in this array * @throws InvalidTypeException * thrown if the types of the replacements do not match the * underlying type of the {@link ArrayReference} * @throws ClassNotLoadedException * thrown if the class type for the {@link ArrayReference} is * not loaded or has been GC'd */ @Override public void setValues(int index, List<? extends Value> values, int srcIndex, int length) throws InvalidTypeException, ClassNotLoadedException { if (values == null || values.size() == 0) { // trying to set nothing should do no work return; } int valuesSize = values.size(); int arrayLength = length(); if (index < 0 || index >= arrayLength) { throw new IndexOutOfBoundsException( JDIMessages.ArrayReferenceImpl_Invalid_index_1); } if (srcIndex < 0 || srcIndex >= valuesSize) { throw new IndexOutOfBoundsException( JDIMessages.ArrayReferenceImpl_Invalid_srcIndex_2); } if (length < -1) { throw new IndexOutOfBoundsException( JDIMessages.ArrayReferenceImpl_Invalid_number_of_value_to_set_in_array_3); } else if (length == -1) { // length == -1 indicates as much values as possible. length = arrayLength - index; int lengthTmp = valuesSize - srcIndex; if (lengthTmp < length) { length = lengthTmp; } } else if (index + length > arrayLength) { throw new IndexOutOfBoundsException( JDIMessages.ArrayReferenceImpl_Attempted_to_set_more_values_in_array_than_length_of_array_3); } else if (srcIndex + length > valuesSize) { // Check if enough values are given. throw new IndexOutOfBoundsException( JDIMessages.ArrayReferenceImpl_Attempted_to_set_more_values_in_array_than_given_4); } // check and convert the values if needed. List<Value> checkedValues = checkValues( values.subList(srcIndex, srcIndex + length), ((ArrayTypeImpl) referenceType()).componentType()); // Note that this information should not be cached. initJdwpRequest(); try { ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); DataOutputStream outData = new DataOutputStream(outBytes); write(this, outData); writeInt(index, "index", outData); //$NON-NLS-1$ writeInt(length, "length", outData); //$NON-NLS-1$ Iterator<Value> iterValues = checkedValues.iterator(); while (iterValues.hasNext()) { ValueImpl value = (ValueImpl) iterValues.next(); if (value != null) { value.write(this, outData); } else { ValueImpl.writeNull(this, outData); } } JdwpReplyPacket replyPacket = requestVM( JdwpCommandPacket.AR_SET_VALUES, outBytes); switch (replyPacket.errorCode()) { case JdwpReplyPacket.TYPE_MISMATCH: throw new InvalidTypeException(); case JdwpReplyPacket.INVALID_CLASS: throw new ClassNotLoadedException(type().name()); } defaultReplyErrorHandler(replyPacket.errorCode()); } catch (IOException e) { defaultIOExceptionHandler(e); } finally { handledJdwpRequest(); } } /** * Check the type and the VM of the values. If the given type is a primitive * type, the values may be converted to match this type. * * @param values * the value(s) to check * @param type * the type to compare the values to * @return the list of values converted to the given type * @throws InvalidTypeException * if the underlying type of an object in the list is not * compatible * * @see ValueImpl#checkValue(Value, Type, VirtualMachineImpl) */ private List<Value> checkValues(List<? extends Value> values, Type type) throws InvalidTypeException { List<Value> checkedValues = new ArrayList<>(values.size()); Iterator<? extends Value> iterValues = values.iterator(); while (iterValues.hasNext()) { checkedValues.add(ValueImpl.checkValue(iterValues.next(), type, virtualMachineImpl())); } return checkedValues; } /* * (non-Javadoc) * * @see org.eclipse.jdi.internal.ObjectReferenceImpl#toString() */ @Override public String toString() { try { StringBuffer buf = new StringBuffer(type().name()); // Insert length of string between (last) square braces. buf.insert(buf.length() - 1, length()); // Append space and idString. buf.append(' '); buf.append(idString()); return buf.toString(); } catch (ObjectCollectedException e) { return JDIMessages.ArrayReferenceImpl__Garbage_Collected__ArrayReference_5 + "[" + length() + "] " + idString(); //$NON-NLS-1$ //$NON-NLS-2$ } catch (Exception e) { return fDescription; } } /** * Reads JDWP representation and returns new instance. * * @param target * the target {@link Mirror} object * @param in * the input stream to read from * @return Reads JDWP representation and returns new instance. * @throws IOException * if there is a problem reading from the stream */ public static ArrayReferenceImpl read(MirrorImpl target, DataInputStream in) throws IOException { VirtualMachineImpl vmImpl = target.virtualMachineImpl(); JdwpObjectID ID = new JdwpObjectID(vmImpl); ID.read(in); if (target.fVerboseWriter != null) { target.fVerboseWriter.println("arrayReference", ID.value()); //$NON-NLS-1$ } if (ID.isNull()) { return null; } ArrayReferenceImpl mirror = new ArrayReferenceImpl(vmImpl, ID); return mirror; } }