| /* |
| * Copyright 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.lang; |
| |
| import static javaemul.internal.InternalPreconditions.checkArrayType; |
| import static javaemul.internal.InternalPreconditions.checkNotNull; |
| |
| import com.google.gwt.core.client.JavaScriptObject; |
| |
| import javaemul.internal.annotations.DoNotInline; |
| import javaemul.internal.annotations.HasNoSideEffects; |
| |
| /** |
| * This is an intrinsic class that contains the implementation details for Java arrays. <p> |
| * |
| * This class should contain only static methods or fields. |
| */ |
| public final class Array { |
| // Array element type classes. Needs to be in sync with enums in TypeCategory.java. |
| private static final int TYPE_JAVA_OBJECT = 0; |
| private static final int TYPE_JAVA_OBJECT_OR_JSO = 1; |
| private static final int TYPE_JSO = 2; |
| private static final int TYPE_ARRAY = 3; |
| private static final int TYPE_JSO_ARRAY = 4; |
| private static final int TYPE_JAVA_LANG_OBJECT = 5; |
| private static final int TYPE_JAVA_LANG_STRING = 6; |
| private static final int TYPE_JAVA_LANG_DOUBLE = 7; |
| private static final int TYPE_JAVA_LANG_BOOLEAN = 8; |
| private static final int TYPE_JS_NATIVE = 9; |
| private static final int TYPE_JS_UNKNOWN_NATIVE = 10; |
| private static final int TYPE_JS_FUNCTION = 11; |
| private static final int TYPE_JS_OBJECT = 12; |
| private static final int TYPE_JS_ARRAY = 13; |
| // Primitive types must be consecutive. |
| private static final int TYPE_PRIMITIVE_LONG = 14; |
| private static final int TYPE_PRIMITIVE_NUMBER = 15; |
| private static final int TYPE_PRIMITIVE_BOOLEAN = 16; |
| |
| public static <T> T[] stampJavaTypeInfo(Object array, T[] referenceType) { |
| if (Array.getElementTypeCategory(referenceType) != TYPE_JS_UNKNOWN_NATIVE) { |
| stampJavaTypeInfo(referenceType.getClass(), Util.getCastableTypeMap(referenceType), |
| Array.getElementTypeId(referenceType), |
| Array.getElementTypeCategory(referenceType), array); |
| } |
| return Array.asArray(array); |
| } |
| |
| /** |
| * Returns an untyped uninitialized array. |
| */ |
| static native Object[] newArray(int size) /*-{ |
| return new Array(size); |
| }-*/; |
| |
| /** |
| * Creates an array like "new T[a][b][c][][]" by passing in a native JSON |
| * array, [a, b, c]. |
| * |
| * @param leafClassLiteral the class literal for the leaf class |
| * @param castableTypeMap the map of types to which this array can be casted, |
| * in the form of a JSON map object |
| * @param elementTypeId the typeId of array elements |
| * @param elementTypeCategory whether the element type is java.lang.Object |
| * ({@link TYPE_JAVA_LANG_OBJECT}), is guaranteed to be a java object |
| * ({@link TYPE_JAVA_OBJECT}), is guaranteed to be a JSO |
| * ({@link TYPE_JSO}), can be either ({@link TYPE_JAVA_OBJECT_OR_JSO}) or |
| * or some primitive type {@link TYPE_PRIMITIVE_BOOLEAN}, {@link TYPE_PRIMITIVE_LONG} or |
| * {@link TYPE_PRIMITIVE_NUMBER}. |
| * @param length the length of the array |
| * @param dimensions the number of dimensions of the array |
| * @return the new array |
| */ |
| public static Object initUnidimensionalArray(Class<?> leafClassLiteral, |
| JavaScriptObject castableTypeMap, JavaScriptObject elementTypeId, int length, |
| int elementTypeCategory, int dimensions) { |
| Object result = initializeArrayElementsWithDefaults(elementTypeCategory, length); |
| if (elementTypeCategory != TYPE_JS_UNKNOWN_NATIVE) { |
| stampJavaTypeInfo(getClassLiteralForArray(leafClassLiteral, dimensions), castableTypeMap, |
| elementTypeId, elementTypeCategory, result); |
| } |
| return result; |
| } |
| |
| /** |
| * Creates an array like "new T[a][b][c][][]" by passing in a native JSON |
| * array, [a, b, c]. |
| * |
| * @param leafClassLiteral the class literal for the leaf class |
| * @param castableTypeMapExprs the JSON castableTypeMap of each dimension, |
| * from highest to lowest |
| * @param elementTypeIds the elementTypeId of each dimension, from highest to lowest |
| * @param leafElementTypeCategory whether the element type is java.lang.Object |
| * ({@link TYPE_JAVA_LANG_OBJECT}), is guaranteed to be a java object |
| * ({@link TYPE_JAVA_OBJECT}), is guaranteed to be a JSO |
| * ({@link TYPE_JSO}), can be either ({@link TYPE_JAVA_OBJECT_OR_JSO}) or |
| * or some primitive type {@link TYPE_PRIMITIVE_BOOLEAN}, {@link TYPE_PRIMITIVE_LONG} or |
| * {@link TYPE_PRIMITIVE_NUMBER}. |
| * @param dimExprs the length of each dimension, from highest to lower |
| * @return the new array |
| */ |
| public static Object initMultidimensionalArray(Class<?> leafClassLiteral, |
| JavaScriptObject[] castableTypeMapExprs, |
| JavaScriptObject[] elementTypeIds, int leafElementTypeCategory, int[] dimExprs, int count) { |
| return initMultidimensionalArray(leafClassLiteral, castableTypeMapExprs, elementTypeIds, |
| leafElementTypeCategory, |
| dimExprs, 0, count); |
| } |
| |
| /** |
| * Creates an array like "new T[][]{a,b,c,d}" by passing in a native JSON |
| * array, [a, b, c, d]. |
| * |
| * @param arrayClass the class of the array |
| * @param castableTypeMap the map of types to which this array can be casted, |
| * in the form of a JSON map object |
| * @param elementTypeId the typeId of array elements |
| * @param elementTypeCategory whether the element type is java.lang.Object |
| * ({@link TYPE_JAVA_LANG_OBJECT}), is guaranteed to be a java object |
| * ({@link TYPE_JAVA_OBJECT}), is guaranteed to be a JSO |
| * ({@link TYPE_JSO}), can be either ({@link TYPE_JAVA_OBJECT_OR_JSO}) or |
| * or some primitive type {@link TYPE_PRIMITIVE_BOOLEAN}, {@link TYPE_PRIMITIVE_LONG} or |
| * {@link TYPE_PRIMITIVE_NUMBER}. |
| * @param array the JSON array that will be transformed into a GWT array |
| * @return values; having wrapped it for GWT |
| */ |
| public static Object stampJavaTypeInfo(Class<?> arrayClass, JavaScriptObject castableTypeMap, |
| JavaScriptObject elementTypeId, int elementTypeCategory, Object array) { |
| setClass(array, arrayClass); |
| Util.setCastableTypeMap(array, castableTypeMap); |
| Util.setTypeMarker(array); |
| Array.setElementTypeId(array, elementTypeId); |
| Array.setElementTypeCategory(array, elementTypeCategory); |
| return array; |
| } |
| |
| /** |
| * Performs an array assignment, after validating the type of the value being |
| * stored. The form of the type check depends on the value of elementTypeId and |
| * elementTypeCategory as follows: |
| * <p> |
| * If the elementTypeCategory is {@link TYPE_JAVA_OBJECT}, this indicates a normal cast check |
| * should be performed, using the elementTypeId as the cast destination type. |
| * JavaScriptObjects cannot be stored in this case. |
| * <p> |
| * If the elementTypeId is {@link TYPE_JAVA_LANG_OBJECT}, this is the cast target for the Object |
| * type, in which case all types can be stored, including JavaScriptObject. |
| * <p> |
| * If the elementTypeId is {@link TYPE_JSO}, this indicates that only JavaScriptObjects can be |
| * stored. |
| * <p> |
| * If the elementTypeId is {@link TYPE_JAVA_OBJECT_OR_JSO}, this indicates that both |
| * JavaScriptObjects, and Java types can be stored. In the case of Java types, a normal cast check |
| * should be performed, using the elementTypeId as the cast destination type. |
| * This case is provided to support arrays declared with an interface type, which has dual |
| * implementations (i.e. interface types which have both Java and JavaScriptObject |
| * implementations). |
| * <p> |
| * Attempting to store an object that cannot satisfy the castability check |
| * throws an {@link ArrayStoreException}. |
| */ |
| public static Object setCheck(Object array, int index, Object value) { |
| checkArrayType(value == null || canSet(array, value)); |
| return set(array, index, value); |
| } |
| |
| @HasNoSideEffects |
| private static boolean canSet(Object array, Object value) { |
| switch (Array.getElementTypeCategory(array)) { |
| case TYPE_JAVA_LANG_STRING: |
| return Cast.instanceOfString(value); |
| case TYPE_JAVA_LANG_DOUBLE: |
| return Cast.instanceOfDouble(value); |
| case TYPE_JAVA_LANG_BOOLEAN: |
| return Cast.instanceOfBoolean(value); |
| case TYPE_ARRAY: |
| return Cast.instanceOfArray(value); |
| case TYPE_JS_FUNCTION: |
| return Cast.instanceOfFunction(value); |
| case TYPE_JS_OBJECT: |
| return Cast.instanceOfJsObject(value); |
| case TYPE_JAVA_OBJECT: |
| return Cast.canCast(value, Array.getElementTypeId(array)); |
| case TYPE_JSO: |
| return Cast.isJavaScriptObject(value); |
| case TYPE_JAVA_OBJECT_OR_JSO: |
| return Cast.isJavaScriptObject(value) |
| || Cast.canCast(value, Array.getElementTypeId(array)); |
| default: |
| return true; |
| } |
| } |
| |
| /** |
| * Use JSNI to effect a castless type change. |
| */ |
| private static native <T> T[] asArray(Object array) /*-{ |
| return array; |
| }-*/; |
| |
| /** |
| * Creates a primitive JSON array of a given the element type class. |
| */ |
| private static native Object initializeArrayElementsWithDefaults( |
| int elementTypeCategory, int length) /*-{ |
| var array = new Array(length); |
| var initValue; |
| switch (elementTypeCategory) { |
| case @com.google.gwt.lang.Array::TYPE_PRIMITIVE_LONG: |
| // Fill array with fast long version of 0, which is just the number 0; so fall through. |
| case @com.google.gwt.lang.Array::TYPE_PRIMITIVE_NUMBER: |
| initValue = 0; |
| break; |
| case @com.google.gwt.lang.Array::TYPE_PRIMITIVE_BOOLEAN: |
| initValue = false; |
| break; |
| default: |
| // Do not initialize as undefined is equivalent to null |
| return array; |
| } |
| |
| for ( var i = 0; i < length; ++i) { |
| array[i] = initValue; |
| } |
| return array; |
| }-*/; |
| |
| private static Object initMultidimensionalArray(Class<?> leafClassLiteral, |
| JavaScriptObject[] castableTypeMapExprs, JavaScriptObject[] elementTypeIds, |
| int leafElementTypeCategory, int[] dimExprs, int index, int count) { |
| int length = dimExprs[index]; |
| boolean isLastDimension = (index == (count - 1)); |
| // All dimensions but the last are plain reference types. |
| int elementTypeCategory = isLastDimension ? leafElementTypeCategory : TYPE_JAVA_OBJECT; |
| |
| Object result = initializeArrayElementsWithDefaults(elementTypeCategory, length); |
| if (leafElementTypeCategory != TYPE_JS_UNKNOWN_NATIVE) { |
| stampJavaTypeInfo(getClassLiteralForArray(leafClassLiteral, count - index), |
| castableTypeMapExprs[index], elementTypeIds[index], elementTypeCategory, result); |
| } |
| |
| if (!isLastDimension) { |
| // Recurse to next dimension. |
| ++index; |
| for (int i = 0; i < length; ++i) { |
| set(result, i, initMultidimensionalArray(leafClassLiteral, castableTypeMapExprs, |
| elementTypeIds, leafElementTypeCategory, dimExprs, index, count)); |
| } |
| } |
| return result; |
| } |
| |
| // This method is package protected so that it is indexed. {@link ImplementClassLiteralsAsFields} |
| // will insert calls to this method when array class literals are constructed. |
| // |
| // Inlining is prevented on this very hot method to avoid a subtantial increase in |
| // {@link JsInliner} execution time. |
| @DoNotInline |
| static <T> Class<T> getClassLiteralForArray(Class<?> clazz , int dimensions) { |
| return getClassLiteralForArrayImpl(clazz, dimensions); |
| } |
| |
| private static native int getElementTypeCategory(Object array) /*-{ |
| return array.__elementTypeCategory$ == null |
| ? @Array::TYPE_JS_UNKNOWN_NATIVE |
| : array.__elementTypeCategory$; |
| }-*/; |
| |
| @HasNoSideEffects |
| private static native JavaScriptObject getElementTypeId(Object array) /*-{ |
| return array.__elementTypeId$; |
| }-*/; |
| |
| // DO NOT INLINE this method into {@link getClassLiteralForArray}. |
| // The purpose of this method is to avoid introducing a public api to {@link java.lang.Class}. |
| private static native <T> Class<T> getClassLiteralForArrayImpl( |
| Class<?> clazz , int dimensions) /*-{ |
| return @java.lang.Class::getClassLiteralForArray(*)(clazz, dimensions); |
| }-*/; |
| |
| /** |
| * Sets a value in the array. |
| */ |
| private static native Object set(Object array, int index, Object value) /*-{ |
| return array[index] = value; |
| }-*/; |
| |
| // violator pattern so that the field remains private |
| private static native void setClass(Object o, Class<?> clazz) /*-{ |
| o.@java.lang.Object::___clazz = clazz; |
| }-*/; |
| |
| private static native void setElementTypeId(Object array, JavaScriptObject elementTypeId) /*-{ |
| array.__elementTypeId$ = elementTypeId; |
| }-*/; |
| |
| private static native void setElementTypeCategory(Object array, int elementTypeCategory) /*-{ |
| array.__elementTypeCategory$ = elementTypeCategory; |
| }-*/; |
| |
| /** |
| * Returns true if {@code src} is a Java array. |
| */ |
| @HasNoSideEffects |
| static boolean isJavaArray(Object src) { |
| return Cast.isArray(src) && Util.hasTypeMarker(src); |
| } |
| |
| /** |
| * Returns true if {@code src} is a Java array. |
| */ |
| static boolean isPrimitiveArray(Object array) { |
| int elementTypeCategory = getElementTypeCategory(array); |
| return elementTypeCategory >= TYPE_PRIMITIVE_LONG |
| && elementTypeCategory <= TYPE_PRIMITIVE_BOOLEAN; |
| }; |
| |
| public static Object ensureNotNull(Object array) { |
| return checkNotNull(array); |
| } |
| |
| private Array() { |
| } |
| } |
| |