/* * 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() { } }