blob: 348e5da7b018f1770669d898610f16def5c7662a [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 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);
array.arrayClass = arrayClass;
Util.setCastableTypeMap(array, castableTypeMap);
array.queryId = queryId;
return array;
}
/**
* Performs an array assignment, checking for valid index and type.
*/
public static Object setCheck(Array array, int index, Object value) {
if (value != null) {
if (array.queryId > 0 && !Cast.canCastUnsafe(value, array.queryId)) {
throw new ArrayStoreException();
}
if (array.queryId < 0 && Cast.isJavaObject(value)) {
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;
}-*/;
/*
* Explicitly initialize all fields to JS false values; see comment in
* ExpandoWrapper.initExpandos().
*/
/**
* Holds the real type-specific Class object for a given array instance. The
* compiler produces a magic implementation of getClass() which returns this
* field directly.
*/
protected Class<?> arrayClass = null;
/**
* The necessary cast target for objects stored into this array. Attempting to
* store an object that cannot satisfy the query id throws and
* {@link ArrayStoreException}.
*/
protected int queryId = 0;
}