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 
  26 package jdk.nashorn.internal.objects;
  27 
  28 import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError;
  29 
  30 import jdk.nashorn.internal.objects.annotations.Attribute;
  31 import jdk.nashorn.internal.objects.annotations.Getter;
  32 import jdk.nashorn.internal.objects.annotations.ScriptClass;
  33 import jdk.nashorn.internal.runtime.JSType;
  34 import jdk.nashorn.internal.runtime.PropertyMap;
  35 import jdk.nashorn.internal.runtime.ScriptObject;
  36 import jdk.nashorn.internal.runtime.ScriptRuntime;
  37 import jdk.nashorn.internal.runtime.arrays.ArrayData;
  38 
  39 @ScriptClass("ArrayBufferView")
  40 abstract class ArrayBufferView extends ScriptObject {
  41 
  42     // initialized by nasgen
  43     private static PropertyMap $nasgenmap$;
  44 
  45     private ArrayBufferView(final NativeArrayBuffer buffer, final int byteOffset, final int elementLength, final Global global) {
  46         super($nasgenmap$);
  47         checkConstructorArgs(buffer, byteOffset, elementLength);
  48         this.setProto(getPrototype(global));
  49         this.setArray(factory().createArrayData(buffer, byteOffset, elementLength));
  50     }
  51 
  52     ArrayBufferView(final NativeArrayBuffer buffer, final int byteOffset, final int elementLength) {
  53         this(buffer, byteOffset, elementLength, Global.instance());
  54     }
  55 
  56     private void checkConstructorArgs(final NativeArrayBuffer buffer, final int byteOffset, final int elementLength) {
  57         if (byteOffset < 0 || elementLength < 0) {
  58             throw new RuntimeException("byteOffset or length must not be negative");
  59         }
  60         if (byteOffset + elementLength * bytesPerElement() > buffer.getByteLength()) {
  61             throw new RuntimeException("byteOffset + byteLength out of range");
  62         }
  63         if (byteOffset % bytesPerElement() != 0) {
  64             throw new RuntimeException("byteOffset must be a multiple of the element size");
  65         }
  66     }
  67 
  68     private int bytesPerElement() {
  69         return factory().bytesPerElement;
  70     }
  71 
  72     @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE)
  73     public static Object buffer(final Object self) {
  74         return ((ArrayDataImpl)((ArrayBufferView)self).getArray()).buffer;
  75     }
  76 
  77     @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE)
  78     public static Object byteOffset(final Object self) {
  79         return ((ArrayDataImpl)((ArrayBufferView)self).getArray()).byteOffset;
  80     }
  81 
  82     @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE)
  83     public static Object byteLength(final Object self) {
  84         final ArrayBufferView view = (ArrayBufferView)self;
  85         return ((ArrayDataImpl)view.getArray()).elementLength * view.bytesPerElement();
  86     }
  87 
  88     @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE)
  89     public static Object length(final Object self) {
  90         return ((ArrayBufferView)self).elementLength();
  91     }
  92 
  93     @Override
  94     public final Object getLength() {
  95         return elementLength();
  96     }
  97 
  98     private int elementLength() {
  99         return ((ArrayDataImpl)getArray()).elementLength;
 100     }
 101 
 102     protected static abstract class ArrayDataImpl extends ArrayData {
 103         protected final NativeArrayBuffer buffer;
 104         protected final int byteOffset;
 105         private final int elementLength;
 106 
 107         protected ArrayDataImpl(final NativeArrayBuffer buffer, final int byteOffset, final int elementLength) {
 108             super(elementLength);
 109             this.buffer = buffer;
 110             this.byteOffset = byteOffset;
 111             this.elementLength = elementLength;
 112         }
 113 
 114         @Override
 115         public ArrayData copy() {
 116             throw new UnsupportedOperationException();   // Not used for ArrayBuffers
 117         }
 118 
 119         @Override
 120         public Object[] asObjectArray() {
 121             final Object[] array = new Object[elementLength];
 122             for (int i = 0; i < elementLength; i++) {
 123                 array[i] = getObjectImpl(i);
 124             }
 125             return array;
 126         }
 127 
 128         @Override
 129         public ArrayData ensure(final long safeIndex) {
 130             return this;
 131         }
 132 
 133         @Override
 134         public void setLength(final long length) {
 135             //empty?
 136             //TODO is this right?
 137         }
 138 
 139         @Override
 140         public ArrayData shrink(final long newLength) {
 141             return this;
 142         }
 143 
 144         @Override
 145         public ArrayData set(final int index, final Object value, final boolean strict) {
 146             if (has(index)) {
 147                 setImpl(index, value);
 148             }
 149             return this;
 150         }
 151 
 152         @Override
 153         public ArrayData set(final int index, final int value, final boolean strict) {
 154             if (has(index)) {
 155                 setImpl(index, value);
 156             }
 157             return this;
 158         }
 159 
 160         @Override
 161         public ArrayData set(final int index, final long value, final boolean strict) {
 162             if (has(index)) {
 163                 setImpl(index, value);
 164             }
 165             return this;
 166         }
 167 
 168         @Override
 169         public ArrayData set(final int index, final double value, final boolean strict) {
 170             if (has(index)) {
 171                 setImpl(index, value);
 172             }
 173             return this;
 174         }
 175 
 176         @Override
 177         public int getInt(final int index) {
 178             return getIntImpl(index);
 179         }
 180 
 181         @Override
 182         public long getLong(final int index) {
 183             return getLongImpl(index);
 184         }
 185 
 186         @Override
 187         public double getDouble(final int index) {
 188             return getDoubleImpl(index);
 189         }
 190 
 191         @Override
 192         public Object getObject(final int index) {
 193             return getObjectImpl(index);
 194         }
 195 
 196         @Override
 197         public boolean has(final int index) {
 198             return index >= 0 && index < elementLength;
 199         }
 200 
 201         @Override
 202         public boolean canDelete(final int index, final boolean strict) {
 203             return false;
 204         }
 205 
 206         @Override
 207         public boolean canDelete(final long fromIndex, final long toIndex, final boolean strict) {
 208             return false;
 209         }
 210 
 211         @Override
 212         public ArrayData delete(final int index) {
 213             return this;
 214         }
 215 
 216         @Override
 217         public ArrayData delete(final long fromIndex, final long toIndex) {
 218             return this;
 219         }
 220 
 221         @Override
 222         protected ArrayData convert(final Class<?> type) {
 223             return this;
 224         }
 225 
 226         @Override
 227         public void shiftLeft(final int by) {
 228             throw new UnsupportedOperationException();
 229         }
 230 
 231         @Override
 232         public ArrayData shiftRight(final int by) {
 233             throw new UnsupportedOperationException();
 234         }
 235 
 236         @Override
 237         public Object pop() {
 238             throw new UnsupportedOperationException();
 239         }
 240 
 241         @Override
 242         public ArrayData slice(final long from, final long to) {
 243             throw new UnsupportedOperationException();
 244         }
 245 
 246         protected abstract int getIntImpl(int key);
 247 
 248         protected long getLongImpl(final int key) {
 249             return getIntImpl(key);
 250         }
 251 
 252         protected double getDoubleImpl(final int key) {
 253             return getIntImpl(key);
 254         }
 255 
 256         protected Object getObjectImpl(final int key) {
 257             return getIntImpl(key);
 258         }
 259 
 260         protected abstract void setImpl(int key, int value);
 261 
 262         protected void setImpl(final int key, final long value) {
 263             setImpl(key, (int)value);
 264         }
 265 
 266         protected void setImpl(final int key, final double value) {
 267             setImpl(key, JSType.toInt32(value));
 268         }
 269 
 270         protected void setImpl(final int key, final Object value) {
 271             setImpl(key, JSType.toInt32(value));
 272         }
 273 
 274         protected abstract int byteIndex(int index);
 275     }
 276 
 277     protected static abstract class Factory {
 278         final int bytesPerElement;
 279         final int maxElementLength;
 280 
 281         public Factory(final int bytesPerElement) {
 282             this.bytesPerElement = bytesPerElement;
 283             this.maxElementLength = Integer.MAX_VALUE / bytesPerElement;
 284         }
 285 
 286         public final ArrayBufferView construct(final int elementLength) {
 287             if(elementLength > maxElementLength) {
 288                 throw rangeError("inappropriate.array.buffer.length", JSType.toString(elementLength));
 289             }
 290             return construct(new NativeArrayBuffer(elementLength * bytesPerElement), 0, elementLength);
 291         }
 292 
 293         public abstract ArrayBufferView construct(NativeArrayBuffer buffer, int byteOffset, int elementLength);
 294 
 295         public abstract ArrayData createArrayData(NativeArrayBuffer buffer, int byteOffset, int elementLength);
 296     }
 297 
 298     protected abstract Factory factory();
 299 
 300     protected abstract ScriptObject getPrototype(final Global global);
 301 
 302     protected boolean isFloatArray() {
 303         return false;
 304     }
 305 
 306     protected static ArrayBufferView constructorImpl(final Object[] args, final Factory factory) {
 307         final Object arg0 = args.length != 0 ? args[0] : 0;
 308         final ArrayBufferView dst;
 309         final int length;
 310         if (arg0 instanceof NativeArrayBuffer) {
 311             // Constructor(ArrayBuffer buffer, optional unsigned long byteOffset, optional unsigned long length)
 312             final NativeArrayBuffer buffer = (NativeArrayBuffer) arg0;
 313             final int byteOffset = args.length > 1 ? JSType.toInt32(args[1]) : 0;
 314             if (args.length > 2) {
 315                 length = JSType.toInt32(args[2]);
 316             } else {
 317                 if ((buffer.getByteLength() - byteOffset) % factory.bytesPerElement != 0) {
 318                     throw new RuntimeException("buffer.byteLength - byteOffset must be a multiple of the element size");
 319                 }
 320                 length = (buffer.getByteLength() - byteOffset) / factory.bytesPerElement;
 321             }
 322             return factory.construct(buffer, byteOffset, length);
 323         } else if (arg0 instanceof ArrayBufferView) {
 324             // Constructor(TypedArray array)
 325             length = ((ArrayBufferView)arg0).elementLength();
 326             dst = factory.construct(length);
 327         } else if (arg0 instanceof NativeArray) {
 328             // Constructor(type[] array)
 329             length = lengthToInt(((NativeArray) arg0).getArray().length());
 330             dst = factory.construct(length);
 331         } else {
 332             // Constructor(unsigned long length)
 333             length = lengthToInt(JSType.toInt64(arg0));
 334             return factory.construct(length);
 335         }
 336 
 337         copyElements(dst, length, (ScriptObject)arg0, 0);
 338         return dst;
 339     }
 340 
 341     protected static Object setImpl(final Object self, final Object array, final Object offset0) {
 342         final ArrayBufferView dest = ((ArrayBufferView)self);
 343         final int length;
 344         if (array instanceof ArrayBufferView) {
 345             // void set(TypedArray array, optional unsigned long offset)
 346             length = ((ArrayBufferView)array).elementLength();
 347         } else if (array instanceof NativeArray) {
 348             // void set(type[] array, optional unsigned long offset)
 349             length = (int) (((NativeArray) array).getArray().length() & 0x7fff_ffff);
 350         } else {
 351             throw new RuntimeException("argument is not of array type");
 352         }
 353 
 354         final ScriptObject source = (ScriptObject) array;
 355         final int offset = JSType.toInt32(offset0); // default=0
 356 
 357         if (dest.elementLength() < length + offset || offset < 0) {
 358             throw new RuntimeException("offset or array length out of bounds");
 359         }
 360 
 361         copyElements(dest, length, source, offset);
 362 
 363         return ScriptRuntime.UNDEFINED;
 364     }
 365 
 366     private static void copyElements(final ArrayBufferView dest, final int length, final ScriptObject source, final int offset) {
 367         if (!dest.isFloatArray()) {
 368             for (int i = 0, j = offset; i < length; i++, j++) {
 369                 dest.set(j, source.getInt(i), false);
 370             }
 371         } else {
 372             for (int i = 0, j = offset; i < length; i++, j++) {
 373                 dest.set(j, source.getDouble(i), false);
 374             }
 375         }
 376     }
 377 
 378     private static int lengthToInt(final long length) {
 379         if (length > Integer.MAX_VALUE || length < 0) {
 380             throw rangeError("inappropriate.array.buffer.length", JSType.toString(length));
 381         }
 382         return (int) (length & Integer.MAX_VALUE);
 383     }
 384 
 385     protected static Object subarrayImpl(final Object self, final Object begin0, final Object end0) {
 386         final ArrayBufferView arrayView = ((ArrayBufferView)self);
 387         final int elementLength = arrayView.elementLength();
 388         final int begin = NativeArrayBuffer.adjustIndex(JSType.toInt32(begin0), elementLength);
 389         final int end = NativeArrayBuffer.adjustIndex(end0 != ScriptRuntime.UNDEFINED ? JSType.toInt32(end0) : elementLength, elementLength);
 390         final ArrayDataImpl arrayData = (ArrayDataImpl)arrayView.getArray();
 391         return arrayView.factory().construct(arrayData.buffer, arrayData.byteIndex(begin), Math.max(end - begin, 0));
 392     }
 393 }