ArrayBufferView.java revision 1036:f0b5e3900a10
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;
31
32import java.nio.ByteBuffer;
33import java.nio.ByteOrder;
34
35import jdk.internal.dynalink.CallSiteDescriptor;
36import jdk.internal.dynalink.linker.GuardedInvocation;
37import jdk.internal.dynalink.linker.LinkRequest;
38import jdk.nashorn.internal.objects.annotations.Attribute;
39import jdk.nashorn.internal.objects.annotations.Getter;
40import jdk.nashorn.internal.objects.annotations.ScriptClass;
41import jdk.nashorn.internal.runtime.JSType;
42import jdk.nashorn.internal.runtime.PropertyMap;
43import jdk.nashorn.internal.runtime.ScriptObject;
44import jdk.nashorn.internal.runtime.ScriptRuntime;
45import jdk.nashorn.internal.runtime.arrays.ArrayData;
46import jdk.nashorn.internal.runtime.arrays.TypedArrayData;
47
48@ScriptClass("ArrayBufferView")
49abstract class ArrayBufferView extends ScriptObject {
50    private final NativeArrayBuffer buffer;
51    private final int byteOffset;
52
53    // initialized by nasgen
54    private static PropertyMap $nasgenmap$;
55
56    private ArrayBufferView(final NativeArrayBuffer buffer, final int byteOffset, final int elementLength, final Global global) {
57        super($nasgenmap$);
58
59        final int bytesPerElement = bytesPerElement();
60
61        checkConstructorArgs(buffer.getByteLength(), bytesPerElement, byteOffset, elementLength);
62        setProto(getPrototype(global));
63
64        this.buffer     = buffer;
65        this.byteOffset = byteOffset;
66
67        assert byteOffset % bytesPerElement == 0;
68        final int start = byteOffset / bytesPerElement;
69        final ByteBuffer newNioBuffer = buffer.getNioBuffer().duplicate().order(ByteOrder.nativeOrder());
70        final ArrayData  data         = factory().createArrayData(newNioBuffer, start, start + elementLength);
71
72        setArray(data);
73    }
74
75    protected ArrayBufferView(final NativeArrayBuffer buffer, final int byteOffset, final int elementLength) {
76        this(buffer, byteOffset, elementLength, Global.instance());
77    }
78
79    private static void checkConstructorArgs(final int byteLength, final int bytesPerElement, final int byteOffset, final int elementLength) {
80        if (byteOffset < 0 || elementLength < 0) {
81            throw new RuntimeException("byteOffset or length must not be negative, byteOffset=" + byteOffset + ", elementLength=" + elementLength + ", bytesPerElement=" + bytesPerElement);
82        } else if (byteOffset + elementLength * bytesPerElement > byteLength) {
83            throw new RuntimeException("byteOffset + byteLength out of range, byteOffset=" + byteOffset + ", elementLength=" + elementLength + ", bytesPerElement=" + bytesPerElement);
84        } else if (byteOffset % bytesPerElement != 0) {
85            throw new RuntimeException("byteOffset must be a multiple of the element size, byteOffset=" + byteOffset + " bytesPerElement=" + bytesPerElement);
86        }
87    }
88
89    private int bytesPerElement() {
90        return factory().bytesPerElement;
91    }
92
93    @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE)
94    public static Object buffer(final Object self) {
95        return ((ArrayBufferView)self).buffer;
96    }
97
98    @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE)
99    public static int byteOffset(final Object self) {
100        return ((ArrayBufferView)self).byteOffset;
101    }
102
103    @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE)
104    public static int byteLength(final Object self) {
105        final ArrayBufferView view = (ArrayBufferView)self;
106        return ((TypedArrayData<?>)view.getArray()).getElementLength() * view.bytesPerElement();
107    }
108
109    @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE)
110    public static int length(final Object self) {
111        return ((ArrayBufferView)self).elementLength();
112    }
113
114    @Override
115    public final Object getLength() {
116        return elementLength();
117    }
118
119    private int elementLength() {
120        return ((TypedArrayData<?>)getArray()).getElementLength();
121    }
122
123    protected static abstract class Factory {
124        final int bytesPerElement;
125        final int maxElementLength;
126
127        public Factory(final int bytesPerElement) {
128            this.bytesPerElement  = bytesPerElement;
129            this.maxElementLength = Integer.MAX_VALUE / bytesPerElement;
130        }
131
132        public final ArrayBufferView construct(final int elementLength) {
133            if (elementLength > maxElementLength) {
134                throw rangeError("inappropriate.array.buffer.length", JSType.toString(elementLength));
135            }
136            return construct(new NativeArrayBuffer(elementLength * bytesPerElement), 0, elementLength);
137        }
138
139        public abstract ArrayBufferView construct(NativeArrayBuffer buffer, int byteOffset, int elementLength);
140
141        public abstract TypedArrayData<?> createArrayData(ByteBuffer nb, int start, int end);
142
143        public abstract String getClassName();
144    }
145
146    protected abstract Factory factory();
147
148    protected abstract ScriptObject getPrototype(final Global global);
149
150    @Override
151    public final String getClassName() {
152        return factory().getClassName();
153    }
154
155    protected boolean isFloatArray() {
156        return false;
157    }
158
159    protected static ArrayBufferView constructorImpl(final boolean newObj, final Object[] args, final Factory factory) {
160        final Object          arg0 = args.length != 0 ? args[0] : 0;
161        final ArrayBufferView dest;
162        final int             length;
163
164        if (!newObj) {
165            throw typeError("constructor.requires.new", factory.getClassName());
166        }
167
168
169        if (arg0 instanceof NativeArrayBuffer) {
170            // Constructor(ArrayBuffer buffer, optional unsigned long byteOffset, optional unsigned long length)
171            final NativeArrayBuffer buffer     = (NativeArrayBuffer)arg0;
172            final int               byteOffset = args.length > 1 ? JSType.toInt32(args[1]) : 0;
173
174            if (args.length > 2) {
175                length = JSType.toInt32(args[2]);
176            } else {
177                if ((buffer.getByteLength() - byteOffset) % factory.bytesPerElement != 0) {
178                    throw new RuntimeException("buffer.byteLength - byteOffset must be a multiple of the element size");
179                }
180                length = (buffer.getByteLength() - byteOffset) / factory.bytesPerElement;
181            }
182
183            return factory.construct(buffer, byteOffset, length);
184        } else if (arg0 instanceof ArrayBufferView) {
185            // Constructor(TypedArray array)
186            length = ((ArrayBufferView)arg0).elementLength();
187            dest   = factory.construct(length);
188        } else if (arg0 instanceof NativeArray) {
189            // Constructor(type[] array)
190            length = lengthToInt(((NativeArray) arg0).getArray().length());
191            dest   = factory.construct(length);
192        } else {
193            // Constructor(unsigned long length). Treating infinity as 0 is a special case for ArrayBufferView.
194            final double dlen = JSType.toNumber(arg0);
195            length = lengthToInt(Double.isInfinite(dlen) ? 0L : JSType.toLong(dlen));
196            return factory.construct(length);
197        }
198
199        copyElements(dest, length, (ScriptObject)arg0, 0);
200
201        return dest;
202    }
203
204    protected static Object setImpl(final Object self, final Object array, final Object offset0) {
205        final ArrayBufferView dest = (ArrayBufferView)self;
206        final int length;
207        if (array instanceof ArrayBufferView) {
208            // void set(TypedArray array, optional unsigned long offset)
209            length = ((ArrayBufferView)array).elementLength();
210        } else if (array instanceof NativeArray) {
211            // void set(type[] array, optional unsigned long offset)
212            length = (int) (((NativeArray) array).getArray().length() & 0x7fff_ffff);
213        } else {
214            throw new RuntimeException("argument is not of array type");
215        }
216
217        final ScriptObject source = (ScriptObject)array;
218        final int offset = JSType.toInt32(offset0); // default=0
219
220        if (dest.elementLength() < length + offset || offset < 0) {
221            throw new RuntimeException("offset or array length out of bounds");
222        }
223
224        copyElements(dest, length, source, offset);
225
226        return ScriptRuntime.UNDEFINED;
227    }
228
229    private static void copyElements(final ArrayBufferView dest, final int length, final ScriptObject source, final int offset) {
230        if (!dest.isFloatArray()) {
231            for (int i = 0, j = offset; i < length; i++, j++) {
232                dest.set(j, source.getInt(i, INVALID_PROGRAM_POINT), 0);
233            }
234        } else {
235            for (int i = 0, j = offset; i < length; i++, j++) {
236                dest.set(j, source.getDouble(i, INVALID_PROGRAM_POINT), 0);
237            }
238        }
239    }
240
241    private static int lengthToInt(final long length) {
242        if (length > Integer.MAX_VALUE || length < 0) {
243            throw rangeError("inappropriate.array.buffer.length", JSType.toString(length));
244        }
245        return (int)(length & Integer.MAX_VALUE);
246    }
247
248    protected static ScriptObject subarrayImpl(final Object self, final Object begin0, final Object end0) {
249        final ArrayBufferView arrayView       = (ArrayBufferView)self;
250        final int             byteOffset      = arrayView.byteOffset;
251        final int             bytesPerElement = arrayView.bytesPerElement();
252        final int             elementLength   = arrayView.elementLength();
253        final int             begin           = NativeArrayBuffer.adjustIndex(JSType.toInt32(begin0), elementLength);
254        final int             end             = NativeArrayBuffer.adjustIndex(end0 != ScriptRuntime.UNDEFINED ? JSType.toInt32(end0) : elementLength, elementLength);
255        final int             length          = Math.max(end - begin, 0);
256
257        assert byteOffset % bytesPerElement == 0;
258
259        //second is byteoffset
260        return arrayView.factory().construct(arrayView.buffer, begin * bytesPerElement + byteOffset, length);
261    }
262
263    @Override
264    protected GuardedInvocation findGetIndexMethod(final CallSiteDescriptor desc, final LinkRequest request) {
265        final GuardedInvocation inv = getArray().findFastGetIndexMethod(getArray().getClass(), desc, request);
266        if (inv != null) {
267            return inv;
268        }
269        return super.findGetIndexMethod(desc, request);
270    }
271
272    @Override
273    protected GuardedInvocation findSetIndexMethod(final CallSiteDescriptor desc, final LinkRequest request) {
274        final GuardedInvocation inv = getArray().findFastSetIndexMethod(getArray().getClass(), desc, request);
275        if (inv != null) {
276            return inv;
277        }
278        return super.findSetIndexMethod(desc, request);
279    }
280}
281