| /* |
| * Copyright 2010 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.requestfactory.client.impl; |
| |
| import com.google.gwt.core.client.GWT; |
| import com.google.gwt.core.client.JavaScriptObject; |
| import com.google.gwt.core.client.JsArray; |
| import com.google.gwt.valuestore.shared.EnumProperty; |
| import com.google.gwt.valuestore.shared.Property; |
| import com.google.gwt.valuestore.shared.PropertyReference; |
| import com.google.gwt.valuestore.shared.Record; |
| |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.util.Date; |
| |
| /** |
| * <p> <span style="color:red">Experimental API: This class is still under rapid |
| * development, and is very likely to be deleted. Use it at your own risk. |
| * </span> </p> JSO implementation of {@link Record}, used to back subclasses of |
| * {@link RecordImpl}. |
| */ |
| public class RecordJsoImpl extends JavaScriptObject implements Record { |
| |
| /** |
| * JSO to hold result and related objects. |
| */ |
| public static class JsonResults extends JavaScriptObject { |
| |
| protected JsonResults() { |
| } |
| |
| public final native JsArray<RecordJsoImpl> getListResult() /*-{ |
| return this.result; |
| }-*/; |
| |
| public final native RecordJsoImpl getRecordResult() /*-{ |
| return this.result; |
| }-*/; |
| |
| public final native JavaScriptObject getRelated() /*-{ |
| return this.related; |
| }-*/; |
| |
| public final native Object getResult() /*-{ |
| return Object(this.result); |
| }-*/; |
| |
| public final native JavaScriptObject getSideEffects() /*-{ |
| return this.sideEffects; |
| }-*/; |
| } |
| |
| public static native JsArray<RecordJsoImpl> arrayFromJson(String json) /*-{ |
| return eval(json); |
| }-*/; |
| |
| public static RecordJsoImpl create(Long id, Integer version, |
| final RecordSchema<?> schema) { |
| RecordJsoImpl copy = create(); |
| copy.setSchema(schema); |
| copy.set(Record.id, id); |
| copy.set(Record.version, version); |
| return copy; |
| } |
| |
| public static RecordJsoImpl emptyCopy(RecordJsoImpl jso) { |
| Long id = jso.get(Record.id); |
| Integer version = jso.get(Record.version); |
| final RecordSchema<?> schema = jso.getSchema(); |
| |
| RecordJsoImpl copy = create(id, version, schema); |
| copy.setRequestFactory(jso.getRequestFactory()); |
| copy.setValueStore(jso.getValueStore()); |
| return copy; |
| } |
| |
| public static native RecordJsoImpl fromJson(String json) /*-{ |
| // TODO: clean this |
| eval("xyz=" + json); |
| return xyz; |
| }-*/; |
| |
| public static native JsonResults fromResults(String json) /*-{ |
| // TODO: clean this |
| eval("xyz=" + json); |
| return xyz; |
| }-*/; |
| |
| /* Made protected, for testing */ |
| |
| protected static native RecordJsoImpl create() /*-{ |
| return {}; |
| }-*/; |
| |
| protected RecordJsoImpl() { |
| } |
| |
| public final native void delete(String name)/*-{ |
| delete this[name]; |
| }-*/; |
| |
| @SuppressWarnings("unchecked") |
| public final <V> V get(Property<V> property) { |
| // TODO lax for the moment b/c client code can't yet reasonably make the |
| // request |
| // assert isDefined(property.getName()) : |
| // "Cannot ask for a property before setting it: " |
| // + property.getName(); |
| if (isNullOrUndefined(property.getName())) { |
| return null; |
| } |
| try { |
| if (Boolean.class.equals(property.getType())) { |
| return (V) Boolean.valueOf(getBoolean(property.getName())); |
| } |
| if (Character.class.equals(property.getType())) { |
| return (V) Character.valueOf(String.valueOf(get(property.getName())).charAt(0)); |
| } |
| if (Byte.class.equals(property.getType())) { |
| return (V) Byte.valueOf((byte) getInt(property.getName())); |
| } |
| if (Short.class.equals(property.getType())) { |
| return (V) Short.valueOf((short) getInt(property.getName())); |
| } |
| if (Float.class.equals(property.getType())) { |
| return (V) Float.valueOf((float) getDouble(property.getName())); |
| } |
| if (BigInteger.class.equals(property.getType())) { |
| return (V) new BigDecimal((String) get(property.getName())).toBigInteger(); |
| } |
| if (BigDecimal.class.equals(property.getType())) { |
| return (V) new BigDecimal((String) get(property.getName())); |
| } |
| if (Integer.class.equals(property.getType())) { |
| return (V) Integer.valueOf(getInt(property.getName())); |
| } |
| if (Long.class.equals(property.getType())) { |
| return (V) Long.valueOf((String) get(property.getName())); |
| } |
| if (Double.class.equals(property.getType())) { |
| if (!isDefined(property.getName())) { |
| return (V) new Double(0.0); |
| } |
| return (V) Double.valueOf(getDouble(property.getName())); |
| } |
| if (Date.class.equals(property.getType())) { |
| double millis = new Date().getTime(); |
| if (isDefined(property.getName())) { |
| millis = Double.parseDouble((String) get(property.getName())); |
| } |
| if (GWT.isScript()) { |
| return (V) dateForDouble(millis); |
| } else { |
| // In dev mode, we're using real JRE dates |
| return (V) new Date((long) millis); |
| } |
| } |
| } catch (final Exception ex) { |
| throw new IllegalStateException( |
| "Property " + property.getName() + " has invalid " + " value " |
| + get(property.getName()) + " for type " + property.getType()); |
| } |
| |
| if (property instanceof EnumProperty) { |
| EnumProperty<V> eProperty = (EnumProperty<V>) property; |
| Enum[] values = (Enum[]) eProperty.getValues(); |
| int ordinal = getInt(property.getName()); |
| for (Enum value : values) { |
| if (ordinal == value.ordinal()) { |
| return (V) value; |
| } |
| } |
| } |
| |
| if (String.class == property.getType()) { |
| return (V) get(property.getName()); |
| } |
| // at this point, we checked all the known primitive/value types we support |
| // TODO: handle embedded types, List, Set, Map |
| |
| // else, it must be a record type |
| String relatedId = (String) get(property.getName()); |
| if (relatedId == null) { |
| return null; |
| } else { |
| // TODO: should cache this or modify JSO field in place |
| String schemaAndId[] = relatedId.split("-"); |
| return (V) getValueStore().getRecordBySchemaAndId( |
| getRequestFactory().getSchema(schemaAndId[0]), |
| Long.valueOf(schemaAndId[1])); |
| } |
| } |
| |
| public final native <T> T get(String propertyName) /*-{ |
| return this[propertyName] || null; |
| }-*/; |
| |
| public final Long getId() { |
| return this.get(id); |
| } |
| |
| public final <V> PropertyReference<V> getRef(Property<V> property) { |
| return new PropertyReference<V>(this, property); |
| } |
| |
| // TODO: HACK! Need to look up token to schema for relatins |
| public final native RequestFactoryJsonImpl getRequestFactory() /*-{ |
| return this['__rf']; |
| }-*/; |
| |
| public final native RecordSchema<?> getSchema() /*-{ |
| return this['__key']; |
| }-*/; |
| |
| // TODO: HACK! Make VS public and stash it in the record for relation lookups |
| public final native ValueStoreJsonImpl getValueStore() /*-{ |
| return this['__vs']; |
| }-*/; |
| |
| public final Integer getVersion() { |
| return this.get(version); |
| } |
| |
| /** |
| * @param name |
| */ |
| public final native boolean isDefined(String name)/*-{ |
| return this[name] !== undefined; |
| }-*/; |
| |
| public final boolean isEmpty() { |
| for (Property<?> property : getSchema().allProperties()) { |
| if ((property != Record.id) && (property != Record.version) && (isDefined( |
| property.getName()))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * @param name |
| */ |
| public final native boolean isNullOrUndefined(String name)/*-{ |
| return this[name] == null; |
| }-*/; |
| |
| public final boolean merge(RecordJsoImpl from) { |
| assert getSchema() == from.getSchema(); |
| |
| boolean changed = false; |
| for (Property<?> property : getSchema().allProperties()) { |
| if (from.isDefined(property.getName())) { |
| changed |= copyPropertyIfDifferent(property.getName(), from); |
| } |
| } |
| |
| return changed; |
| } |
| |
| public final <V> void set(Property<V> property, V value) { |
| if (value instanceof String) { |
| setString(property.getName(), (String) value); |
| return; |
| } |
| if (value instanceof Character) { |
| setString(property.getName(), String.valueOf(value)); |
| return; |
| } |
| |
| if (value instanceof Long || value instanceof BigDecimal |
| || value instanceof BigInteger) { |
| setString(property.getName(), String.valueOf(value)); |
| return; |
| } |
| |
| if (value instanceof Integer || value instanceof Short |
| || value instanceof Byte) { |
| setInt(property.getName(), ((Number) value).intValue()); |
| return; |
| } |
| |
| if (value instanceof Date) { |
| long millis = ((Date) value).getTime(); |
| setString(property.getName(), String.valueOf(millis)); |
| return; |
| } |
| if (value instanceof Double || value instanceof Float) { |
| setDouble(property.getName(), ((Number) value).doubleValue()); |
| return; |
| } |
| |
| if (value instanceof Enum<?>) { |
| setInt(property.getName(), ((Enum<?>) value).ordinal()); |
| return; |
| } |
| |
| if (value instanceof Boolean) { |
| setBoolean(property.getName(), ((Boolean) value).booleanValue()); |
| } |
| |
| if (value instanceof RecordImpl) { |
| setString(property.getName(), ((RecordImpl) value).getUniqueId()); |
| return; |
| } |
| |
| throw new UnsupportedOperationException( |
| "Can't yet set properties of type " + value.getClass().getName()); |
| } |
| |
| // TODO: HACK! Need to look up token to schema for relatins |
| public final native void setRequestFactory(RequestFactoryJsonImpl requestFactory) /*-{ |
| this['__rf'] = requestFactory; |
| }-*/; |
| |
| public final native void setSchema(RecordSchema<?> schema) /*-{ |
| this['__key'] = schema; |
| }-*/; |
| |
| // TODO: HACK! Make VS public and stash it in the record for relation lookups |
| |
| public final native void setValueStore(ValueStoreJsonImpl valueStoreJson) /*-{ |
| this['__vs']=valueStoreJson; |
| }-*/; |
| |
| /** |
| * Return JSON representation using org.json library. |
| * |
| * @return returned string. |
| */ |
| public final native String toJson() /*-{ |
| // Safari 4.0.5 appears not to honor the replacer argument, so we can't do this: |
| |
| // var replacer = function(key, value) { |
| // if (key == '__key') { |
| // return; |
| // } |
| // return value; |
| // } |
| // return $wnd.JSON.stringify(this, replacer); |
| |
| var key = this.__key; |
| delete this.__key; |
| var rf = this.__rf; |
| delete this.__rf; |
| var vs = this.__vs; |
| delete this.__vs; |
| // TODO verify that the stringify() from json2.js works on IE |
| var rtn = $wnd.JSON.stringify(this); |
| this.__key = key; |
| this.__rf = rf; |
| this.__vs = vs; |
| return rtn; |
| }-*/; |
| |
| /** |
| * Return JSON representation of just id and version fields, using org.json |
| * library. |
| * |
| * @return returned string. |
| */ |
| public final native String toJsonIdVersion() /*-{ |
| // Safari 4.0.5 appears not to honor the replacer argument, so we can't do this: |
| // var replacer = function(key, value) { |
| // if (key == 'id' || key == 'version') { |
| // return value; |
| // } |
| // return; |
| // } |
| // return $wnd.JSON.stringify(this, replacer); |
| var object = { id: this.id, version: this.version }; |
| return $wnd.JSON.stringify(object); |
| }-*/; |
| |
| private native boolean copyPropertyIfDifferent(String name, |
| RecordJsoImpl from) /*-{ |
| if (this[name] == from[name]) { |
| return false; |
| } |
| this[name] = from[name]; |
| return true; |
| }-*/; |
| |
| private native Date dateForDouble(double millis) /*-{ |
| return @java.util.Date::createFrom(D)(millis); |
| }-*/; |
| |
| private native boolean getBoolean(String name) /*-{ |
| return this[name]; |
| }-*/; |
| |
| private native double getDouble(String name) /*-{ |
| return this[name]; |
| }-*/; |
| |
| private native int getInt(String name) /*-{ |
| return this[name]; |
| }-*/; |
| |
| private native void setBoolean(String name, boolean value) /*-{ |
| this[name] = value; |
| }-*/; |
| |
| private native void setDouble(String name, double value) /*-{ |
| this[name] = value; |
| }-*/; |
| |
| private native void setInt(String name, int value) /*-{ |
| this[name] = value; |
| }-*/; |
| |
| private native void setString(String name, String value) /*-{ |
| this[name] = value; |
| }-*/; |
| } |