1 /*
  2  * Copyright (c) 1997, 2023, 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.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 
 25 /*
 26  * The Original Code is HAT. The Initial Developer of the
 27  * Original Code is Bill Foote, with contributions from others
 28  * at JavaSoft/Sun.
 29  */
 30 
 31 package jdk.test.lib.hprof.model;
 32 
 33 import java.io.IOException;
 34 import java.util.Objects;
 35 
 36 /**
 37  * An array of values, that is, an array of ints, boolean, floats or the like.
 38  *
 39  * @author      Bill Foote
 40  */
 41 public class JavaValueArray extends JavaLazyReadObject
 42                 /*imports*/ implements ArrayTypeCodes {
 43 
 44     private static String arrayTypeName(byte sig) {
 45         switch (sig) {
 46             case 'B':
 47                 return "byte[]";
 48             case 'Z':
 49                 return "boolean[]";
 50             case 'C':
 51                 return "char[]";
 52             case 'S':
 53                 return "short[]";
 54             case 'I':
 55                 return "int[]";
 56             case 'F':
 57                 return "float[]";
 58             case 'J':
 59                 return "long[]";
 60             case 'D':
 61                 return "double[]";
 62             default:
 63                 throw new RuntimeException("invalid array element sig: " + sig);
 64         }
 65     }
 66 
 67     private static int elementSize(byte type) {
 68         switch (type) {
 69             case 'B':
 70             case 'Z':
 71                 return 1;
 72             case 'C':
 73             case 'S':
 74                 return 2;
 75             case 'I':
 76             case 'F':
 77                 return 4;
 78             case 'J':
 79             case 'D':
 80                 return 8;
 81             default:
 82                 throw new RuntimeException("invalid array element type: " + type);
 83         }
 84     }
 85 
 86     /*
 87      * Java primitive array record (HPROF_GC_PRIM_ARRAY_DUMP) looks
 88      * as below:
 89      *
 90      *    object ID
 91      *    stack trace serial number (int)
 92      *    number of elements (int)
 93      *    element type (byte)
 94      *    array data
 95      */
 96     @Override
 97     protected final long readValueLength() throws IOException {
 98         long offset = getOffset() + idSize() + 4;
 99         // length of the array in elements
100         long len = buf().getInt(offset);
101         // byte length of array
102         return len * elementSize(getElementType());
103     }
104 
105     private long dataStartOffset() {
106         return getOffset() + idSize() + 4 + 4 + 1;
107     }
108 
109 
110     @Override
111     protected final JavaThing[] readValue() throws IOException {
112         int len = getLength();
113         long offset = dataStartOffset();
114 
115         JavaThing[] res = new JavaThing[len];
116         synchronized (buf()) {
117             switch (getElementType()) {
118                 case 'Z': {
119                               for (int i = 0; i < len; i++) {
120                                   res[i] = new JavaBoolean(booleanAt(offset));
121                                   offset += 1;
122                               }
123                               return res;
124                 }
125                 case 'B': {
126                               for (int i = 0; i < len; i++) {
127                                   res[i] = new JavaByte(byteAt(offset));
128                                   offset += 1;
129                               }
130                               return res;
131                 }
132                 case 'C': {
133                               for (int i = 0; i < len; i++) {
134                                   res[i] = new JavaChar(charAt(offset));
135                                   offset += 2;
136                               }
137                               return res;
138                 }
139                 case 'S': {
140                               for (int i = 0; i < len; i++) {
141                                   res[i] = new JavaShort(shortAt(offset));
142                                   offset += 2;
143                               }
144                               return res;
145                 }
146                 case 'I': {
147                               for (int i = 0; i < len; i++) {
148                                   res[i] = new JavaInt(intAt(offset));
149                                   offset += 4;
150                               }
151                               return res;
152                 }
153                 case 'J': {
154                               for (int i = 0; i < len; i++) {
155                                   res[i] = new JavaLong(longAt(offset));
156                                   offset += 8;
157                               }
158                               return res;
159                 }
160                 case 'F': {
161                               for (int i = 0; i < len; i++) {
162                                   res[i] = new JavaFloat(floatAt(offset));
163                                   offset += 4;
164                               }
165                               return res;
166                 }
167                 case 'D': {
168                               for (int i = 0; i < len; i++) {
169                                   res[i] = new JavaDouble(doubleAt(offset));
170                                   offset += 8;
171                               }
172                               return res;
173                 }
174                 default: {
175                              throw new RuntimeException("unknown primitive type?");
176                 }
177             }
178         }
179     }
180 
181     // JavaClass set only after resolve.
182     private JavaClass clazz;
183 
184     // This field contains elementSignature byte and
185     // divider to be used to calculate length. Note that
186     // length of content byte[] is not same as array length.
187     // Actual array length is (byte[].length / divider)
188     private int data;
189 
190     // First 8 bits of data is used for element signature
191     private static final int SIGNATURE_MASK = 0x0FF;
192 
193     // Next 8 bits of data is used for length divider
194     private static final int LENGTH_DIVIDER_MASK = 0x0FF00;
195 
196     // Number of bits to shift to get length divider
197     private static final int LENGTH_DIVIDER_SHIFT = 8;
198 
199     public JavaValueArray(byte elementSignature, long offset) {
200         super(offset);
201         this.data = (elementSignature & SIGNATURE_MASK);
202     }
203 
204     public JavaClass getClazz() {
205         return clazz;
206     }
207 
208     public void visitReferencedObjects(JavaHeapObjectVisitor v) {
209         super.visitReferencedObjects(v);
210     }
211 
212     public void resolve(Snapshot snapshot) {
213         if (clazz instanceof JavaClass) {
214             return;
215         }
216         byte elementSig = getElementType();
217         clazz = snapshot.findClass(arrayTypeName(elementSig));
218         if (clazz == null) {
219             clazz = snapshot.getArrayClass("" + ((char) elementSig));
220         }
221         getClazz().addInstance(this);
222         super.resolve(snapshot);
223     }
224 
225     public int getLength() {
226         int divider = (data & LENGTH_DIVIDER_MASK) >>> LENGTH_DIVIDER_SHIFT;
227         if (divider == 0) {
228             byte elementSignature = getElementType();
229             switch (elementSignature) {
230             case 'B':
231             case 'Z':
232                 divider = 1;
233                 break;
234             case 'C':
235             case 'S':
236                 divider = 2;
237                 break;
238             case 'I':
239             case 'F':
240                 divider = 4;
241                 break;
242             case 'J':
243             case 'D':
244                 divider = 8;
245                 break;
246             default:
247                 throw new RuntimeException("unknown primitive type: " +
248                                 elementSignature);
249             }
250             data |= (divider << LENGTH_DIVIDER_SHIFT);
251         }
252         return (int)(getValueLength() / divider);
253     }
254 
255     public JavaThing[] getElements() {
256         return getValue();
257     }
258 
259     public byte getElementType() {
260         return (byte) (data & SIGNATURE_MASK);
261     }
262 
263     private void checkIndex(int index) {
264         Objects.checkIndex(index, getLength());
265     }
266 
267     private void requireType(char type) {
268         if (getElementType() != type) {
269             throw new RuntimeException("not of type : " + type);
270         }
271     }
272 
273     public String valueString() {
274         return valueString(true);
275     }
276 
277     public String valueString(boolean bigLimit) {
278         // Char arrays deserve special treatment
279         StringBuilder result;
280         JavaThing[] things = getValue();
281         byte elementSignature = getElementType();
282         if (elementSignature == 'C')  {
283             result = new StringBuilder();
284             for (int i = 0; i < things.length; i++) {
285                 result.append(things[i]);
286             }
287         } else {
288             int limit = 8;
289             if (bigLimit) {
290                 limit = 1000;
291             }
292             result = new StringBuilder("{");
293             for (int i = 0; i < things.length; i++) {
294                 if (i > 0) {
295                     result.append(", ");
296                 }
297                 if (i >= limit) {
298                     result.append("... ");
299                     break;
300                 }
301                 switch (elementSignature) {
302                     case 'Z': {
303                         boolean val = ((JavaBoolean)things[i]).value;
304                         if (val) {
305                             result.append("true");
306                         } else {
307                             result.append("false");
308                         }
309                         break;
310                     }
311                     case 'B': {
312                         byte val = ((JavaByte)things[i]).value;
313                         result.append("0x").append(Integer.toString(val, 16));
314                         break;
315                     }
316                     case 'S': {
317                         short val = ((JavaShort)things[i]).value;
318                         result.append(val);
319                         break;
320                     }
321                     case 'I': {
322                         int val = ((JavaInt)things[i]).value;
323                         result.append(val);
324                         break;
325                     }
326                     case 'J': {         // long
327                         long val = ((JavaLong)things[i]).value;
328                         result.append(val);
329                         break;
330                     }
331                     case 'F': {
332                         float val = ((JavaFloat)things[i]).value;
333                         result.append(val);
334                         break;
335                     }
336                     case 'D': {         // double
337                         double val = ((JavaDouble)things[i]).value;
338                         result.append(val);
339                         break;
340                     }
341                     default: {
342                         throw new RuntimeException("unknown primitive type?");
343                     }
344                 }
345             }
346             result.append('}');
347         }
348         return result.toString();
349     }
350 
351     // Tries to represent the value as string (used by JavaObject.toString).
352     public String valueAsString() {
353         if (getElementType() == 'B')  {
354             JavaThing[] things = getValue();
355             byte[] bytes = new byte[things.length];
356             for (int i = 0; i < things.length; i++) {
357                 bytes[i] = ((JavaByte)things[i]).value;
358             }
359             return new String(bytes);
360         }
361         // fallback
362         return valueString();
363     }
364 }