| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * 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.isEmpty()) { |
| // 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 { |
| StringBuilder buf = new StringBuilder(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; |
| } |
| } |