/*
* 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 com.google.gwt.core.client.JavaScriptObject;
/**
* This is a magic class the compiler uses as a base class for injected array
* classes.
*/
public final class Array {
private static final class ExpandoWrapper {
/**
* A JS array containing the names of any expandos we need to add to arrays
* (such as "hashCode", "equals", "toString").
*/
private static final Object expandoNames = makeEmptyJsArray();
/**
* A JS array containing the values of any expandos we need to add to arrays
* (such as hashCode(), equals(), toString()).
*/
private static final Object expandoValues = makeEmptyJsArray();
static {
initExpandos(new Array(), expandoNames, expandoValues);
}
public static void wrapArray(Array array) {
wrapArray(array, expandoNames, expandoValues);
}
private static native void initExpandos(Array protoType,
Object expandoNames, Object expandoValues) /*-{
var i = 0, value;
for ( var name in protoType) {
// Only copy non-null values over; this generally means only functions
// will get copied over, and not fields, which is good because we will
// setup the fields manually and it's best if length doesn't get blown
// away.
if (value = protoType[name]) {
expandoNames[i] = name;
expandoValues[i] = value;
++i;
}
}
}-*/;
private static native Object makeEmptyJsArray() /*-{
return [];
}-*/;
private static native void wrapArray(Array array, Object expandoNames,
Object expandoValues) /*-{
for ( var i = 0, c = expandoNames.length; i < c; ++i) {
array[expandoNames[i]] = expandoValues[i];
}
}-*/;
}
/*
* TODO: static init instead of lazy init when we can elide the clinit calls.
*/
static final int FALSE_SEED_TYPE = 2;
static final int LONG_SEED_TYPE = 3;
static final int NULL_SEED_TYPE = 0;
static final int ZERO_SEED_TYPE = 1;
/**
* Creates a copy of the specified array.
*/
public static <T> T[] clone(T[] array) {
return cloneSubrange(array, 0, array.length);
}
/**
* Creates a copy of a subrange of the specified array.
*/
public static <T> T[] cloneSubrange(T[] array, int fromIndex, int toIndex) {
Array a = asArrayType(array);
Array result = arraySlice(a, fromIndex, toIndex);
initValues(a.getClass(), Util.getCastableTypeMap(a), a.queryId, result);
// implicit type arg not inferred (as of JDK 1.5.0_07)
return Array.<T> asArray(result);
}
/**
* Creates a new array of the exact same type and length as a given array.
*/
public static <T> T[] createFrom(T[] array) {
return createFrom(array, array.length);
}
/**
* Creates an empty array of the exact same type as a given array, with the
* specified length.
*/
public static <T> T[] createFrom(T[] array, int length) {
Array a = asArrayType(array);
Array result = createFromSeed(NULL_SEED_TYPE, length);
initValues(a.getClass(), Util.getCastableTypeMap(a), a.queryId, result);
// implicit type arg not inferred (as of JDK 1.5.0_07)
return Array.<T> asArray(result);
}
/**
* Creates an array like "new T[a][b][c][][]" by passing in a native JSON
* array, [a, b, c].
*
* @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 queryId the queryId of the array
* @param length the length of the array
* @param seedType the primitive type of the array; 0: null; 1: zero; 2: false; 3: long
* @return the new array
*/
public static Array initDim(Class<?> arrayClass,
JavaScriptObject castableTypeMap, int queryId, int length, int seedType) {
Array result = createFromSeed(seedType, length);
initValues(arrayClass, castableTypeMap, queryId, result);
return result;
}
/**
* Creates an array like "new T[a][b][c][][]" by passing in a native JSON
* array, [a, b, c].
*
* @param arrayClasses the class of each dimension of the array
* @param castableTypeMapExprs the JSON castableTypeMap of each dimension,
* from highest to lowest
* @param queryIdExprs the queryId of each dimension, from highest to lowest
* @param dimExprs the length of each dimension, from highest to lower
* @param seedType the primitive type of the array; 0: null; 1: zero; 2: false; 3: long
* @return the new array
*/
public static Array initDims(Class<?> arrayClasses[],
JavaScriptObject[] castableTypeMapExprs, int[] queryIdExprs,
int[] dimExprs, int count, int seedType) {
return initDims(arrayClasses, castableTypeMapExprs, queryIdExprs,
dimExprs, 0, count, seedType);
}
/**
* 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 queryId the queryId of the array
* @param array the JSON array that will be transformed into a GWT array
* @return values; having wrapped it for GWT
*/
public static Array initValues(Class<?> arrayClass,
JavaScriptObject castableTypeMap, int queryId, Array array) {
ExpandoWrapper.wrapArray(array);
setClass(array, arrayClass);
Util.setCastableTypeMap(array, castableTypeMap);
array.queryId = queryId;
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 queryId, as
* follows:
* <p>
* If the queryId is > 0, this indicates a normal cast check should be
* performed, using the queryId as the cast destination type.
* JavaScriptObjects cannot be stored in this case.
* <p>
* If the queryId == 0, this is the cast target for the Object type, in which
* case all types can be stored, including JavaScriptObject.
* <p>
* If the queryId == -1, this indicates that only JavaScriptObjects can be
* stored (-1 is the cast target for JavaScriptObject, by convention).
* <p>
* If the queryId is < -1, this indicates that both JavaScriptObjects, and
* Java types can be stored. In the case of Java types, the inverse of the
* queryId is used for castability testing. 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>
* Note, by convention, a queryId of 1 is reserved for String, which is a
* final class, and can't implement an interface, and thus, it's inverse, -1,
* can safely be interpreted as a special case, as stated above.
* <p>
* Attempting to store an object that cannot satisfy the castability check
* throws an {@link ArrayStoreException}.
*/
public static Object setCheck(Array array, int index, Object value) {
if (value != null) {
if (array.queryId > 0 && !Cast.canCastUnsafe(value, array.queryId)) {
// value must be castable to queryId
throw new ArrayStoreException();
} else if (array.queryId == -1 && Cast.isJavaObject(value)) {
// value must be a JavaScriptObject
throw new ArrayStoreException();
} else if (array.queryId < -1 && !Cast.isJavaScriptObject(value)
&& !Cast.canCastUnsafe(value, -array.queryId)) {
// value must be a JavaScriptObject, or else castable to the inverse of
// queryId
throw new ArrayStoreException();
}
}
return set(array, index, value);
}
private static native Array arraySlice(Array array, int fromIndex, int toIndex) /*-{
return array.slice(fromIndex, toIndex);
}-*/;
/**
* Use JSNI to effect a castless type change.
*/
private static native <T> T[] asArray(Array array) /*-{
return array;
}-*/;
/**
* Use JSNI to effect a castless type change.
*/
private static native <T> Array asArrayType(T[] array) /*-{
return array;
}-*/;
/**
* Creates a primitive JSON array of a given seedType.
*
* @param seedType the primitive type of the array; 0: null; 1: zero;
* 2: false; 3: (long) 0
* @param length the requested length
* @see #NULL_SEED_TYPE
* @see #ZERO_SEED_TYPE
* @see #FALSE_SEED_TYPE
* @see #LONG_SEED_TYPE
* @return the new JSON array
*/
private static native Array createFromSeed(int seedType, int length) /*-{
var array = new Array(length);
if (seedType == 3) {
// Fill array with the type used by LongLib
for ( var i = 0; i < length; ++i) {
var value = new Object();
value.l = value.m = value.h = 0;
array[i] = value;
}
} else if (seedType > 0) {
var value = [null, 0, false][seedType];
for ( var i = 0; i < length; ++i) {
array[i] = value;
}
}
return array;
}-*/;
private static Array initDims(Class<?> arrayClasses[],
JavaScriptObject[] castableTypeMapExprs, int[] queryIdExprs, int[] dimExprs,
int index, int count, int seedType) {
int length = dimExprs[index];
boolean isLastDim = (index == (count - 1));
Array result = createFromSeed(isLastDim ? seedType : NULL_SEED_TYPE, length);
initValues(arrayClasses[index], castableTypeMapExprs[index],
queryIdExprs[index], result);
if (!isLastDim) {
// Recurse to next dimension.
++index;
for (int i = 0; i < length; ++i) {
set(result, i, initDims(arrayClasses, castableTypeMapExprs,
queryIdExprs, dimExprs, index, count, seedType));
}
}
return result;
}
/**
* Sets a value in the array.
*/
private static native Object set(Array 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;
}-*/;
/*
* Explicitly initialize all fields to JS false values; see comment in
* ExpandoWrapper.initExpandos().
*/
/**
* A representation of the necessary cast target for objects stored into this
* array.
*
* @see #setCheck
*/
protected int queryId = 0;
}