ArrayBufferView.java revision 1423:c13179703f65
1/* 2 * Copyright (c) 2010, 2013, 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 jdk.nashorn.internal.objects; 27 28import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError; 29import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 30import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; 31import java.nio.ByteBuffer; 32import java.nio.ByteOrder; 33import jdk.internal.dynalink.CallSiteDescriptor; 34import jdk.internal.dynalink.linker.GuardedInvocation; 35import jdk.internal.dynalink.linker.LinkRequest; 36import jdk.nashorn.internal.objects.annotations.Attribute; 37import jdk.nashorn.internal.objects.annotations.Getter; 38import jdk.nashorn.internal.objects.annotations.ScriptClass; 39import jdk.nashorn.internal.runtime.JSType; 40import jdk.nashorn.internal.runtime.PropertyMap; 41import jdk.nashorn.internal.runtime.ScriptObject; 42import jdk.nashorn.internal.runtime.ScriptRuntime; 43import jdk.nashorn.internal.runtime.arrays.ArrayData; 44import jdk.nashorn.internal.runtime.arrays.TypedArrayData; 45 46/** 47 * ArrayBufferView, es6 class or TypedArray implementation 48 */ 49@ScriptClass("ArrayBufferView") 50public abstract class ArrayBufferView extends ScriptObject { 51 private final NativeArrayBuffer buffer; 52 private final int byteOffset; 53 54 // initialized by nasgen 55 private static PropertyMap $nasgenmap$; 56 57 private ArrayBufferView(final NativeArrayBuffer buffer, final int byteOffset, final int elementLength, final Global global) { 58 super($nasgenmap$); 59 60 final int bytesPerElement = bytesPerElement(); 61 62 checkConstructorArgs(buffer.getByteLength(), bytesPerElement, byteOffset, elementLength); 63 setProto(getPrototype(global)); 64 65 this.buffer = buffer; 66 this.byteOffset = byteOffset; 67 68 assert byteOffset % bytesPerElement == 0; 69 final int start = byteOffset / bytesPerElement; 70 final ByteBuffer newNioBuffer = buffer.getNioBuffer().duplicate().order(ByteOrder.nativeOrder()); 71 final ArrayData data = factory().createArrayData(newNioBuffer, start, start + elementLength); 72 73 setArray(data); 74 } 75 76 /** 77 * Constructor 78 * 79 * @param buffer underlying NativeArrayBuffer 80 * @param byteOffset byte offset for buffer 81 * @param elementLength element length in bytes 82 */ 83 protected ArrayBufferView(final NativeArrayBuffer buffer, final int byteOffset, final int elementLength) { 84 this(buffer, byteOffset, elementLength, Global.instance()); 85 } 86 87 private static void checkConstructorArgs(final int byteLength, final int bytesPerElement, final int byteOffset, final int elementLength) { 88 if (byteOffset < 0 || elementLength < 0) { 89 throw new RuntimeException("byteOffset or length must not be negative, byteOffset=" + byteOffset + ", elementLength=" + elementLength + ", bytesPerElement=" + bytesPerElement); 90 } else if (byteOffset + elementLength * bytesPerElement > byteLength) { 91 throw new RuntimeException("byteOffset + byteLength out of range, byteOffset=" + byteOffset + ", elementLength=" + elementLength + ", bytesPerElement=" + bytesPerElement); 92 } else if (byteOffset % bytesPerElement != 0) { 93 throw new RuntimeException("byteOffset must be a multiple of the element size, byteOffset=" + byteOffset + " bytesPerElement=" + bytesPerElement); 94 } 95 } 96 97 private int bytesPerElement() { 98 return factory().bytesPerElement; 99 } 100 101 /** 102 * Buffer getter as per spec 103 * @param self ArrayBufferView instance 104 * @return buffer 105 */ 106 @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE) 107 public static Object buffer(final Object self) { 108 return ((ArrayBufferView)self).buffer; 109 } 110 111 /** 112 * Buffer offset getter as per spec 113 * @param self ArrayBufferView instance 114 * @return buffer offset 115 */ 116 @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE) 117 public static int byteOffset(final Object self) { 118 return ((ArrayBufferView)self).byteOffset; 119 } 120 121 /** 122 * Byte length getter as per spec 123 * @param self ArrayBufferView instance 124 * @return array buffer view length in bytes 125 */ 126 @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE) 127 public static int byteLength(final Object self) { 128 final ArrayBufferView view = (ArrayBufferView)self; 129 return ((TypedArrayData<?>)view.getArray()).getElementLength() * view.bytesPerElement(); 130 } 131 132 /** 133 * Length getter as per spec 134 * @param self ArrayBufferView instance 135 * @return length in elements 136 */ 137 @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE) 138 public static int length(final Object self) { 139 return ((ArrayBufferView)self).elementLength(); 140 } 141 142 @Override 143 public final Object getLength() { 144 return elementLength(); 145 } 146 147 private int elementLength() { 148 return ((TypedArrayData<?>)getArray()).getElementLength(); 149 } 150 151 /** 152 * Factory class for byte ArrayBufferViews 153 */ 154 protected static abstract class Factory { 155 final int bytesPerElement; 156 final int maxElementLength; 157 158 /** 159 * Constructor 160 * 161 * @param bytesPerElement number of bytes per element for this buffer 162 */ 163 public Factory(final int bytesPerElement) { 164 this.bytesPerElement = bytesPerElement; 165 this.maxElementLength = Integer.MAX_VALUE / bytesPerElement; 166 } 167 168 /** 169 * Factory method 170 * 171 * @param elementLength number of elements 172 * @return new ArrayBufferView 173 */ 174 public final ArrayBufferView construct(final int elementLength) { 175 if (elementLength > maxElementLength) { 176 throw rangeError("inappropriate.array.buffer.length", JSType.toString(elementLength)); 177 } 178 return construct(new NativeArrayBuffer(elementLength * bytesPerElement), 0, elementLength); 179 } 180 181 /** 182 * Factory method 183 * 184 * @param buffer underlying buffer 185 * @param byteOffset byte offset 186 * @param elementLength number of elements 187 * 188 * @return new ArrayBufferView 189 */ 190 public abstract ArrayBufferView construct(final NativeArrayBuffer buffer, final int byteOffset, final int elementLength); 191 192 /** 193 * Factory method for array data 194 * 195 * @param nb underlying native buffer 196 * @param start start element 197 * @param end end element 198 * 199 * @return new array data 200 */ 201 public abstract TypedArrayData<?> createArrayData(final ByteBuffer nb, final int start, final int end); 202 203 /** 204 * Get the class name for this type of buffer 205 * 206 * @return class name 207 */ 208 public abstract String getClassName(); 209 } 210 211 /** 212 * Get the factor for this kind of buffer 213 * @return Factory 214 */ 215 protected abstract Factory factory(); 216 217 /** 218 * Get the prototype for this ArrayBufferView 219 * @param global global instance 220 * @return prototype 221 */ 222 protected abstract ScriptObject getPrototype(final Global global); 223 224 @Override 225 public final String getClassName() { 226 return factory().getClassName(); 227 } 228 229 /** 230 * Check if this array contains floats 231 * @return true if float array (or double) 232 */ 233 protected boolean isFloatArray() { 234 return false; 235 } 236 237 /** 238 * Inheritable constructor implementation 239 * 240 * @param newObj is this a new constructor 241 * @param args arguments 242 * @param factory factory 243 * 244 * @return new ArrayBufferView 245 */ 246 protected static ArrayBufferView constructorImpl(final boolean newObj, final Object[] args, final Factory factory) { 247 final Object arg0 = args.length != 0 ? args[0] : 0; 248 final ArrayBufferView dest; 249 final int length; 250 251 if (!newObj) { 252 throw typeError("constructor.requires.new", factory.getClassName()); 253 } 254 255 256 if (arg0 instanceof NativeArrayBuffer) { 257 // Constructor(ArrayBuffer buffer, optional unsigned long byteOffset, optional unsigned long length) 258 final NativeArrayBuffer buffer = (NativeArrayBuffer)arg0; 259 final int byteOffset = args.length > 1 ? JSType.toInt32(args[1]) : 0; 260 261 if (args.length > 2) { 262 length = JSType.toInt32(args[2]); 263 } else { 264 if ((buffer.getByteLength() - byteOffset) % factory.bytesPerElement != 0) { 265 throw new RuntimeException("buffer.byteLength - byteOffset must be a multiple of the element size"); 266 } 267 length = (buffer.getByteLength() - byteOffset) / factory.bytesPerElement; 268 } 269 270 return factory.construct(buffer, byteOffset, length); 271 } else if (arg0 instanceof ArrayBufferView) { 272 // Constructor(TypedArray array) 273 length = ((ArrayBufferView)arg0).elementLength(); 274 dest = factory.construct(length); 275 } else if (arg0 instanceof NativeArray) { 276 // Constructor(type[] array) 277 length = lengthToInt(((NativeArray) arg0).getArray().length()); 278 dest = factory.construct(length); 279 } else { 280 // Constructor(unsigned long length). Treating infinity as 0 is a special case for ArrayBufferView. 281 final double dlen = JSType.toNumber(arg0); 282 length = lengthToInt(Double.isInfinite(dlen) ? 0L : JSType.toLong(dlen)); 283 return factory.construct(length); 284 } 285 286 copyElements(dest, length, (ScriptObject)arg0, 0); 287 288 return dest; 289 } 290 291 /** 292 * Inheritable implementation of set, if no efficient implementation is available 293 * 294 * @param self ArrayBufferView instance 295 * @param array array 296 * @param offset0 array offset 297 * 298 * @return result of setter 299 */ 300 protected static Object setImpl(final Object self, final Object array, final Object offset0) { 301 final ArrayBufferView dest = (ArrayBufferView)self; 302 final int length; 303 if (array instanceof ArrayBufferView) { 304 // void set(TypedArray array, optional unsigned long offset) 305 length = ((ArrayBufferView)array).elementLength(); 306 } else if (array instanceof NativeArray) { 307 // void set(type[] array, optional unsigned long offset) 308 length = (int) (((NativeArray) array).getArray().length() & 0x7fff_ffff); 309 } else { 310 throw new RuntimeException("argument is not of array type"); 311 } 312 313 final ScriptObject source = (ScriptObject)array; 314 final int offset = JSType.toInt32(offset0); // default=0 315 316 if (dest.elementLength() < length + offset || offset < 0) { 317 throw new RuntimeException("offset or array length out of bounds"); 318 } 319 320 copyElements(dest, length, source, offset); 321 322 return ScriptRuntime.UNDEFINED; 323 } 324 325 private static void copyElements(final ArrayBufferView dest, final int length, final ScriptObject source, final int offset) { 326 if (!dest.isFloatArray()) { 327 for (int i = 0, j = offset; i < length; i++, j++) { 328 dest.set(j, source.getInt(i, INVALID_PROGRAM_POINT), 0); 329 } 330 } else { 331 for (int i = 0, j = offset; i < length; i++, j++) { 332 dest.set(j, source.getDouble(i, INVALID_PROGRAM_POINT), 0); 333 } 334 } 335 } 336 337 private static int lengthToInt(final long length) { 338 if (length > Integer.MAX_VALUE || length < 0) { 339 throw rangeError("inappropriate.array.buffer.length", JSType.toString(length)); 340 } 341 return (int)(length & Integer.MAX_VALUE); 342 } 343 344 /** 345 * Implementation of subarray if no efficient override exists 346 * 347 * @param self ArrayBufferView instance 348 * @param begin0 begin index 349 * @param end0 end index 350 * 351 * @return sub array 352 */ 353 protected static ScriptObject subarrayImpl(final Object self, final Object begin0, final Object end0) { 354 final ArrayBufferView arrayView = (ArrayBufferView)self; 355 final int byteOffset = arrayView.byteOffset; 356 final int bytesPerElement = arrayView.bytesPerElement(); 357 final int elementLength = arrayView.elementLength(); 358 final int begin = NativeArrayBuffer.adjustIndex(JSType.toInt32(begin0), elementLength); 359 final int end = NativeArrayBuffer.adjustIndex(end0 != ScriptRuntime.UNDEFINED ? JSType.toInt32(end0) : elementLength, elementLength); 360 final int length = Math.max(end - begin, 0); 361 362 assert byteOffset % bytesPerElement == 0; 363 364 //second is byteoffset 365 return arrayView.factory().construct(arrayView.buffer, begin * bytesPerElement + byteOffset, length); 366 } 367 368 @Override 369 protected GuardedInvocation findGetIndexMethod(final CallSiteDescriptor desc, final LinkRequest request) { 370 final GuardedInvocation inv = getArray().findFastGetIndexMethod(getArray().getClass(), desc, request); 371 if (inv != null) { 372 return inv; 373 } 374 return super.findGetIndexMethod(desc, request); 375 } 376 377 @Override 378 protected GuardedInvocation findSetIndexMethod(final CallSiteDescriptor desc, final LinkRequest request) { 379 final GuardedInvocation inv = getArray().findFastSetIndexMethod(getArray().getClass(), desc, request); 380 if (inv != null) { 381 return inv; 382 } 383 return super.findSetIndexMethod(desc, request); 384 } 385} 386