blob: 0b7244ab738ca1a716e0807538c00fba23fec4f7 [file] [log] [blame]
/*
* 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() {
}
}