diff --git a/src/com/massivecraft/core/lib/gson2/AnonymousAndLocalClassExclusionStrategy.java b/src/com/massivecraft/core/lib/gson2/AnonymousAndLocalClassExclusionStrategy.java new file mode 100755 index 00000000..f1d811a7 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/AnonymousAndLocalClassExclusionStrategy.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +/** + * Strategy for excluding anonymous and local classes. + * + * @author Joel Leitch + */ +final class AnonymousAndLocalClassExclusionStrategy implements ExclusionStrategy { + + public boolean shouldSkipField(FieldAttributes f) { + return isAnonymousOrLocal(f.getDeclaredClass()); + } + + public boolean shouldSkipClass(Class clazz) { + return isAnonymousOrLocal(clazz); + } + + private boolean isAnonymousOrLocal(Class clazz) { + return !Enum.class.isAssignableFrom(clazz) + && (clazz.isAnonymousClass() || clazz.isLocalClass()); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/Cache.java b/src/com/massivecraft/core/lib/gson2/Cache.java new file mode 100755 index 00000000..b91600c6 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/Cache.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +/** + * Defines generic cache interface. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +interface Cache { + + /** + * Adds the new value object into the cache for the given key. If the key already + * exists, then this method will override the value for the key. + * + * @param key the key identifier for the {@code value} object + * @param value the value object to store in the cache + */ + void addElement(K key, V value); + + /** + * Retrieve the cached value for the given {@code key}. + * + * @param key the key identifying the value + * @return the cached value for the given {@code key} + */ + V getElement(K key); +} diff --git a/src/com/massivecraft/core/lib/gson2/CamelCaseSeparatorNamingPolicy.java b/src/com/massivecraft/core/lib/gson2/CamelCaseSeparatorNamingPolicy.java new file mode 100755 index 00000000..53c20811 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/CamelCaseSeparatorNamingPolicy.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.internal.$Gson$Preconditions; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collection; + +/** + * Converts the field name that uses camel-case define word separation into separate words that + * are separated by the provided {@code separatorString}. + * + *

The following is an example:

+ *
+ * class IntWrapper {
+ *   public int integerField = 0;
+ * }
+ *
+ * CamelCaseSeparatorNamingPolicy policy = new CamelCaseSeparatorNamingPolicy("_");
+ * String translatedFieldName =
+ *     policy.translateName(IntWrapper.class.getField("integerField"));
+ *
+ * assert("integer_Field".equals(translatedFieldName));
+ * 
+ * + * @author Joel Leitch + */ +final class CamelCaseSeparatorNamingPolicy extends RecursiveFieldNamingPolicy { + private final String separatorString; + + /** + * Constructs a new CamelCaseSeparatorNamingPolicy object that will add the + * {@code separatorString} between each of the words separated by camel case. + * + * @param separatorString the string value to place between words + * @throws IllegalArgumentException thrown if the {@code separatorString} parameter + * is null or empty. + */ + public CamelCaseSeparatorNamingPolicy(String separatorString) { + $Gson$Preconditions.checkNotNull(separatorString); + $Gson$Preconditions.checkArgument(!"".equals(separatorString)); + this.separatorString = separatorString; + } + + @Override + protected String translateName(String target, Type fieldType, + Collection annnotations) { + StringBuilder translation = new StringBuilder(); + for (int i = 0; i < target.length(); i++) { + char character = target.charAt(i); + if (Character.isUpperCase(character) && translation.length() != 0) { + translation.append(separatorString); + } + translation.append(character); + } + + return translation.toString(); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/CompositionFieldNamingPolicy.java b/src/com/massivecraft/core/lib/gson2/CompositionFieldNamingPolicy.java new file mode 100755 index 00000000..710a8cb1 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/CompositionFieldNamingPolicy.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collection; + +/** + * Performs numerous field naming translations wrapped up as one object. + * + * @author Joel Leitch + */ +abstract class CompositionFieldNamingPolicy extends RecursiveFieldNamingPolicy { + + private final RecursiveFieldNamingPolicy[] fieldPolicies; + + public CompositionFieldNamingPolicy(RecursiveFieldNamingPolicy... fieldNamingPolicies) { + if (fieldNamingPolicies == null) { + throw new NullPointerException("naming policies can not be null."); + } + this.fieldPolicies = fieldNamingPolicies; + } + + @Override + protected String translateName(String target, Type fieldType, Collection annotations) { + for (RecursiveFieldNamingPolicy policy : fieldPolicies) { + target = policy.translateName(target, fieldType, annotations); + } + return target; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/DefaultTypeAdapters.java b/src/com/massivecraft/core/lib/gson2/DefaultTypeAdapters.java new file mode 100755 index 00000000..fc6cc803 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/DefaultTypeAdapters.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.lang.reflect.Type; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * List of all the default type adapters ({@link JsonSerializer}s, {@link JsonDeserializer}s, + * and {@link InstanceCreator}s. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +final class DefaultTypeAdapters { + /** + * This type adapter supports three subclasses of date: Date, Timestamp, and + * java.sql.Date. + */ + static final class DefaultDateTypeAdapter implements JsonSerializer, JsonDeserializer { + private final DateFormat enUsFormat; + private final DateFormat localFormat; + private final DateFormat iso8601Format; + + DefaultDateTypeAdapter() { + this(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US), + DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT)); + } + + DefaultDateTypeAdapter(String datePattern) { + this(new SimpleDateFormat(datePattern, Locale.US), new SimpleDateFormat(datePattern)); + } + + DefaultDateTypeAdapter(int style) { + this(DateFormat.getDateInstance(style, Locale.US), DateFormat.getDateInstance(style)); + } + + public DefaultDateTypeAdapter(int dateStyle, int timeStyle) { + this(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US), + DateFormat.getDateTimeInstance(dateStyle, timeStyle)); + } + + DefaultDateTypeAdapter(DateFormat enUsFormat, DateFormat localFormat) { + this.enUsFormat = enUsFormat; + this.localFormat = localFormat; + this.iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + this.iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + // These methods need to be synchronized since JDK DateFormat classes are not thread-safe + // See issue 162 + public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { + synchronized (localFormat) { + String dateFormatAsString = enUsFormat.format(src); + return new JsonPrimitive(dateFormatAsString); + } + } + + public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + if (!(json instanceof JsonPrimitive)) { + throw new JsonParseException("The date should be a string value"); + } + Date date = deserializeToDate(json); + if (typeOfT == Date.class) { + return date; + } else if (typeOfT == Timestamp.class) { + return new Timestamp(date.getTime()); + } else if (typeOfT == java.sql.Date.class) { + return new java.sql.Date(date.getTime()); + } else { + throw new IllegalArgumentException(getClass() + " cannot deserialize to " + typeOfT); + } + } + + private Date deserializeToDate(JsonElement json) { + synchronized (localFormat) { + try { + return localFormat.parse(json.getAsString()); + } catch (ParseException ignored) { + } + try { + return enUsFormat.parse(json.getAsString()); + } catch (ParseException ignored) { + } + try { + return iso8601Format.parse(json.getAsString()); + } catch (ParseException e) { + throw new JsonSyntaxException(json.getAsString(), e); + } + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(DefaultDateTypeAdapter.class.getSimpleName()); + sb.append('(').append(localFormat.getClass().getSimpleName()).append(')'); + return sb.toString(); + } + } +} diff --git a/src/com/massivecraft/core/lib/gson2/DisjunctionExclusionStrategy.java b/src/com/massivecraft/core/lib/gson2/DisjunctionExclusionStrategy.java new file mode 100755 index 00000000..64940e95 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/DisjunctionExclusionStrategy.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.internal.$Gson$Preconditions; + +import java.util.Collection; + +/** + * A wrapper class used to collect numerous {@link ExclusionStrategy} objects + * and perform a short-circuited OR operation. + * + * @author Joel Leitch + */ +final class DisjunctionExclusionStrategy implements ExclusionStrategy { + private final Collection strategies; + + DisjunctionExclusionStrategy(Collection strategies) { + this.strategies = $Gson$Preconditions.checkNotNull(strategies); + } + + public boolean shouldSkipField(FieldAttributes f) { + for (ExclusionStrategy strategy : strategies) { + if (strategy.shouldSkipField(f)) { + return true; + } + } + return false; + } + + public boolean shouldSkipClass(Class clazz) { + for (ExclusionStrategy strategy : strategies) { + if (strategy.shouldSkipClass(clazz)) { + return true; + } + } + return false; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/ExclusionStrategy.java b/src/com/massivecraft/core/lib/gson2/ExclusionStrategy.java new file mode 100755 index 00000000..bfc17b06 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/ExclusionStrategy.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +/** + * A strategy (or policy) definition that is used to decide whether or not a field or top-level + * class should be serialized or deserialized as part of the JSON output/input. For serialization, + * if the {@link #shouldSkipClass(Class)} method returns false then that class or field type + * will not be part of the JSON output. For deserialization, if {@link #shouldSkipClass(Class)} + * returns false, then it will not be set as part of the Java object structure. + * + *

The following are a few examples that shows how you can use this exclusion mechanism. + * + *

Exclude fields and objects based on a particular class type: + *

+ * private static class SpecificClassExclusionStrategy implements ExclusionStrategy {
+ *   private final Class<?> excludedThisClass;
+ *
+ *   public SpecificClassExclusionStrategy(Class<?> excludedThisClass) {
+ *     this.excludedThisClass = excludedThisClass;
+ *   }
+ *
+ *   public boolean shouldSkipClass(Class<?> clazz) {
+ *     return excludedThisClass.equals(clazz);
+ *   }
+ *
+ *   public boolean shouldSkipField(FieldAttributes f) {
+ *     return excludedThisClass.equals(f.getDeclaredClass());
+ *   }
+ * }
+ * 
+ * + *

Excludes fields and objects based on a particular annotation: + *

+ * public @interface FooAnnotation {
+ *   // some implementation here
+ * }
+ *
+ * // Excludes any field (or class) that is tagged with an "@FooAnnotation"
+ * private static class FooAnnotationExclusionStrategy implements ExclusionStrategy {
+ *   public boolean shouldSkipClass(Class<?> clazz) {
+ *     return clazz.getAnnotation(FooAnnotation.class) != null;
+ *   }
+ *
+ *   public boolean shouldSkipField(FieldAttributes f) {
+ *     return f.getAnnotation(FooAnnotation.class) != null;
+ *   }
+ * }
+ * 
+ * + *

Now if you want to configure {@code Gson} to use a user defined exclusion strategy, then + * the {@code GsonBuilder} is required. The following is an example of how you can use the + * {@code GsonBuilder} to configure Gson to use one of the above sample: + *

+ * ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
+ * Gson gson = new GsonBuilder()
+ *     .setExclusionStrategies(excludeStrings)
+ *     .create();
+ * 
+ * + *

For certain model classes, you may only want to serialize a field, but exclude it for + * deserialization. To do that, you can write an {@code ExclusionStrategy} as per normal; + * however, you would register it with the + * {@link GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy)} method. + * For example: + *

+ * ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
+ * Gson gson = new GsonBuilder()
+ *     .addDeserializationExclusionStrategy(excludeStrings)
+ *     .create();
+ * 
+ * + * @author Inderjeet Singh + * @author Joel Leitch + * + * @see GsonBuilder#setExclusionStrategies(ExclusionStrategy...) + * @see GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy) + * @see GsonBuilder#addSerializationExclusionStrategy(ExclusionStrategy) + * + * @since 1.4 + */ +public interface ExclusionStrategy { + + /** + * @param f the field object that is under test + * @return true if the field should be ignored; otherwise false + */ + public boolean shouldSkipField(FieldAttributes f); + + /** + * @param clazz the class object that is under test + * @return true if the class should be ignored; otherwise false + */ + public boolean shouldSkipClass(Class clazz); +} diff --git a/src/com/massivecraft/core/lib/gson2/ExposeAnnotationDeserializationExclusionStrategy.java b/src/com/massivecraft/core/lib/gson2/ExposeAnnotationDeserializationExclusionStrategy.java new file mode 100755 index 00000000..42e22efc --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/ExposeAnnotationDeserializationExclusionStrategy.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.annotations.Expose; + +/** + * Excludes fields that do not have the {@link Expose} annotation + * + * @author Joel Leitch + */ +final class ExposeAnnotationDeserializationExclusionStrategy implements ExclusionStrategy { + public boolean shouldSkipClass(Class clazz) { + return false; + } + + public boolean shouldSkipField(FieldAttributes f) { + Expose annotation = f.getAnnotation(Expose.class); + if (annotation == null) { + return true; + } + return !annotation.deserialize(); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/ExposeAnnotationSerializationExclusionStrategy.java b/src/com/massivecraft/core/lib/gson2/ExposeAnnotationSerializationExclusionStrategy.java new file mode 100755 index 00000000..d9e5df86 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/ExposeAnnotationSerializationExclusionStrategy.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.annotations.Expose; + +/** + * Excludes fields that do not have the {@link Expose} annotation + * + * @author Joel Leitch + */ +final class ExposeAnnotationSerializationExclusionStrategy implements ExclusionStrategy { + public boolean shouldSkipClass(Class clazz) { + return false; + } + + public boolean shouldSkipField(FieldAttributes f) { + Expose annotation = f.getAnnotation(Expose.class); + if (annotation == null) { + return true; + } + return !annotation.serialize(); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/FieldAttributes.java b/src/com/massivecraft/core/lib/gson2/FieldAttributes.java new file mode 100755 index 00000000..05e3b1f8 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/FieldAttributes.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2009 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.internal.$Gson$Preconditions; +import com.massivecraft.core.lib.gson2.internal.Pair; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +/** + * A data object that stores attributes of a field. + * + *

This class is immutable; therefore, it can be safely shared across threads. + * + * @author Inderjeet Singh + * @author Joel Leitch + * + * @since 1.4 + */ +public final class FieldAttributes { + private static final String MAX_CACHE_PROPERTY_NAME = + "com.google.gson.annotation_cache_size_hint"; + + private static final Cache, String>, Collection> ANNOTATION_CACHE = + new LruCache,String>, Collection>(getMaxCacheSize()); + + private final Class declaringClazz; + private final Field field; + private final Class declaredType; + private final boolean isSynthetic; + private final int modifiers; + private final String name; + + // Fields used for lazy initialization + private Type genericType; + private Collection annotations; + + /** + * Constructs a Field Attributes object from the {@code f}. + * + * @param f the field to pull attributes from + */ + FieldAttributes(Class declaringClazz, Field f) { + this.declaringClazz = $Gson$Preconditions.checkNotNull(declaringClazz); + this.name = f.getName(); + this.declaredType = f.getType(); + this.isSynthetic = f.isSynthetic(); + this.modifiers = f.getModifiers(); + this.field = f; + } + + private static int getMaxCacheSize() { + final int defaultMaxCacheSize = 2000; + try { + String propertyValue = System.getProperty( + MAX_CACHE_PROPERTY_NAME, String.valueOf(defaultMaxCacheSize)); + return Integer.parseInt(propertyValue); + } catch (NumberFormatException e) { + return defaultMaxCacheSize; + } + } + + /** + * @return the declaring class that contains this field + */ + public Class getDeclaringClass() { + return declaringClazz; + } + + /** + * @return the name of the field + */ + public String getName() { + return name; + } + + /** + *

For example, assume the following class definition: + *

+   * public class Foo {
+   *   private String bar;
+   *   private List<String> red;
+   * }
+   *
+   * Type listParmeterizedType = new TypeToken<List<String>>() {}.getType();
+   * 
+ * + *

This method would return {@code String.class} for the {@code bar} field and + * {@code listParameterizedType} for the {@code red} field. + * + * @return the specific type declared for this field + */ + public Type getDeclaredType() { + if (genericType == null) { + genericType = field.getGenericType(); + } + return genericType; + } + + /** + * Returns the {@code Class} object that was declared for this field. + * + *

For example, assume the following class definition: + *

+   * public class Foo {
+   *   private String bar;
+   *   private List<String> red;
+   * }
+   * 
+ * + *

This method would return {@code String.class} for the {@code bar} field and + * {@code List.class} for the {@code red} field. + * + * @return the specific class object that was declared for the field + */ + public Class getDeclaredClass() { + return declaredType; + } + + /** + * Return the {@code T} annotation object from this field if it exist; otherwise returns + * {@code null}. + * + * @param annotation the class of the annotation that will be retrieved + * @return the annotation instance if it is bound to the field; otherwise {@code null} + */ + public T getAnnotation(Class annotation) { + return getAnnotationFromArray(getAnnotations(), annotation); + } + + /** + * Return the annotations that are present on this field. + * + * @return an array of all the annotations set on the field + * @since 1.4 + */ + public Collection getAnnotations() { + if (annotations == null) { + Pair, String> key = new Pair, String>(declaringClazz, name); + Collection cachedValue = ANNOTATION_CACHE.getElement(key); + if (cachedValue == null) { + cachedValue = Collections.unmodifiableCollection( + Arrays.asList(field.getAnnotations())); + ANNOTATION_CACHE.addElement(key, cachedValue); + } + annotations = cachedValue; + } + return annotations; + } + + /** + * Returns {@code true} if the field is defined with the {@code modifier}. + * + *

This method is meant to be called as: + *

+   * boolean hasPublicModifier = fieldAttribute.hasModifier(java.lang.reflect.Modifier.PUBLIC);
+   * 
+ * + * @see java.lang.reflect.Modifier + */ + public boolean hasModifier(int modifier) { + return (modifiers & modifier) != 0; + } + + /** + * This is exposed internally only for the removing synthetic fields from the JSON output. + * + * @return true if the field is synthetic; otherwise false + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + Object get(Object instance) throws IllegalAccessException { + return field.get(instance); + } + + /** + * This is exposed internally only for the removing synthetic fields from the JSON output. + * + * @return true if the field is synthetic; otherwise false + */ + boolean isSynthetic() { + return isSynthetic; + } + + /** + * @deprecated remove this when {@link FieldNamingStrategy} is deleted. + */ + @Deprecated + Field getFieldObject() { + return field; + } + + @SuppressWarnings("unchecked") + private static T getAnnotationFromArray( + Collection annotations, Class annotation) { + for (Annotation a : annotations) { + if (a.annotationType() == annotation) { + return (T) a; + } + } + return null; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/FieldNamingPolicy.java b/src/com/massivecraft/core/lib/gson2/FieldNamingPolicy.java new file mode 100755 index 00000000..cd17671f --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/FieldNamingPolicy.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +/** + * An enumeration that defines a few standard naming conventions for JSON field names. + * This enumeration should be used in conjunction with {@link com.massivecraft.core.lib.gson2.GsonBuilder} + * to configure a {@link com.massivecraft.core.lib.gson2.Gson} instance to properly translate Java field + * names into the desired JSON field names. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public enum FieldNamingPolicy { + /** + * Using this naming policy with Gson will ensure that the first "letter" of the Java + * field name is capitalized when serialized to its JSON form. + * + *

Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

+ *
    + *
  • someFieldName ---> SomeFieldName
  • + *
  • _someFieldName ---> _SomeFieldName
  • + *
+ */ + UPPER_CAMEL_CASE(new ModifyFirstLetterNamingPolicy( + ModifyFirstLetterNamingPolicy.LetterModifier.UPPER)), + + /** + * Using this naming policy with Gson will ensure that the first "letter" of the Java + * field name is capitalized when serialized to its JSON form and the words will be + * separated by a space. + * + *

Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

+ *
    + *
  • someFieldName ---> Some Field Name
  • + *
  • _someFieldName ---> _Some Field Name
  • + *
+ * + * @since 1.4 + */ + UPPER_CAMEL_CASE_WITH_SPACES(new UpperCamelCaseSeparatorNamingPolicy(" ")), + + /** + * Using this naming policy with Gson will modify the Java Field name from its camel cased + * form to a lower case field name where each word is separated by an underscore (_). + * + *

Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

+ *
    + *
  • someFieldName ---> some_field_name
  • + *
  • _someFieldName ---> _some_field_name
  • + *
  • aStringField ---> a_string_field
  • + *
  • aURL ---> a_u_r_l
  • + *
+ */ + LOWER_CASE_WITH_UNDERSCORES(new LowerCamelCaseSeparatorNamingPolicy("_")), + + /** + * Using this naming policy with Gson will modify the Java Field name from its camel cased + * form to a lower case field name where each word is separated by a dash (-). + * + *

Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

+ *
    + *
  • someFieldName ---> some-field-name
  • + *
  • _someFieldName ---> _some-field-name
  • + *
  • aStringField ---> a-string-field
  • + *
  • aURL ---> a-u-r-l
  • + *
+ * Using dashes in JavaScript is not recommended since dash is also used for a minus sign in + * expressions. This requires that a field named with dashes is always accessed as a quoted + * property like {@code myobject['my-field']}. Accessing it as an object field + * {@code myobject.my-field} will result in an unintended javascript expression. + * @since 1.4 + */ + LOWER_CASE_WITH_DASHES(new LowerCamelCaseSeparatorNamingPolicy("-")); + + private final FieldNamingStrategy2 namingPolicy; + + private FieldNamingPolicy(FieldNamingStrategy2 namingPolicy) { + this.namingPolicy = namingPolicy; + } + + FieldNamingStrategy2 getFieldNamingPolicy() { + return namingPolicy; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/FieldNamingStrategy.java b/src/com/massivecraft/core/lib/gson2/FieldNamingStrategy.java new file mode 100755 index 00000000..02ad9528 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/FieldNamingStrategy.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.lang.reflect.Field; + +/** + * A mechanism for providing custom field naming in Gson. This allows the client code to translate + * field names into a particular convention that is not supported as a normal Java field + * declaration rules. For example, Java does not support "-" characters in a field name. + * + * @author Inderjeet Singh + * @author Joel Leitch + * @since 1.3 + */ +public interface FieldNamingStrategy { + + /** + * Translates the field name into its JSON field name representation. + * + * @param f the field object that we are translating + * @return the translated field name. + * @since 1.3 + */ + public String translateName(Field f); +} diff --git a/src/com/massivecraft/core/lib/gson2/FieldNamingStrategy2.java b/src/com/massivecraft/core/lib/gson2/FieldNamingStrategy2.java new file mode 100755 index 00000000..0768c84a --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/FieldNamingStrategy2.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +/** + * The new mechanism for providing custom field naming in Gson. This allows the client code + * to translate field names into a particular convention that is not supported as a normal + * Java field declaration rules. For example, Java does not support "-" characters in a + * field name. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +interface FieldNamingStrategy2 { + + /** + * Translates the field name into its JSON field name representation. + * + * @param f the field that is being translated + * @return the translated field name. + */ + public String translateName(FieldAttributes f); +} diff --git a/src/com/massivecraft/core/lib/gson2/FieldNamingStrategy2Adapter.java b/src/com/massivecraft/core/lib/gson2/FieldNamingStrategy2Adapter.java new file mode 100755 index 00000000..19d58c00 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/FieldNamingStrategy2Adapter.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.internal.$Gson$Preconditions; + +/** + * Adapts the old FieldNamingStrategy to the new {@link FieldNamingStrategy2} + * type. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +final class FieldNamingStrategy2Adapter implements FieldNamingStrategy2 { + private final FieldNamingStrategy adaptee; + + FieldNamingStrategy2Adapter(FieldNamingStrategy adaptee) { + this.adaptee = $Gson$Preconditions.checkNotNull(adaptee); + } + + @SuppressWarnings("deprecation") + public String translateName(FieldAttributes f) { + return adaptee.translateName(f.getFieldObject()); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/Gson.java b/src/com/massivecraft/core/lib/gson2/Gson.java new file mode 100755 index 00000000..b7bf37cc --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/Gson.java @@ -0,0 +1,802 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.internal.ConstructorConstructor; +import com.massivecraft.core.lib.gson2.internal.ParameterizedTypeHandlerMap; +import com.massivecraft.core.lib.gson2.internal.Primitives; +import com.massivecraft.core.lib.gson2.internal.Streams; +import com.massivecraft.core.lib.gson2.internal.bind.ArrayTypeAdapter; +import com.massivecraft.core.lib.gson2.internal.bind.BigDecimalTypeAdapter; +import com.massivecraft.core.lib.gson2.internal.bind.BigIntegerTypeAdapter; +import com.massivecraft.core.lib.gson2.internal.bind.CollectionTypeAdapterFactory; +import com.massivecraft.core.lib.gson2.internal.bind.DateTypeAdapter; +import com.massivecraft.core.lib.gson2.internal.bind.ExcludedTypeAdapterFactory; +import com.massivecraft.core.lib.gson2.internal.bind.JsonElementReader; +import com.massivecraft.core.lib.gson2.internal.bind.JsonElementWriter; +import com.massivecraft.core.lib.gson2.internal.bind.MapTypeAdapterFactory; +import com.massivecraft.core.lib.gson2.internal.bind.MiniGson; +import com.massivecraft.core.lib.gson2.internal.bind.ObjectTypeAdapter; +import com.massivecraft.core.lib.gson2.internal.bind.ReflectiveTypeAdapterFactory; +import com.massivecraft.core.lib.gson2.internal.bind.SqlDateTypeAdapter; +import com.massivecraft.core.lib.gson2.internal.bind.TimeTypeAdapter; +import com.massivecraft.core.lib.gson2.internal.bind.TypeAdapter; +import com.massivecraft.core.lib.gson2.internal.bind.TypeAdapters; +import com.massivecraft.core.lib.gson2.reflect.TypeToken; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; +import com.massivecraft.core.lib.gson2.stream.MalformedJsonException; + +import java.io.EOFException; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * This is the main class for using Gson. Gson is typically used by first constructing a + * Gson instance and then invoking {@link #toJson(Object)} or {@link #fromJson(String, Class)} + * methods on it. + * + *

You can create a Gson instance by invoking {@code new Gson()} if the default configuration + * is all you need. You can also use {@link GsonBuilder} to build a Gson instance with various + * configuration options such as versioning support, pretty printing, custom + * {@link JsonSerializer}s, {@link JsonDeserializer}s, and {@link InstanceCreator}s.

+ * + *

Here is an example of how Gson is used for a simple Class: + * + *

+ * Gson gson = new Gson(); // Or use new GsonBuilder().create();
+ * MyType target = new MyType();
+ * String json = gson.toJson(target); // serializes target to Json
+ * MyType target2 = gson.fromJson(json, MyType.class); // deserializes json into target2
+ * 

+ * + *

If the object that your are serializing/deserializing is a {@code ParameterizedType} + * (i.e. contains at least one type parameter and may be an array) then you must use the + * {@link #toJson(Object, Type)} or {@link #fromJson(String, Type)} method. Here is an + * example for serializing and deserialing a {@code ParameterizedType}: + * + *

+ * Type listType = new TypeToken<List<String>>() {}.getType();
+ * List<String> target = new LinkedList<String>();
+ * target.add("blah");
+ *
+ * Gson gson = new Gson();
+ * String json = gson.toJson(target, listType);
+ * List<String> target2 = gson.fromJson(json, listType);
+ * 

+ * + *

See the Gson User Guide + * for a more complete set of examples.

+ * + * @see com.massivecraft.core.lib.gson2.reflect.TypeToken + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public final class Gson { + @SuppressWarnings("rawtypes") + static final ParameterizedTypeHandlerMap EMPTY_MAP = + new ParameterizedTypeHandlerMap().makeUnmodifiable(); + + static final boolean DEFAULT_JSON_NON_EXECUTABLE = false; + + // Default instances of plug-ins + static final AnonymousAndLocalClassExclusionStrategy DEFAULT_ANON_LOCAL_CLASS_EXCLUSION_STRATEGY = + new AnonymousAndLocalClassExclusionStrategy(); + static final SyntheticFieldExclusionStrategy DEFAULT_SYNTHETIC_FIELD_EXCLUSION_STRATEGY = + new SyntheticFieldExclusionStrategy(true); + static final ModifierBasedExclusionStrategy DEFAULT_MODIFIER_BASED_EXCLUSION_STRATEGY = + new ModifierBasedExclusionStrategy(Modifier.TRANSIENT, Modifier.STATIC); + static final FieldNamingStrategy2 DEFAULT_NAMING_POLICY = + new SerializedNameAnnotationInterceptingNamingPolicy(new JavaFieldNamingPolicy()); + + private static final ExclusionStrategy DEFAULT_EXCLUSION_STRATEGY = createExclusionStrategy(); + + private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n"; + + private final ExclusionStrategy deserializationExclusionStrategy; + private final ExclusionStrategy serializationExclusionStrategy; + private final ConstructorConstructor constructorConstructor; + + /** Map containing Type or Class objects as keys */ + private final ParameterizedTypeHandlerMap> serializers; + + /** Map containing Type or Class objects as keys */ + private final ParameterizedTypeHandlerMap> deserializers; + + private final boolean serializeNulls; + private final boolean htmlSafe; + private final boolean generateNonExecutableJson; + private final boolean prettyPrinting; + + private final MiniGson miniGson; + + /** + * Constructs a Gson object with default configuration. The default configuration has the + * following settings: + *
    + *
  • The JSON generated by toJson methods is in compact representation. This + * means that all the unneeded white-space is removed. You can change this behavior with + * {@link GsonBuilder#setPrettyPrinting()}.
  • + *
  • The generated JSON omits all the fields that are null. Note that nulls in arrays are + * kept as is since an array is an ordered list. Moreover, if a field is not null, but its + * generated JSON is empty, the field is kept. You can configure Gson to serialize null values + * by setting {@link GsonBuilder#serializeNulls()}.
  • + *
  • Gson provides default serialization and deserialization for Enums, {@link Map}, + * {@link java.net.URL}, {@link java.net.URI}, {@link java.util.Locale}, {@link java.util.Date}, + * {@link java.math.BigDecimal}, and {@link java.math.BigInteger} classes. If you would prefer + * to change the default representation, you can do so by registering a type adapter through + * {@link GsonBuilder#registerTypeAdapter(Type, Object)}.
  • + *
  • The default Date format is same as {@link java.text.DateFormat#DEFAULT}. This format + * ignores the millisecond portion of the date during serialization. You can change + * this by invoking {@link GsonBuilder#setDateFormat(int)} or + * {@link GsonBuilder#setDateFormat(String)}.
  • + *
  • By default, Gson ignores the {@link com.massivecraft.core.lib.gson2.annotations.Expose} annotation. + * You can enable Gson to serialize/deserialize only those fields marked with this annotation + * through {@link GsonBuilder#excludeFieldsWithoutExposeAnnotation()}.
  • + *
  • By default, Gson ignores the {@link com.massivecraft.core.lib.gson2.annotations.Since} annotation. You + * can enable Gson to use this annotation through {@link GsonBuilder#setVersion(double)}.
  • + *
  • The default field naming policy for the output Json is same as in Java. So, a Java class + * field versionNumber will be output as "versionNumber@quot; in + * Json. The same rules are applied for mapping incoming Json to the Java classes. You can + * change this policy through {@link GsonBuilder#setFieldNamingPolicy(FieldNamingPolicy)}.
  • + *
  • By default, Gson excludes transient or static fields from + * consideration for serialization and deserialization. You can change this behavior through + * {@link GsonBuilder#excludeFieldsWithModifiers(int...)}.
  • + *
+ */ + @SuppressWarnings("unchecked") + public Gson() { + this(DEFAULT_EXCLUSION_STRATEGY, DEFAULT_EXCLUSION_STRATEGY, DEFAULT_NAMING_POLICY, + EMPTY_MAP, false, EMPTY_MAP, EMPTY_MAP, false, DEFAULT_JSON_NON_EXECUTABLE, true, + false, false, LongSerializationPolicy.DEFAULT, + Collections.emptyList()); + } + + Gson(final ExclusionStrategy deserializationExclusionStrategy, + final ExclusionStrategy serializationExclusionStrategy, + final FieldNamingStrategy2 fieldNamingPolicy, + final ParameterizedTypeHandlerMap> instanceCreators, boolean serializeNulls, + final ParameterizedTypeHandlerMap> serializers, + final ParameterizedTypeHandlerMap> deserializers, + boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe, + boolean prettyPrinting, boolean serializeSpecialFloatingPointValues, + LongSerializationPolicy longSerializationPolicy, + List typeAdapterFactories) { + this.deserializationExclusionStrategy = deserializationExclusionStrategy; + this.serializationExclusionStrategy = serializationExclusionStrategy; + this.constructorConstructor = new ConstructorConstructor(instanceCreators); + this.serializeNulls = serializeNulls; + this.serializers = serializers; + this.deserializers = deserializers; + this.generateNonExecutableJson = generateNonExecutableGson; + this.htmlSafe = htmlSafe; + this.prettyPrinting = prettyPrinting; + + /* + TODO: for serialization, honor: + serializationExclusionStrategy + fieldNamingPolicy + serializeNulls + serializers + */ + TypeAdapter.Factory reflectiveTypeAdapterFactory + = new ReflectiveTypeAdapterFactory(constructorConstructor) { + @Override + public String getFieldName(Class declaringClazz, Field f, Type declaredType) { + return fieldNamingPolicy.translateName(new FieldAttributes(declaringClazz, f)); + } + @Override + public boolean serializeField(Class declaringClazz, Field f, Type declaredType) { + ExclusionStrategy strategy = Gson.this.serializationExclusionStrategy; + return !strategy.shouldSkipClass(f.getType()) + && !strategy.shouldSkipField(new FieldAttributes(declaringClazz, f)); + } + + @Override + public boolean deserializeField(Class declaringClazz, Field f, Type declaredType) { + ExclusionStrategy strategy = Gson.this.deserializationExclusionStrategy; + return !strategy.shouldSkipClass(f.getType()) + && !strategy.shouldSkipField(new FieldAttributes(declaringClazz, f)); + } + }; + + MiniGson.Builder builder = new MiniGson.Builder() + .withoutDefaultFactories() + .factory(TypeAdapters.STRING_FACTORY) + .factory(TypeAdapters.INTEGER_FACTORY) + .factory(TypeAdapters.BOOLEAN_FACTORY) + .factory(TypeAdapters.BYTE_FACTORY) + .factory(TypeAdapters.SHORT_FACTORY) + .factory(TypeAdapters.newFactory(long.class, Long.class, + longAdapter(longSerializationPolicy))) + .factory(TypeAdapters.newFactory(double.class, Double.class, + doubleAdapter(serializeSpecialFloatingPointValues))) + .factory(TypeAdapters.newFactory(float.class, Float.class, + floatAdapter(serializeSpecialFloatingPointValues))) + .factory(new ExcludedTypeAdapterFactory( + serializationExclusionStrategy, deserializationExclusionStrategy)) + .factory(TypeAdapters.NUMBER_FACTORY) + .factory(TypeAdapters.CHARACTER_FACTORY) + .factory(TypeAdapters.STRING_BUILDER_FACTORY) + .factory(TypeAdapters.STRING_BUFFER_FACTORY) + .typeAdapter(BigDecimal.class, new BigDecimalTypeAdapter()) + .typeAdapter(BigInteger.class, new BigIntegerTypeAdapter()) + .factory(TypeAdapters.JSON_ELEMENT_FACTORY) + .factory(ObjectTypeAdapter.FACTORY); + + for (TypeAdapter.Factory factory : typeAdapterFactories) { + builder.factory(factory); + } + + builder + .factory(new GsonToMiniGsonTypeAdapterFactory(this, serializers, deserializers)) + .factory(new CollectionTypeAdapterFactory(constructorConstructor)) + .factory(TypeAdapters.URL_FACTORY) + .factory(TypeAdapters.URI_FACTORY) + .factory(TypeAdapters.UUID_FACTORY) + .factory(TypeAdapters.LOCALE_FACTORY) + .factory(TypeAdapters.INET_ADDRESS_FACTORY) + .factory(TypeAdapters.BIT_SET_FACTORY) + .factory(DateTypeAdapter.FACTORY) + .factory(TypeAdapters.CALENDAR_FACTORY) + .factory(TimeTypeAdapter.FACTORY) + .factory(SqlDateTypeAdapter.FACTORY) + .factory(TypeAdapters.TIMESTAMP_FACTORY) + .factory(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization)) + .factory(ArrayTypeAdapter.FACTORY) + .factory(TypeAdapters.ENUM_FACTORY) + .factory(reflectiveTypeAdapterFactory); + + this.miniGson = builder.build(); + } + + private TypeAdapter doubleAdapter(boolean serializeSpecialFloatingPointValues) { + if (serializeSpecialFloatingPointValues) { + return TypeAdapters.DOUBLE; + } + return new TypeAdapter() { + @Override public Double read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + return reader.nextDouble(); + } + @Override public void write(JsonWriter writer, Number value) throws IOException { + if (value == null) { + writer.nullValue(); // TODO: better policy here? + return; + } + double doubleValue = value.doubleValue(); + checkValidFloatingPoint(doubleValue); + writer.value(value); + } + }; + } + + private TypeAdapter floatAdapter(boolean serializeSpecialFloatingPointValues) { + if (serializeSpecialFloatingPointValues) { + return TypeAdapters.FLOAT; + } + return new TypeAdapter() { + @Override public Float read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + return (float) reader.nextDouble(); + } + @Override public void write(JsonWriter writer, Number value) throws IOException { + if (value == null) { + writer.nullValue(); // TODO: better policy here? + return; + } + float floatValue = value.floatValue(); + checkValidFloatingPoint(floatValue); + writer.value(value); + } + }; + } + + private void checkValidFloatingPoint(double value) { + if (Double.isNaN(value) || Double.isInfinite(value)) { + throw new IllegalArgumentException(value + + " is not a valid double value as per JSON specification. To override this" + + " behavior, use GsonBuilder.serializeSpecialDoubleValues() method."); + } + } + + private TypeAdapter longAdapter(LongSerializationPolicy longSerializationPolicy) { + if (longSerializationPolicy == LongSerializationPolicy.DEFAULT) { + return TypeAdapters.LONG; + } + return new TypeAdapter() { + @Override public Number read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + return reader.nextLong(); + } + @Override public void write(JsonWriter writer, Number value) throws IOException { + if (value == null) { + writer.nullValue(); // TODO: better policy here? + return; + } + writer.value(value.toString()); + } + }; + } + + private static ExclusionStrategy createExclusionStrategy() { + List strategies = new LinkedList(); + strategies.add(DEFAULT_ANON_LOCAL_CLASS_EXCLUSION_STRATEGY); + strategies.add(DEFAULT_SYNTHETIC_FIELD_EXCLUSION_STRATEGY); + strategies.add(DEFAULT_MODIFIER_BASED_EXCLUSION_STRATEGY); + return new DisjunctionExclusionStrategy(strategies); + } + + /** + * This method serializes the specified object into its equivalent representation as a tree of + * {@link JsonElement}s. This method should be used when the specified object is not a generic + * type. This method uses {@link Class#getClass()} to get the type for the specified object, but + * the {@code getClass()} loses the generic type information because of the Type Erasure feature + * of Java. Note that this method works fine if the any of the object fields are of generic type, + * just the object itself should not be of a generic type. If the object is of generic type, use + * {@link #toJsonTree(Object, Type)} instead. + * + * @param src the object for which Json representation is to be created setting for Gson + * @return Json representation of {@code src}. + * @since 1.4 + */ + public JsonElement toJsonTree(Object src) { + if (src == null) { + return JsonNull.INSTANCE; + } + return toJsonTree(src, src.getClass()); + } + + /** + * This method serializes the specified object, including those of generic types, into its + * equivalent representation as a tree of {@link JsonElement}s. This method must be used if the + * specified object is a generic type. For non-generic objects, use {@link #toJsonTree(Object)} + * instead. + * + * @param src the object for which JSON representation is to be created + * @param typeOfSrc The specific genericized type of src. You can obtain + * this type by using the {@link com.massivecraft.core.lib.gson2.reflect.TypeToken} class. For example, + * to get the type for {@code Collection}, you should use: + *
+   * Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType();
+   * 
+ * @return Json representation of {@code src} + * @since 1.4 + */ + @SuppressWarnings({"unchecked", "rawtypes"}) // the caller is required to make src and typeOfSrc consistent + public JsonElement toJsonTree(Object src, Type typeOfSrc) { + JsonElementWriter writer = new JsonElementWriter(); + toJson(src, typeOfSrc, writer); + return writer.get(); + } + + /** + * This method serializes the specified object into its equivalent Json representation. + * This method should be used when the specified object is not a generic type. This method uses + * {@link Class#getClass()} to get the type for the specified object, but the + * {@code getClass()} loses the generic type information because of the Type Erasure feature + * of Java. Note that this method works fine if the any of the object fields are of generic type, + * just the object itself should not be of a generic type. If the object is of generic type, use + * {@link #toJson(Object, Type)} instead. If you want to write out the object to a + * {@link Writer}, use {@link #toJson(Object, Appendable)} instead. + * + * @param src the object for which Json representation is to be created setting for Gson + * @return Json representation of {@code src}. + */ + public String toJson(Object src) { + if (src == null) { + return toJson(JsonNull.INSTANCE); + } + return toJson(src, src.getClass()); + } + + /** + * This method serializes the specified object, including those of generic types, into its + * equivalent Json representation. This method must be used if the specified object is a generic + * type. For non-generic objects, use {@link #toJson(Object)} instead. If you want to write out + * the object to a {@link Appendable}, use {@link #toJson(Object, Type, Appendable)} instead. + * + * @param src the object for which JSON representation is to be created + * @param typeOfSrc The specific genericized type of src. You can obtain + * this type by using the {@link com.massivecraft.core.lib.gson2.reflect.TypeToken} class. For example, + * to get the type for {@code Collection}, you should use: + *
+   * Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType();
+   * 
+ * @return Json representation of {@code src} + */ + public String toJson(Object src, Type typeOfSrc) { + StringWriter writer = new StringWriter(); + toJson(src, typeOfSrc, writer); + return writer.toString(); + } + + /** + * This method serializes the specified object into its equivalent Json representation. + * This method should be used when the specified object is not a generic type. This method uses + * {@link Class#getClass()} to get the type for the specified object, but the + * {@code getClass()} loses the generic type information because of the Type Erasure feature + * of Java. Note that this method works fine if the any of the object fields are of generic type, + * just the object itself should not be of a generic type. If the object is of generic type, use + * {@link #toJson(Object, Type, Appendable)} instead. + * + * @param src the object for which Json representation is to be created setting for Gson + * @param writer Writer to which the Json representation needs to be written + * @throws JsonIOException if there was a problem writing to the writer + * @since 1.2 + */ + public void toJson(Object src, Appendable writer) throws JsonIOException { + if (src != null) { + toJson(src, src.getClass(), writer); + } else { + toJson(JsonNull.INSTANCE, writer); + } + } + + /** + * This method serializes the specified object, including those of generic types, into its + * equivalent Json representation. This method must be used if the specified object is a generic + * type. For non-generic objects, use {@link #toJson(Object, Appendable)} instead. + * + * @param src the object for which JSON representation is to be created + * @param typeOfSrc The specific genericized type of src. You can obtain + * this type by using the {@link com.massivecraft.core.lib.gson2.reflect.TypeToken} class. For example, + * to get the type for {@code Collection}, you should use: + *
+   * Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType();
+   * 
+ * @param writer Writer to which the Json representation of src needs to be written. + * @throws JsonIOException if there was a problem writing to the writer + * @since 1.2 + */ + public void toJson(Object src, Type typeOfSrc, Appendable writer) throws JsonIOException { + try { + JsonWriter jsonWriter = newJsonWriter(Streams.writerForAppendable(writer)); + toJson(src, typeOfSrc, jsonWriter); + } catch (IOException e) { + throw new JsonIOException(e); + } + } + + /** + * Writes the JSON representation of {@code src} of type {@code typeOfSrc} to + * {@code writer}. + * @throws JsonIOException if there was a problem writing to the writer + */ + @SuppressWarnings("unchecked") + public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException { + TypeAdapter adapter = miniGson.getAdapter(TypeToken.get(typeOfSrc)); + boolean oldLenient = writer.isLenient(); + writer.setLenient(true); + boolean oldHtmlSafe = writer.isHtmlSafe(); + writer.setHtmlSafe(htmlSafe); + boolean oldSerializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(serializeNulls); + try { + ((TypeAdapter) adapter).write(writer, src); + } catch (IOException e) { + throw new JsonIOException(e); + } finally { + writer.setLenient(oldLenient); + writer.setHtmlSafe(oldHtmlSafe); + writer.setSerializeNulls(oldSerializeNulls); + } + } + + /** + * Converts a tree of {@link JsonElement}s into its equivalent JSON representation. + * + * @param jsonElement root of a tree of {@link JsonElement}s + * @return JSON String representation of the tree + * @since 1.4 + */ + public String toJson(JsonElement jsonElement) { + StringWriter writer = new StringWriter(); + toJson(jsonElement, writer); + return writer.toString(); + } + + /** + * Writes out the equivalent JSON for a tree of {@link JsonElement}s. + * + * @param jsonElement root of a tree of {@link JsonElement}s + * @param writer Writer to which the Json representation needs to be written + * @throws JsonIOException if there was a problem writing to the writer + * @since 1.4 + */ + public void toJson(JsonElement jsonElement, Appendable writer) throws JsonIOException { + try { + JsonWriter jsonWriter = newJsonWriter(Streams.writerForAppendable(writer)); + toJson(jsonElement, jsonWriter); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Returns a new JSON writer configured for this GSON and with the non-execute + * prefix if that is configured. + */ + private JsonWriter newJsonWriter(Writer writer) throws IOException { + if (generateNonExecutableJson) { + writer.write(JSON_NON_EXECUTABLE_PREFIX); + } + JsonWriter jsonWriter = new JsonWriter(writer); + if (prettyPrinting) { + jsonWriter.setIndent(" "); + } + jsonWriter.setSerializeNulls(serializeNulls); + return jsonWriter; + } + + /** + * Writes the JSON for {@code jsonElement} to {@code writer}. + * @throws JsonIOException if there was a problem writing to the writer + */ + public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException { + boolean oldLenient = writer.isLenient(); + writer.setLenient(true); + boolean oldHtmlSafe = writer.isHtmlSafe(); + writer.setHtmlSafe(htmlSafe); + boolean oldSerializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(serializeNulls); + try { + Streams.write(jsonElement, writer); + } catch (IOException e) { + throw new JsonIOException(e); + } finally { + writer.setLenient(oldLenient); + writer.setHtmlSafe(oldHtmlSafe); + writer.setSerializeNulls(oldSerializeNulls); + } + } + + /** + * This method deserializes the specified Json into an object of the specified class. It is not + * suitable to use if the specified class is a generic type since it will not have the generic + * type information because of the Type Erasure feature of Java. Therefore, this method should not + * be used if the desired type is a generic type. Note that this method works fine if the any of + * the fields of the specified object are generics, just the object itself should not be a + * generic type. For the cases when the object is of generic type, invoke + * {@link #fromJson(String, Type)}. If you have the Json in a {@link Reader} instead of + * a String, use {@link #fromJson(Reader, Class)} instead. + * + * @param the type of the desired object + * @param json the string from which the object is to be deserialized + * @param classOfT the class of T + * @return an object of type T from the string + * @throws JsonSyntaxException if json is not a valid representation for an object of type + * classOfT + */ + public T fromJson(String json, Class classOfT) throws JsonSyntaxException { + Object object = fromJson(json, (Type) classOfT); + return Primitives.wrap(classOfT).cast(object); + } + + /** + * This method deserializes the specified Json into an object of the specified type. This method + * is useful if the specified object is a generic type. For non-generic objects, use + * {@link #fromJson(String, Class)} instead. If you have the Json in a {@link Reader} instead of + * a String, use {@link #fromJson(Reader, Type)} instead. + * + * @param the type of the desired object + * @param json the string from which the object is to be deserialized + * @param typeOfT The specific genericized type of src. You can obtain this type by using the + * {@link com.massivecraft.core.lib.gson2.reflect.TypeToken} class. For example, to get the type for + * {@code Collection}, you should use: + *
+   * Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType();
+   * 
+ * @return an object of type T from the string + * @throws JsonParseException if json is not a valid representation for an object of type typeOfT + * @throws JsonSyntaxException if json is not a valid representation for an object of type + */ + @SuppressWarnings("unchecked") + public T fromJson(String json, Type typeOfT) throws JsonSyntaxException { + if (json == null) { + return null; + } + StringReader reader = new StringReader(json); + T target = (T) fromJson(reader, typeOfT); + return target; + } + + /** + * This method deserializes the Json read from the specified reader into an object of the + * specified class. It is not suitable to use if the specified class is a generic type since it + * will not have the generic type information because of the Type Erasure feature of Java. + * Therefore, this method should not be used if the desired type is a generic type. Note that + * this method works fine if the any of the fields of the specified object are generics, just the + * object itself should not be a generic type. For the cases when the object is of generic type, + * invoke {@link #fromJson(Reader, Type)}. If you have the Json in a String form instead of a + * {@link Reader}, use {@link #fromJson(String, Class)} instead. + * + * @param the type of the desired object + * @param json the reader producing the Json from which the object is to be deserialized. + * @param classOfT the class of T + * @return an object of type T from the string + * @throws JsonIOException if there was a problem reading from the Reader + * @throws JsonSyntaxException if json is not a valid representation for an object of type + * @since 1.2 + */ + public T fromJson(Reader json, Class classOfT) throws JsonSyntaxException, JsonIOException { + JsonReader jsonReader = new JsonReader(json); + Object object = fromJson(jsonReader, classOfT); + assertFullConsumption(object, jsonReader); + return Primitives.wrap(classOfT).cast(object); + } + + /** + * This method deserializes the Json read from the specified reader into an object of the + * specified type. This method is useful if the specified object is a generic type. For + * non-generic objects, use {@link #fromJson(Reader, Class)} instead. If you have the Json in a + * String form instead of a {@link Reader}, use {@link #fromJson(String, Type)} instead. + * + * @param the type of the desired object + * @param json the reader producing Json from which the object is to be deserialized + * @param typeOfT The specific genericized type of src. You can obtain this type by using the + * {@link com.massivecraft.core.lib.gson2.reflect.TypeToken} class. For example, to get the type for + * {@code Collection}, you should use: + *
+   * Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType();
+   * 
+ * @return an object of type T from the json + * @throws JsonIOException if there was a problem reading from the Reader + * @throws JsonSyntaxException if json is not a valid representation for an object of type + * @since 1.2 + */ + public T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException { + JsonReader jsonReader = new JsonReader(json); + T object = (T) fromJson(jsonReader, typeOfT); + assertFullConsumption(object, jsonReader); + return object; + } + + private static void assertFullConsumption(Object obj, JsonReader reader) { + try { + if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) { + throw new JsonIOException("JSON document was not fully consumed."); + } + } catch (MalformedJsonException e) { + throw new JsonSyntaxException(e); + } catch (IOException e) { + throw new JsonIOException(e); + } + } + + /** + * Reads the next JSON value from {@code reader} and convert it to an object + * of type {@code typeOfT}. + * Since Type is not parameterized by T, this method is type unsafe and should be used carefully + * + * @throws JsonIOException if there was a problem writing to the Reader + * @throws JsonSyntaxException if json is not a valid representation for an object of type + */ + @SuppressWarnings("unchecked") + public T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException { + boolean isEmpty = true; + boolean oldLenient = reader.isLenient(); + reader.setLenient(true); + try { + reader.peek(); + isEmpty = false; + TypeAdapter typeAdapter = (TypeAdapter) miniGson.getAdapter(TypeToken.get(typeOfT)); + return typeAdapter.read(reader); + } catch (EOFException e) { + /* + * For compatibility with JSON 1.5 and earlier, we return null for empty + * documents instead of throwing. + */ + if (isEmpty) { + return null; + } + throw new JsonSyntaxException(e); + } catch (IllegalStateException e) { + throw new JsonSyntaxException(e); + } catch (IOException e) { + // TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxException + throw new JsonSyntaxException(e); + } finally { + reader.setLenient(oldLenient); + } + } + + /** + * This method deserializes the Json read from the specified parse tree into an object of the + * specified type. It is not suitable to use if the specified class is a generic type since it + * will not have the generic type information because of the Type Erasure feature of Java. + * Therefore, this method should not be used if the desired type is a generic type. Note that + * this method works fine if the any of the fields of the specified object are generics, just the + * object itself should not be a generic type. For the cases when the object is of generic type, + * invoke {@link #fromJson(JsonElement, Type)}. + * @param the type of the desired object + * @param json the root of the parse tree of {@link JsonElement}s from which the object is to + * be deserialized + * @param classOfT The class of T + * @return an object of type T from the json + * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT + * @since 1.3 + */ + public T fromJson(JsonElement json, Class classOfT) throws JsonSyntaxException { + Object object = fromJson(json, (Type) classOfT); + return Primitives.wrap(classOfT).cast(object); + } + + /** + * This method deserializes the Json read from the specified parse tree into an object of the + * specified type. This method is useful if the specified object is a generic type. For + * non-generic objects, use {@link #fromJson(JsonElement, Class)} instead. + * + * @param the type of the desired object + * @param json the root of the parse tree of {@link JsonElement}s from which the object is to + * be deserialized + * @param typeOfT The specific genericized type of src. You can obtain this type by using the + * {@link com.massivecraft.core.lib.gson2.reflect.TypeToken} class. For example, to get the type for + * {@code Collection}, you should use: + *
+   * Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType();
+   * 
+ * @return an object of type T from the json + * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT + * @since 1.3 + */ + public T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException { + if (json == null) { + return null; + } + return (T) fromJson(new JsonElementReader(json), typeOfT); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("{") + .append("serializeNulls:").append(serializeNulls) + .append(",serializers:").append(serializers) + .append(",deserializers:").append(deserializers) + + // using the name instanceCreator instead of ObjectConstructor since the users of Gson are + // more familiar with the concept of Instance Creators. Moreover, the objectConstructor is + // just a utility class around instance creators, and its toString() only displays them. + .append(",instanceCreators:").append(constructorConstructor) + .append("}"); + return sb.toString(); + } + +} diff --git a/src/com/massivecraft/core/lib/gson2/GsonBuilder.java b/src/com/massivecraft/core/lib/gson2/GsonBuilder.java new file mode 100755 index 00000000..0c3961e9 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/GsonBuilder.java @@ -0,0 +1,717 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.DefaultTypeAdapters.DefaultDateTypeAdapter; +import com.massivecraft.core.lib.gson2.internal.$Gson$Preconditions; +import com.massivecraft.core.lib.gson2.internal.ParameterizedTypeHandlerMap; +import com.massivecraft.core.lib.gson2.internal.Primitives; +import com.massivecraft.core.lib.gson2.internal.bind.TypeAdapter; + +import java.lang.reflect.Type; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + *

Use this builder to construct a {@link Gson} instance when you need to set configuration + * options other than the default. For {@link Gson} with default configuration, it is simpler to + * use {@code new Gson()}. {@code GsonBuilder} is best used by creating it, and then invoking its + * various configuration methods, and finally calling create.

+ * + *

The following is an example shows how to use the {@code GsonBuilder} to construct a Gson + * instance: + * + *

+ * Gson gson = new GsonBuilder()
+ *     .registerTypeAdapter(Id.class, new IdTypeAdapter())
+ *     .enableComplexMapKeySerialization()
+ *     .serializeNulls()
+ *     .setDateFormat(DateFormat.LONG)
+ *     .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
+ *     .setPrettyPrinting()
+ *     .setVersion(1.0)
+ *     .create();
+ * 

+ * + *

NOTES: + *

    + *
  • the order of invocation of configuration methods does not matter.
  • + *
  • The default serialization of {@link Date} and its subclasses in Gson does + * not contain time-zone information. So, if you are using date/time instances, + * use {@code GsonBuilder} and its {@code setDateFormat} methods.
  • + *
+ *

+ * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public final class GsonBuilder { + private static final InnerClassExclusionStrategy innerClassExclusionStrategy = + new InnerClassExclusionStrategy(); + private static final ExposeAnnotationDeserializationExclusionStrategy + exposeAnnotationDeserializationExclusionStrategy = + new ExposeAnnotationDeserializationExclusionStrategy(); + private static final ExposeAnnotationSerializationExclusionStrategy + exposeAnnotationSerializationExclusionStrategy = + new ExposeAnnotationSerializationExclusionStrategy(); + + private final Set serializeExclusionStrategies = + new HashSet(); + private final Set deserializeExclusionStrategies = + new HashSet(); + + private double ignoreVersionsAfter; + private ModifierBasedExclusionStrategy modifierBasedExclusionStrategy; + private boolean serializeInnerClasses; + private boolean excludeFieldsWithoutExposeAnnotation; + private LongSerializationPolicy longSerializationPolicy; + private FieldNamingStrategy2 fieldNamingPolicy; + private final ParameterizedTypeHandlerMap> instanceCreators; + private final ParameterizedTypeHandlerMap> serializers; + private final ParameterizedTypeHandlerMap> deserializers; + private final List typeAdapterFactories + = new ArrayList(); + private boolean serializeNulls; + private String datePattern; + private int dateStyle; + private int timeStyle; + private boolean complexMapKeySerialization = false; + private boolean serializeSpecialFloatingPointValues; + private boolean escapeHtmlChars; + private boolean prettyPrinting; + private boolean generateNonExecutableJson; + + /** + * Creates a GsonBuilder instance that can be used to build Gson with various configuration + * settings. GsonBuilder follows the builder pattern, and it is typically used by first + * invoking various configuration methods to set desired options, and finally calling + * {@link #create()}. + */ + public GsonBuilder() { + // add default exclusion strategies + deserializeExclusionStrategies.add(Gson.DEFAULT_ANON_LOCAL_CLASS_EXCLUSION_STRATEGY); + deserializeExclusionStrategies.add(Gson.DEFAULT_SYNTHETIC_FIELD_EXCLUSION_STRATEGY); + serializeExclusionStrategies.add(Gson.DEFAULT_ANON_LOCAL_CLASS_EXCLUSION_STRATEGY); + serializeExclusionStrategies.add(Gson.DEFAULT_SYNTHETIC_FIELD_EXCLUSION_STRATEGY); + + // setup default values + ignoreVersionsAfter = VersionConstants.IGNORE_VERSIONS; + serializeInnerClasses = true; + prettyPrinting = false; + escapeHtmlChars = true; + modifierBasedExclusionStrategy = Gson.DEFAULT_MODIFIER_BASED_EXCLUSION_STRATEGY; + excludeFieldsWithoutExposeAnnotation = false; + longSerializationPolicy = LongSerializationPolicy.DEFAULT; + fieldNamingPolicy = Gson.DEFAULT_NAMING_POLICY; + instanceCreators = new ParameterizedTypeHandlerMap>(); + serializers = new ParameterizedTypeHandlerMap>(); + deserializers = new ParameterizedTypeHandlerMap>(); + serializeNulls = false; + dateStyle = DateFormat.DEFAULT; + timeStyle = DateFormat.DEFAULT; + serializeSpecialFloatingPointValues = false; + generateNonExecutableJson = false; + } + + /** + * Configures Gson to enable versioning support. + * + * @param ignoreVersionsAfter any field or type marked with a version higher than this value + * are ignored during serialization or deserialization. + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + public GsonBuilder setVersion(double ignoreVersionsAfter) { + this.ignoreVersionsAfter = ignoreVersionsAfter; + return this; + } + + /** + * Configures Gson to excludes all class fields that have the specified modifiers. By default, + * Gson will exclude all fields marked transient or static. This method will override that + * behavior. + * + * @param modifiers the field modifiers. You must use the modifiers specified in the + * {@link java.lang.reflect.Modifier} class. For example, + * {@link java.lang.reflect.Modifier#TRANSIENT}, + * {@link java.lang.reflect.Modifier#STATIC}. + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + public GsonBuilder excludeFieldsWithModifiers(int... modifiers) { + modifierBasedExclusionStrategy = new ModifierBasedExclusionStrategy(modifiers); + return this; + } + + /** + * Makes the output JSON non-executable in Javascript by prefixing the generated JSON with some + * special text. This prevents attacks from third-party sites through script sourcing. See + * Gson Issue 42 + * for details. + * + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.3 + */ + public GsonBuilder generateNonExecutableJson() { + this.generateNonExecutableJson = true; + return this; + } + + /** + * Configures Gson to exclude all fields from consideration for serialization or deserialization + * that do not have the {@link com.massivecraft.core.lib.gson2.annotations.Expose} annotation. + * + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + public GsonBuilder excludeFieldsWithoutExposeAnnotation() { + excludeFieldsWithoutExposeAnnotation = true; + return this; + } + + /** + * Configure Gson to serialize null fields. By default, Gson omits all fields that are null + * during serialization. + * + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.2 + */ + public GsonBuilder serializeNulls() { + this.serializeNulls = true; + return this; + } + + /** + * Enabling this feature will only change the serialized form if the map key is + * a complex type (i.e. non-primitive) in its serialized JSON + * form. The default implementation of map serialization uses {@code toString()} + * on the key; however, when this is called then one of the following cases + * apply: + * + *

Maps as JSON objects

+ * For this case, assume that a type adapter is registered to serialize and + * deserialize some {@code Point} class, which contains an x and y coordinate, + * to/from the JSON Primitive string value {@code "(x,y)"}. The Java map would + * then be serialized as a {@link JsonObject}. + * + *

Below is an example: + *

  {@code
+   *   Gson gson = new GsonBuilder()
+   *       .register(Point.class, new MyPointTypeAdapter())
+   *       .enableComplexMapKeySerialization()
+   *       .create();
+   *
+   *   Map original = new LinkedHashMap();
+   *   original.put(new Point(5, 6), "a");
+   *   original.put(new Point(8, 8), "b");
+   *   System.out.println(gson.toJson(original, type));
+   * }
+ * The above code prints this JSON object:
  {@code
+   *   {
+   *     "(5,6)": "a",
+   *     "(8,8)": "b"
+   *   }
+   * }
+ * + *

Maps as JSON arrays

+ * For this case, assume that a type adapter was NOT registered for some + * {@code Point} class, but rather the default Gson serialization is applied. + * In this case, some {@code new Point(2,3)} would serialize as {@code + * {"x":2,"y":5}}. + * + *

Given the assumption above, a {@code Map} will be + * serialize as an array of arrays (can be viewed as an entry set of pairs). + * + *

Below is an example of serializing complex types as JSON arrays: + *

 {@code
+   *   Gson gson = new GsonBuilder()
+   *       .enableComplexMapKeySerialization()
+   *       .create();
+   *
+   *   Map original = new LinkedHashMap();
+   *   original.put(new Point(5, 6), "a");
+   *   original.put(new Point(8, 8), "b");
+   *   System.out.println(gson.toJson(original, type));
+   * }
+   *
+   * The JSON output would look as follows:
+   * 
   {@code
+   *   [
+   *     [
+   *       {
+   *         "x": 5,
+   *         "y": 6
+   *       },
+   *       "a"
+   *     ],
+   *     [
+   *       {
+   *         "x": 8,
+   *         "y": 8
+   *       },
+   *       "b"
+   *     ]
+   *   ]
+   * }
+ * + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.7 + */ + public GsonBuilder enableComplexMapKeySerialization() { + complexMapKeySerialization = true; + return this; + } + + /** + * Configures Gson to exclude inner classes during serialization. + * + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.3 + */ + public GsonBuilder disableInnerClassSerialization() { + serializeInnerClasses = false; + return this; + } + + /** + * Configures Gson to apply a specific serialization policy for {@code Long} and {@code long} + * objects. + * + * @param serializationPolicy the particular policy to use for serializing longs. + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.3 + */ + public GsonBuilder setLongSerializationPolicy(LongSerializationPolicy serializationPolicy) { + this.longSerializationPolicy = serializationPolicy; + return this; + } + + /** + * Configures Gson to apply a specific naming policy to an object's field during serialization + * and deserialization. + * + * @param namingConvention the JSON field naming convention to use for serialization and + * deserialization. + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + public GsonBuilder setFieldNamingPolicy(FieldNamingPolicy namingConvention) { + return setFieldNamingStrategy(namingConvention.getFieldNamingPolicy()); + } + + /** + * Configures Gson to apply a specific naming policy strategy to an object's field during + * serialization and deserialization. + * + * @param fieldNamingStrategy the actual naming strategy to apply to the fields + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.3 + */ + public GsonBuilder setFieldNamingStrategy(FieldNamingStrategy fieldNamingStrategy) { + return setFieldNamingStrategy(new FieldNamingStrategy2Adapter(fieldNamingStrategy)); + } + + /** + * Configures Gson to apply a specific naming policy strategy to an object's field during + * serialization and deserialization. + * + * @param fieldNamingStrategy the actual naming strategy to apply to the fields + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + GsonBuilder setFieldNamingStrategy(FieldNamingStrategy2 fieldNamingStrategy) { + this.fieldNamingPolicy = + new SerializedNameAnnotationInterceptingNamingPolicy(fieldNamingStrategy); + return this; + } + + /** + * Configures Gson to apply a set of exclusion strategies during both serialization and + * deserialization. Each of the {@code strategies} will be applied as a disjunction rule. + * This means that if one of the {@code strategies} suggests that a field (or class) should be + * skipped then that field (or object) is skipped during serializaiton/deserialization. + * + * @param strategies the set of strategy object to apply during object (de)serialization. + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.4 + */ + public GsonBuilder setExclusionStrategies(ExclusionStrategy... strategies) { + List strategyList = Arrays.asList(strategies); + serializeExclusionStrategies.addAll(strategyList); + deserializeExclusionStrategies.addAll(strategyList); + return this; + } + + /** + * Configures Gson to apply the passed in exclusion strategy during serialization. + * If this method is invoked numerous times with different exclusion strategy objects + * then the exclusion strategies that were added will be applied as a disjunction rule. + * This means that if one of the added exclusion strategies suggests that a field (or + * class) should be skipped then that field (or object) is skipped during its + * serialization. + * + * @param strategy an exclusion strategy to apply during serialization. + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.7 + */ + public GsonBuilder addSerializationExclusionStrategy(ExclusionStrategy strategy) { + serializeExclusionStrategies.add(strategy); + return this; + } + + /** + * Configures Gson to apply the passed in exclusion strategy during deserialization. + * If this method is invoked numerous times with different exclusion strategy objects + * then the exclusion strategies that were added will be applied as a disjunction rule. + * This means that if one of the added exclusion strategies suggests that a field (or + * class) should be skipped then that field (or object) is skipped during its + * deserialization. + * + * @param strategy an exclusion strategy to apply during deserialization. + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.7 + */ + public GsonBuilder addDeserializationExclusionStrategy(ExclusionStrategy strategy) { + deserializeExclusionStrategies.add(strategy); + return this; + } + + /** + * Configures Gson to output Json that fits in a page for pretty printing. This option only + * affects Json serialization. + * + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + public GsonBuilder setPrettyPrinting() { + prettyPrinting = true; + return this; + } + + /** + * By default, Gson escapes HTML characters such as < > etc. Use this option to configure + * Gson to pass-through HTML characters as is. + * + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.3 + */ + public GsonBuilder disableHtmlEscaping() { + this.escapeHtmlChars = false; + return this; + } + + /** + * Configures Gson to serialize {@code Date} objects according to the pattern provided. You can + * call this method or {@link #setDateFormat(int)} multiple times, but only the last invocation + * will be used to decide the serialization format. + * + *

The date format will be used to serialize and deserialize {@link java.util.Date}, {@link + * java.sql.Timestamp} and {@link java.sql.Date}. + * + *

Note that this pattern must abide by the convention provided by {@code SimpleDateFormat} + * class. See the documentation in {@link java.text.SimpleDateFormat} for more information on + * valid date and time patterns.

+ * + * @param pattern the pattern that dates will be serialized/deserialized to/from + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.2 + */ + public GsonBuilder setDateFormat(String pattern) { + // TODO(Joel): Make this fail fast if it is an invalid date format + this.datePattern = pattern; + return this; + } + + /** + * Configures Gson to to serialize {@code Date} objects according to the style value provided. + * You can call this method or {@link #setDateFormat(String)} multiple times, but only the last + * invocation will be used to decide the serialization format. + * + *

Note that this style value should be one of the predefined constants in the + * {@code DateFormat} class. See the documentation in {@link java.text.DateFormat} for more + * information on the valid style constants.

+ * + * @param style the predefined date style that date objects will be serialized/deserialized + * to/from + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.2 + */ + public GsonBuilder setDateFormat(int style) { + this.dateStyle = style; + this.datePattern = null; + return this; + } + + /** + * Configures Gson to to serialize {@code Date} objects according to the style value provided. + * You can call this method or {@link #setDateFormat(String)} multiple times, but only the last + * invocation will be used to decide the serialization format. + * + *

Note that this style value should be one of the predefined constants in the + * {@code DateFormat} class. See the documentation in {@link java.text.DateFormat} for more + * information on the valid style constants.

+ * + * @param dateStyle the predefined date style that date objects will be serialized/deserialized + * to/from + * @param timeStyle the predefined style for the time portion of the date objects + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.2 + */ + public GsonBuilder setDateFormat(int dateStyle, int timeStyle) { + this.dateStyle = dateStyle; + this.timeStyle = timeStyle; + this.datePattern = null; + return this; + } + + /** + * Configures Gson for custom serialization or deserialization. This method combines the + * registration of an {@link InstanceCreator}, {@link JsonSerializer}, and a + * {@link JsonDeserializer}. It is best used when a single object {@code typeAdapter} implements + * all the required interfaces for custom serialization with Gson. If an instance creator, + * serializer or deserializer was previously registered for the specified {@code type}, it is + * overwritten. + * + * @param type the type definition for the type adapter being registered + * @param typeAdapter This object must implement at least one of the {@link InstanceCreator}, + * {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces. + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) { + return registerTypeAdapter(type, typeAdapter, false); + } + + private GsonBuilder registerTypeAdapter(Type type, Object typeAdapter, boolean isSystem) { + $Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer + || typeAdapter instanceof JsonDeserializer + || typeAdapter instanceof InstanceCreator + || typeAdapter instanceof TypeAdapter.Factory); + if (Primitives.isPrimitive(type) || Primitives.isWrapperType(type)) { + throw new IllegalArgumentException( + "Cannot register type adapters for " + type); + } + if (typeAdapter instanceof InstanceCreator) { + registerInstanceCreator(type, (InstanceCreator) typeAdapter, isSystem); + } + if (typeAdapter instanceof JsonSerializer) { + registerSerializer(type, (JsonSerializer) typeAdapter, isSystem); + } + if (typeAdapter instanceof JsonDeserializer) { + registerDeserializer(type, (JsonDeserializer) typeAdapter, isSystem); + } + if (typeAdapter instanceof TypeAdapter.Factory) { + typeAdapterFactories.add((TypeAdapter.Factory) typeAdapter); + } + return this; + } + + /** + * Configures Gson to use a custom {@link InstanceCreator} for the specified type. If an instance + * creator was previously registered for the specified class, it is overwritten. Since this method + * takes a type instead of a Class object, it can be used to register a specific handler for a + * generic type corresponding to a raw type. + * + * @param the type for which instance creator is being registered + * @param typeOfT The Type definition for T + * @param instanceCreator the instance creator for T + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + private GsonBuilder registerInstanceCreator(Type typeOfT, + InstanceCreator instanceCreator, boolean isSystem) { + instanceCreators.register(typeOfT, instanceCreator, isSystem); + return this; + } + + /** + * Configures Gson to use a custom JSON serializer for the specified type. You should use this + * method if you want to register different serializers for different generic types corresponding + * to a raw type. + * + * @param the type for which the serializer is being registered + * @param typeOfT The type definition for T + * @param serializer the custom serializer + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + private GsonBuilder registerSerializer(Type typeOfT, JsonSerializer serializer, + boolean isSystem) { + serializers.register(typeOfT, serializer, isSystem); + return this; + } + + /** + * Configures Gson to use a custom JSON deserializer for the specified type. You should use this + * method if you want to register different deserializers for different generic types + * corresponding to a raw type. + * + * @param the type for which the deserializer is being registered + * @param typeOfT The type definition for T + * @param deserializer the custom deserializer + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + private GsonBuilder registerDeserializer(Type typeOfT, JsonDeserializer deserializer, + boolean isSystem) { + deserializers.register(typeOfT, new JsonDeserializerExceptionWrapper(deserializer), isSystem); + return this; + } + + /** + * Configures Gson for custom serialization or deserialization for an inheritance type hierarchy. + * This method combines the registration of an {@link InstanceCreator}, {@link JsonSerializer}, + * and a {@link JsonDeserializer}. It is best used when a single object {@code typeAdapter} + * implements all the required interfaces for custom serialization with Gson. + * If an instance creator, serializer or deserializer was previously registered for the specified + * type hierarchy, it is overwritten. If an instance creator, serializer or deserializer is + * registered for a specific type in the type hierarchy, it will be invoked instead of the one + * registered for the type hierarchy. + * + * @param baseType the class definition for the type adapter being registered for the base class + * or interface + * @param typeAdapter This object must implement at least one of the {@link InstanceCreator}, + * {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces. + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.7 + */ + public GsonBuilder registerTypeHierarchyAdapter(Class baseType, Object typeAdapter) { + return registerTypeHierarchyAdapter(baseType, typeAdapter, false); + } + + private GsonBuilder registerTypeHierarchyAdapter(Class baseType, Object typeAdapter, + boolean isSystem) { + $Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer + || typeAdapter instanceof JsonDeserializer || typeAdapter instanceof InstanceCreator); + if (typeAdapter instanceof InstanceCreator) { + registerInstanceCreatorForTypeHierarchy(baseType, (InstanceCreator) typeAdapter, isSystem); + } + if (typeAdapter instanceof JsonSerializer) { + registerSerializerForTypeHierarchy(baseType, (JsonSerializer) typeAdapter, isSystem); + } + if (typeAdapter instanceof JsonDeserializer) { + registerDeserializerForTypeHierarchy(baseType, (JsonDeserializer) typeAdapter, isSystem); + } + return this; + } + + private GsonBuilder registerInstanceCreatorForTypeHierarchy(Class classOfT, + InstanceCreator instanceCreator, boolean isSystem) { + instanceCreators.registerForTypeHierarchy(classOfT, instanceCreator, isSystem); + return this; + } + + private GsonBuilder registerSerializerForTypeHierarchy(Class classOfT, + JsonSerializer serializer, boolean isSystem) { + serializers.registerForTypeHierarchy(classOfT, serializer, isSystem); + return this; + } + + private GsonBuilder registerDeserializerForTypeHierarchy(Class classOfT, + JsonDeserializer deserializer, boolean isSystem) { + deserializers.registerForTypeHierarchy(classOfT, + new JsonDeserializerExceptionWrapper(deserializer), isSystem); + return this; + } + + /** + * Section 2.4 of JSON specification disallows + * special double values (NaN, Infinity, -Infinity). However, + * Javascript + * specification (see section 4.3.20, 4.3.22, 4.3.23) allows these values as valid Javascript + * values. Moreover, most JavaScript engines will accept these special values in JSON without + * problem. So, at a practical level, it makes sense to accept these values as valid JSON even + * though JSON specification disallows them. + * + *

Gson always accepts these special values during deserialization. However, it outputs + * strictly compliant JSON. Hence, if it encounters a float value {@link Float#NaN}, + * {@link Float#POSITIVE_INFINITY}, {@link Float#NEGATIVE_INFINITY}, or a double value + * {@link Double#NaN}, {@link Double#POSITIVE_INFINITY}, {@link Double#NEGATIVE_INFINITY}, it + * will throw an {@link IllegalArgumentException}. This method provides a way to override the + * default behavior when you know that the JSON receiver will be able to handle these special + * values. + * + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.3 + */ + public GsonBuilder serializeSpecialFloatingPointValues() { + this.serializeSpecialFloatingPointValues = true; + return this; + } + + /** + * Creates a {@link Gson} instance based on the current configuration. This method is free of + * side-effects to this {@code GsonBuilder} instance and hence can be called multiple times. + * + * @return an instance of Gson configured with the options currently set in this builder + */ + public Gson create() { + List deserializationStrategies = + new LinkedList(deserializeExclusionStrategies); + List serializationStrategies = + new LinkedList(serializeExclusionStrategies); + deserializationStrategies.add(modifierBasedExclusionStrategy); + serializationStrategies.add(modifierBasedExclusionStrategy); + + if (!serializeInnerClasses) { + deserializationStrategies.add(innerClassExclusionStrategy); + serializationStrategies.add(innerClassExclusionStrategy); + } + if (ignoreVersionsAfter != VersionConstants.IGNORE_VERSIONS) { + VersionExclusionStrategy versionExclusionStrategy = + new VersionExclusionStrategy(ignoreVersionsAfter); + deserializationStrategies.add(versionExclusionStrategy); + serializationStrategies.add(versionExclusionStrategy); + } + if (excludeFieldsWithoutExposeAnnotation) { + deserializationStrategies.add(exposeAnnotationDeserializationExclusionStrategy); + serializationStrategies.add(exposeAnnotationSerializationExclusionStrategy); + } + addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, serializers, deserializers); + + return new Gson(new DisjunctionExclusionStrategy(deserializationStrategies), + new DisjunctionExclusionStrategy(serializationStrategies), + fieldNamingPolicy, instanceCreators.copyOf().makeUnmodifiable(), serializeNulls, + serializers.copyOf().makeUnmodifiable(), deserializers.copyOf().makeUnmodifiable(), + complexMapKeySerialization, generateNonExecutableJson, escapeHtmlChars, prettyPrinting, + serializeSpecialFloatingPointValues, longSerializationPolicy, typeAdapterFactories); + } + + private static void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle, + ParameterizedTypeHandlerMap> serializers, + ParameterizedTypeHandlerMap> deserializers) { + DefaultDateTypeAdapter dateTypeAdapter = null; + if (datePattern != null && !"".equals(datePattern.trim())) { + dateTypeAdapter = new DefaultDateTypeAdapter(datePattern); + } else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) { + dateTypeAdapter = new DefaultDateTypeAdapter(dateStyle, timeStyle); + } + + if (dateTypeAdapter != null) { + registerIfAbsent(Date.class, serializers, dateTypeAdapter); + registerIfAbsent(Date.class, deserializers, dateTypeAdapter); + registerIfAbsent(Timestamp.class, serializers, dateTypeAdapter); + registerIfAbsent(Timestamp.class, deserializers, dateTypeAdapter); + registerIfAbsent(java.sql.Date.class, serializers, dateTypeAdapter); + registerIfAbsent(java.sql.Date.class, deserializers, dateTypeAdapter); + } + } + + private static void registerIfAbsent(Class type, + ParameterizedTypeHandlerMap adapters, T adapter) { + if (!adapters.hasSpecificHandlerFor(type)) { + adapters.register(type, adapter, false); + } + } +} diff --git a/src/com/massivecraft/core/lib/gson2/GsonToMiniGsonTypeAdapterFactory.java b/src/com/massivecraft/core/lib/gson2/GsonToMiniGsonTypeAdapterFactory.java new file mode 100755 index 00000000..33f73ea6 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/GsonToMiniGsonTypeAdapterFactory.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.internal.ParameterizedTypeHandlerMap; +import com.massivecraft.core.lib.gson2.internal.Streams; +import com.massivecraft.core.lib.gson2.internal.bind.MiniGson; +import com.massivecraft.core.lib.gson2.internal.bind.TypeAdapter; +import com.massivecraft.core.lib.gson2.reflect.TypeToken; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.lang.reflect.Type; + +final class GsonToMiniGsonTypeAdapterFactory implements TypeAdapter.Factory { + private final ParameterizedTypeHandlerMap> serializers; + private final ParameterizedTypeHandlerMap> deserializers; + private final JsonDeserializationContext deserializationContext; + private final JsonSerializationContext serializationContext; + + public GsonToMiniGsonTypeAdapterFactory(final Gson gson, + ParameterizedTypeHandlerMap> serializers, + ParameterizedTypeHandlerMap> deserializers) { + this.serializers = serializers; + this.deserializers = deserializers; + + this.deserializationContext = new JsonDeserializationContext() { + public T deserialize(JsonElement json, Type typeOfT) throws JsonParseException { + return (T) gson.fromJson(json, typeOfT); + } + }; + + this.serializationContext = new JsonSerializationContext() { + public JsonElement serialize(Object src) { + return gson.toJsonTree(src); + } + public JsonElement serialize(Object src, Type typeOfSrc) { + return gson.toJsonTree(src, typeOfSrc); + } + }; + } + + public TypeAdapter create(final MiniGson context, final TypeToken typeToken) { + final Type type = typeToken.getType(); + + @SuppressWarnings("unchecked") // guaranteed to match typeOfT + final JsonSerializer serializer + = (JsonSerializer) serializers.getHandlerFor(type, false); + @SuppressWarnings("unchecked") // guaranteed to match typeOfT + final JsonDeserializer deserializer + = (JsonDeserializer) deserializers.getHandlerFor(type, false); + + if (serializer == null && deserializer == null) { + return null; + } + + return new TypeAdapter() { + /** + * The delegate is lazily created because it may not be needed, and + * creating it may fail. + */ + private TypeAdapter delegate; + + @Override public T read(JsonReader reader) throws IOException { + if (deserializer == null) { + return delegate().read(reader); + } + JsonElement value = Streams.parse(reader); + if (value.isJsonNull()) { + return null; + } + return deserializer.deserialize(value, type, deserializationContext); + } + + @Override public void write(JsonWriter writer, T value) throws IOException { + if (serializer == null) { + delegate().write(writer, value); + return; + } + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = serializer.serialize(value, type, serializationContext); + Streams.write(element, writer); + } + + private TypeAdapter delegate() { + TypeAdapter d = delegate; + return d != null + ? d + : (delegate = context.getNextAdapter(GsonToMiniGsonTypeAdapterFactory.this, typeToken)); + } + }; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/InnerClassExclusionStrategy.java b/src/com/massivecraft/core/lib/gson2/InnerClassExclusionStrategy.java new file mode 100755 index 00000000..63816b7f --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/InnerClassExclusionStrategy.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.lang.reflect.Modifier; + +/** + * Strategy for excluding inner classes. + * + * @author Joel Leitch + */ +final class InnerClassExclusionStrategy implements ExclusionStrategy { + + public boolean shouldSkipField(FieldAttributes f) { + return isInnerClass(f.getDeclaredClass()); + } + + public boolean shouldSkipClass(Class clazz) { + return isInnerClass(clazz); + } + + private boolean isInnerClass(Class clazz) { + return clazz.isMemberClass() && !isStatic(clazz); + } + + private boolean isStatic(Class clazz) { + return (clazz.getModifiers() & Modifier.STATIC) != 0; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/InstanceCreator.java b/src/com/massivecraft/core/lib/gson2/InstanceCreator.java new file mode 100755 index 00000000..15961c16 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/InstanceCreator.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.lang.reflect.Type; + +/** + * This interface is implemented to create instances of a class that does not define a no-args + * constructor. If you can modify the class, you should instead add a private, or public + * no-args constructor. However, that is not possible for library classes, such as JDK classes, or + * a third-party library that you do not have source-code of. In such cases, you should define an + * instance creator for the class. Implementations of this interface should be registered with + * {@link GsonBuilder#registerTypeAdapter(Type, Object)} method before Gson will be able to use + * them. + *

Let us look at an example where defining an InstanceCreator might be useful. The + * {@code Id} class defined below does not have a default no-args constructor.

+ * + *
+ * public class Id<T> {
+ *   private final Class<T> clazz;
+ *   private final long value;
+ *   public Id(Class<T> clazz, long value) {
+ *     this.clazz = clazz;
+ *     this.value = value;
+ *   }
+ * }
+ * 
+ * + *

If Gson encounters an object of type {@code Id} during deserialization, it will throw an + * exception. The easiest way to solve this problem will be to add a (public or private) no-args + * constructor as follows:

+ * + *
+ * private Id() {
+ *   this(Object.class, 0L);
+ * }
+ * 
+ * + *

However, let us assume that the developer does not have access to the source-code of the + * {@code Id} class, or does not want to define a no-args constructor for it. The developer + * can solve this problem by defining an {@code InstanceCreator} for {@code Id}:

+ * + *
+ * class IdInstanceCreator implements InstanceCreator<Id> {
+ *   public Id createInstance(Type type) {
+ *     return new Id(Object.class, 0L);
+ *   }
+ * }
+ * 
+ * + *

Note that it does not matter what the fields of the created instance contain since Gson will + * overwrite them with the deserialized values specified in Json. You should also ensure that a + * new object is returned, not a common object since its fields will be overwritten. + * The developer will need to register {@code IdInstanceCreator} with Gson as follows:

+ * + *
+ * Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdInstanceCreator()).create();
+ * 
+ * + * @param the type of object that will be created by this implementation. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public interface InstanceCreator { + + /** + * Gson invokes this call-back method during deserialization to create an instance of the + * specified type. The fields of the returned instance are overwritten with the data present + * in the Json. Since the prior contents of the object are destroyed and overwritten, do not + * return an instance that is useful elsewhere. In particular, do not return a common instance, + * always use {@code new} to create a new instance. + * + * @param type the parameterized T represented as a {@link Type}. + * @return a default object instance of type T. + */ + public T createInstance(Type type); +} diff --git a/src/com/massivecraft/core/lib/gson2/JavaFieldNamingPolicy.java b/src/com/massivecraft/core/lib/gson2/JavaFieldNamingPolicy.java new file mode 100755 index 00000000..fcf4f168 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JavaFieldNamingPolicy.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collection; + +/** + * A simple implementation of the {@link FieldNamingStrategy2} interface such that it does not + * perform any string translation of the incoming field name. + * + *

The following is an example:

+ * + *
+ * class IntWrapper {
+ *   public int integerField = 0;
+ * }
+ *
+ * JavaFieldNamingPolicy policy = new JavaFieldNamingPolicy();
+ * String translatedFieldName =
+ *     policy.translateName(IntWrapper.class.getField("integerField"));
+ *
+ * assert("integerField".equals(translatedFieldName));
+ * 
+ * + *

This is the default {@link FieldNamingStrategy2} used by Gson.

+ * + * @author Joel Leitch + */ +final class JavaFieldNamingPolicy extends RecursiveFieldNamingPolicy { + + @Override + protected String translateName(String target, Type fieldType, Collection annotations) { + return target; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/JsonArray.java b/src/com/massivecraft/core/lib/gson2/JsonArray.java new file mode 100755 index 00000000..11640b28 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonArray.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * A class representing an array type in Json. An array is a list of {@link JsonElement}s each of + * which can be of a different type. This is an ordered list, meaning that the order in which + * elements are added is preserved. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public final class JsonArray extends JsonElement implements Iterable { + private final List elements; + + /** + * Creates an empty JsonArray. + */ + public JsonArray() { + elements = new ArrayList(); + } + + /** + * Adds the specified element to self. + * + * @param element the element that needs to be added to the array. + */ + public void add(JsonElement element) { + if (element == null) { + element = JsonNull.INSTANCE; + } + elements.add(element); + } + + /** + * Adds all the elements of the specified array to self. + * + * @param array the array whose elements need to be added to the array. + */ + public void addAll(JsonArray array) { + elements.addAll(array.elements); + } + + /** + * Returns the number of elements in the array. + * + * @return the number of elements in the array. + */ + public int size() { + return elements.size(); + } + + /** + * Returns an iterator to navigate the elemetns of the array. Since the array is an ordered list, + * the iterator navigates the elements in the order they were inserted. + * + * @return an iterator to navigate the elements of the array. + */ + public Iterator iterator() { + return elements.iterator(); + } + + /** + * Returns the ith element of the array. + * + * @param i the index of the element that is being sought. + * @return the element present at the ith index. + * @throws IndexOutOfBoundsException if i is negative or greater than or equal to the + * {@link #size()} of the array. + */ + public JsonElement get(int i) { + return elements.get(i); + } + + /** + * convenience method to get this array as a {@link Number} if it contains a single element. + * + * @return get this element as a number if it is single element array. + * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and + * is not a valid Number. + * @throws IllegalStateException if the array has more than one element. + */ + @Override + public Number getAsNumber() { + if (elements.size() == 1) { + return elements.get(0).getAsNumber(); + } + throw new IllegalStateException(); + } + + /** + * convenience method to get this array as a {@link String} if it contains a single element. + * + * @return get this element as a String if it is single element array. + * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and + * is not a valid String. + * @throws IllegalStateException if the array has more than one element. + */ + @Override + public String getAsString() { + if (elements.size() == 1) { + return elements.get(0).getAsString(); + } + throw new IllegalStateException(); + } + + /** + * convenience method to get this array as a double if it contains a single element. + * + * @return get this element as a double if it is single element array. + * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and + * is not a valid double. + * @throws IllegalStateException if the array has more than one element. + */ + @Override + public double getAsDouble() { + if (elements.size() == 1) { + return elements.get(0).getAsDouble(); + } + throw new IllegalStateException(); + } + + /** + * convenience method to get this array as a {@link BigDecimal} if it contains a single element. + * + * @return get this element as a {@link BigDecimal} if it is single element array. + * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive}. + * @throws NumberFormatException if the element at index 0 is not a valid {@link BigDecimal}. + * @throws IllegalStateException if the array has more than one element. + * @since 1.2 + */ + @Override + public BigDecimal getAsBigDecimal() { + if (elements.size() == 1) { + return elements.get(0).getAsBigDecimal(); + } + throw new IllegalStateException(); + } + + /** + * convenience method to get this array as a {@link BigInteger} if it contains a single element. + * + * @return get this element as a {@link BigInteger} if it is single element array. + * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive}. + * @throws NumberFormatException if the element at index 0 is not a valid {@link BigInteger}. + * @throws IllegalStateException if the array has more than one element. + * @since 1.2 + */ + @Override + public BigInteger getAsBigInteger() { + if (elements.size() == 1) { + return elements.get(0).getAsBigInteger(); + } + throw new IllegalStateException(); + } + + /** + * convenience method to get this array as a float if it contains a single element. + * + * @return get this element as a float if it is single element array. + * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and + * is not a valid float. + * @throws IllegalStateException if the array has more than one element. + */ + @Override + public float getAsFloat() { + if (elements.size() == 1) { + return elements.get(0).getAsFloat(); + } + throw new IllegalStateException(); + } + + /** + * convenience method to get this array as a long if it contains a single element. + * + * @return get this element as a long if it is single element array. + * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and + * is not a valid long. + * @throws IllegalStateException if the array has more than one element. + */ + @Override + public long getAsLong() { + if (elements.size() == 1) { + return elements.get(0).getAsLong(); + } + throw new IllegalStateException(); + } + + /** + * convenience method to get this array as an integer if it contains a single element. + * + * @return get this element as an integer if it is single element array. + * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and + * is not a valid integer. + * @throws IllegalStateException if the array has more than one element. + */ + @Override + public int getAsInt() { + if (elements.size() == 1) { + return elements.get(0).getAsInt(); + } + throw new IllegalStateException(); + } + + @Override + public byte getAsByte() { + if (elements.size() == 1) { + return elements.get(0).getAsByte(); + } + throw new IllegalStateException(); + } + + @Override + public char getAsCharacter() { + if (elements.size() == 1) { + return elements.get(0).getAsCharacter(); + } + throw new IllegalStateException(); + } + + /** + * convenience method to get this array as a primitive short if it contains a single element. + * + * @return get this element as a primitive short if it is single element array. + * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and + * is not a valid short. + * @throws IllegalStateException if the array has more than one element. + */ + @Override + public short getAsShort() { + if (elements.size() == 1) { + return elements.get(0).getAsShort(); + } + throw new IllegalStateException(); + } + + /** + * convenience method to get this array as a boolean if it contains a single element. + * + * @return get this element as a boolean if it is single element array. + * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and + * is not a valid boolean. + * @throws IllegalStateException if the array has more than one element. + */ + @Override + public boolean getAsBoolean() { + if (elements.size() == 1) { + return elements.get(0).getAsBoolean(); + } + throw new IllegalStateException(); + } + + @Override + public boolean equals(Object o) { + return (o == this) || (o instanceof JsonArray && ((JsonArray) o).elements.equals(elements)); + } + + @Override + public int hashCode() { + return elements.hashCode(); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/JsonDeserializationContext.java b/src/com/massivecraft/core/lib/gson2/JsonDeserializationContext.java new file mode 100755 index 00000000..0082d16f --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonDeserializationContext.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.lang.reflect.Type; + +/** + * Context for deserialization that is passed to a custom deserializer during invocation of its + * {@link JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext)} + * method. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public interface JsonDeserializationContext { + + /** + * Invokes default deserialization on the specified object. It should never be invoked on + * the element received as a parameter of the + * {@link JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext)} method. Doing + * so will result in an infinite loop since Gson will in-turn call the custom deserializer again. + * + * @param json the parse tree. + * @param typeOfT type of the expected return value. + * @param The type of the deserialized object. + * @return An object of type typeOfT. + * @throws JsonParseException if the parse tree does not contain expected data. + */ + public T deserialize(JsonElement json, Type typeOfT) throws JsonParseException; +} \ No newline at end of file diff --git a/src/com/massivecraft/core/lib/gson2/JsonDeserializer.java b/src/com/massivecraft/core/lib/gson2/JsonDeserializer.java new file mode 100755 index 00000000..a8637488 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonDeserializer.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.lang.reflect.Type; + +/** + *

Interface representing a custom deserializer for Json. You should write a custom + * deserializer, if you are not happy with the default deserialization done by Gson. You will + * also need to register this deserializer through + * {@link GsonBuilder#registerTypeAdapter(Type, Object)}.

+ * + *

Let us look at example where defining a deserializer will be useful. The {@code Id} class + * defined below has two fields: {@code clazz} and {@code value}.

+ * + *
+ * public class Id<T> {
+ *   private final Class<T> clazz;
+ *   private final long value;
+ *   public Id(Class<T> clazz, long value) {
+ *     this.clazz = clazz;
+ *     this.value = value;
+ *   }
+ *   public long getValue() {
+ *     return value;
+ *   }
+ * }
+ * 
+ * + *

The default deserialization of {@code Id(com.foo.MyObject.class, 20L)} will require the + * Json string to be {"clazz":com.foo.MyObject,"value":20}. Suppose, you already know + * the type of the field that the {@code Id} will be deserialized into, and hence just want to + * deserialize it from a Json string {@code 20}. You can achieve that by writing a custom + * deserializer:

+ * + *
+ * class IdDeserializer implements JsonDeserializer<Id>() {
+ *   public Id deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ *       throws JsonParseException {
+ *     return new Id((Class)typeOfT, id.getValue());
+ *   }
+ * 
+ * + *

You will also need to register {@code IdDeserializer} with Gson as follows:

+ * + *
+ * Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdDeserializer()).create();
+ * 
+ * + * @author Inderjeet Singh + * @author Joel Leitch + * + * @param type for which the deserializer is being registered. It is possible that a + * deserializer may be asked to deserialize a specific generic type of the T. + */ +public interface JsonDeserializer { + + /** + * Gson invokes this call-back method during deserialization when it encounters a field of the + * specified type. + *

In the implementation of this call-back method, you should consider invoking + * {@link JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects + * for any non-trivial field of the returned object. However, you should never invoke it on the + * the same type passing {@code json} since that will cause an infinite loop (Gson will call your + * call-back method again). + * + * @param json The Json data being deserialized + * @param typeOfT The type of the Object to deserialize to + * @return a deserialized object of the specified type typeOfT which is a subclass of {@code T} + * @throws JsonParseException if json is not in the expected format of {@code typeofT} + */ + public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException; +} diff --git a/src/com/massivecraft/core/lib/gson2/JsonDeserializerExceptionWrapper.java b/src/com/massivecraft/core/lib/gson2/JsonDeserializerExceptionWrapper.java new file mode 100755 index 00000000..d17060be --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonDeserializerExceptionWrapper.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.internal.$Gson$Preconditions; + +import java.lang.reflect.Type; + +/** + * Decorators a {@code JsonDeserializer} instance with exception handling. This wrapper class + * ensures that a {@code JsonDeserializer} will not propagate any exception other than a + * {@link JsonParseException}. + * + * @param type of the deserializer being wrapped. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +final class JsonDeserializerExceptionWrapper implements JsonDeserializer { + + private final JsonDeserializer delegate; + + /** + * Returns a wrapped {@link JsonDeserializer} object that has been decorated with + * {@link JsonParseException} handling. + * + * @param delegate the {@code JsonDeserializer} instance to be wrapped. + * @throws IllegalArgumentException if {@code delegate} is {@code null}. + */ + JsonDeserializerExceptionWrapper(JsonDeserializer delegate) { + this.delegate = $Gson$Preconditions.checkNotNull(delegate); + } + + public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + try { + return delegate.deserialize(json, typeOfT, context); + } catch (JsonParseException e) { + // just rethrow the exception + throw e; + } catch (Exception e) { + // rethrow as a JsonParseException + StringBuilder errorMsg = new StringBuilder() + .append("The JsonDeserializer ") + .append(delegate) + .append(" failed to deserialize json object ") + .append(json) + .append(" given the type ") + .append(typeOfT); + throw new JsonParseException(errorMsg.toString(), e); + } + } + + @Override + public String toString() { + return delegate.toString(); + } +} \ No newline at end of file diff --git a/src/com/massivecraft/core/lib/gson2/JsonElement.java b/src/com/massivecraft/core/lib/gson2/JsonElement.java new file mode 100755 index 00000000..1b3e5144 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonElement.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.internal.Streams; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.io.StringWriter; +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * A class representing an element of Json. It could either be a {@link JsonObject}, a + * {@link JsonArray}, a {@link JsonPrimitive} or a {@link JsonNull}. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public abstract class JsonElement { + /** + * provides check for verifying if this element is an array or not. + * + * @return true if this element is of type {@link JsonArray}, false otherwise. + */ + public boolean isJsonArray() { + return this instanceof JsonArray; + } + + /** + * provides check for verifying if this element is a Json object or not. + * + * @return true if this element is of type {@link JsonObject}, false otherwise. + */ + public boolean isJsonObject() { + return this instanceof JsonObject; + } + + /** + * provides check for verifying if this element is a primitive or not. + * + * @return true if this element is of type {@link JsonPrimitive}, false otherwise. + */ + public boolean isJsonPrimitive() { + return this instanceof JsonPrimitive; + } + + /** + * provides check for verifying if this element represents a null value or not. + * + * @return true if this element is of type {@link JsonNull}, false otherwise. + * @since 1.2 + */ + public boolean isJsonNull() { + return this instanceof JsonNull; + } + + /** + * convenience method to get this element as a {@link JsonObject}. If the element is of some + * other type, a {@link ClassCastException} will result. Hence it is best to use this method + * after ensuring that this element is of the desired type by calling {@link #isJsonObject()} + * first. + * + * @return get this element as a {@link JsonObject}. + * @throws IllegalStateException if the element is of another type. + */ + public JsonObject getAsJsonObject() { + if (isJsonObject()) { + return (JsonObject) this; + } + throw new IllegalStateException("Not a JSON Object: " + this); + } + + /** + * convenience method to get this element as a {@link JsonArray}. If the element is of some + * other type, a {@link ClassCastException} will result. Hence it is best to use this method + * after ensuring that this element is of the desired type by calling {@link #isJsonArray()} + * first. + * + * @return get this element as a {@link JsonArray}. + * @throws IllegalStateException if the element is of another type. + */ + public JsonArray getAsJsonArray() { + if (isJsonArray()) { + return (JsonArray) this; + } + throw new IllegalStateException("This is not a JSON Array."); + } + + /** + * convenience method to get this element as a {@link JsonPrimitive}. If the element is of some + * other type, a {@link ClassCastException} will result. Hence it is best to use this method + * after ensuring that this element is of the desired type by calling {@link #isJsonPrimitive()} + * first. + * + * @return get this element as a {@link JsonPrimitive}. + * @throws IllegalStateException if the element is of another type. + */ + public JsonPrimitive getAsJsonPrimitive() { + if (isJsonPrimitive()) { + return (JsonPrimitive) this; + } + throw new IllegalStateException("This is not a JSON Primitive."); + } + + /** + * convenience method to get this element as a {@link JsonNull}. If the element is of some + * other type, a {@link ClassCastException} will result. Hence it is best to use this method + * after ensuring that this element is of the desired type by calling {@link #isJsonNull()} + * first. + * + * @return get this element as a {@link JsonNull}. + * @throws IllegalStateException if the element is of another type. + * @since 1.2 + */ + public JsonNull getAsJsonNull() { + if (isJsonNull()) { + return (JsonNull) this; + } + throw new IllegalStateException("This is not a JSON Null."); + } + + /** + * convenience method to get this element as a boolean value. + * + * @return get this element as a primitive boolean value. + * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid + * boolean value. + * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains + * more than a single element. + */ + public boolean getAsBoolean() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + /** + * convenience method to get this element as a {@link Boolean} value. + * + * @return get this element as a {@link Boolean} value. + * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid + * boolean value. + * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains + * more than a single element. + */ + Boolean getAsBooleanWrapper() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + /** + * convenience method to get this element as a {@link Number}. + * + * @return get this element as a {@link Number}. + * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid + * number. + * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains + * more than a single element. + */ + public Number getAsNumber() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + /** + * convenience method to get this element as a string value. + * + * @return get this element as a string value. + * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid + * string value. + * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains + * more than a single element. + */ + public String getAsString() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + /** + * convenience method to get this element as a primitive double value. + * + * @return get this element as a primitive double value. + * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid + * double value. + * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains + * more than a single element. + */ + public double getAsDouble() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + /** + * convenience method to get this element as a primitive float value. + * + * @return get this element as a primitive float value. + * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid + * float value. + * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains + * more than a single element. + */ + public float getAsFloat() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + /** + * convenience method to get this element as a primitive long value. + * + * @return get this element as a primitive long value. + * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid + * long value. + * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains + * more than a single element. + */ + public long getAsLong() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + /** + * convenience method to get this element as a primitive integer value. + * + * @return get this element as a primitive integer value. + * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid + * integer value. + * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains + * more than a single element. + */ + public int getAsInt() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + /** + * convenience method to get this element as a primitive byte value. + * + * @return get this element as a primitive byte value. + * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid + * byte value. + * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains + * more than a single element. + * @since 1.3 + */ + public byte getAsByte() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + /** + * convenience method to get this element as a primitive character value. + * + * @return get this element as a primitive char value. + * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid + * char value. + * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains + * more than a single element. + * @since 1.3 + */ + public char getAsCharacter() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + /** + * convenience method to get this element as a {@link BigDecimal}. + * + * @return get this element as a {@link BigDecimal}. + * @throws ClassCastException if the element is of not a {@link JsonPrimitive}. + * * @throws NumberFormatException if the element is not a valid {@link BigDecimal}. + * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains + * more than a single element. + * @since 1.2 + */ + public BigDecimal getAsBigDecimal() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + /** + * convenience method to get this element as a {@link BigInteger}. + * + * @return get this element as a {@link BigInteger}. + * @throws ClassCastException if the element is of not a {@link JsonPrimitive}. + * @throws NumberFormatException if the element is not a valid {@link BigInteger}. + * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains + * more than a single element. + * @since 1.2 + */ + public BigInteger getAsBigInteger() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + /** + * convenience method to get this element as a primitive short value. + * + * @return get this element as a primitive short value. + * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid + * short value. + * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains + * more than a single element. + */ + public short getAsShort() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + /** + * Returns a String representation of this element. + */ + @Override + public String toString() { + try { + StringWriter stringWriter = new StringWriter(); + JsonWriter jsonWriter = new JsonWriter(stringWriter); + jsonWriter.setLenient(true); + Streams.write(this, jsonWriter); + return stringWriter.toString(); + } catch (IOException e) { + throw new AssertionError(e); + } + } +} diff --git a/src/com/massivecraft/core/lib/gson2/JsonElementVisitor.java b/src/com/massivecraft/core/lib/gson2/JsonElementVisitor.java new file mode 100755 index 00000000..4aca2948 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonElementVisitor.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.io.IOException; + +/** + * Definition of a visitor for a JsonElement tree. + * + * @author Inderjeet Singh + */ +interface JsonElementVisitor { + void visitPrimitive(JsonPrimitive primitive) throws IOException; + void visitNull() throws IOException; + + void startArray(JsonArray array) throws IOException; + void visitArrayMember(JsonArray parent, JsonPrimitive member, boolean isFirst) throws IOException; + void visitArrayMember(JsonArray parent, JsonArray member, boolean isFirst) throws IOException; + void visitArrayMember(JsonArray parent, JsonObject member, boolean isFirst) throws IOException; + void visitNullArrayMember(JsonArray parent, boolean isFirst) throws IOException; + void endArray(JsonArray array) throws IOException; + + void startObject(JsonObject object) throws IOException; + void visitObjectMember(JsonObject parent, String memberName, JsonPrimitive member, + boolean isFirst) throws IOException; + void visitObjectMember(JsonObject parent, String memberName, JsonArray member, + boolean isFirst) throws IOException; + void visitObjectMember(JsonObject parent, String memberName, JsonObject member, + boolean isFirst) throws IOException; + void visitNullObjectMember(JsonObject parent, String memberName, + boolean isFirst) throws IOException; + void endObject(JsonObject object) throws IOException; +} \ No newline at end of file diff --git a/src/com/massivecraft/core/lib/gson2/JsonIOException.java b/src/com/massivecraft/core/lib/gson2/JsonIOException.java new file mode 100755 index 00000000..ba74f5be --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonIOException.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +/** + * This exception is raised when Gson was unable to read an input stream + * or write to one. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public final class JsonIOException extends JsonParseException { + + private static final long serialVersionUID = 1L; + + public JsonIOException(String msg) { + super(msg); + } + + public JsonIOException(String msg, Throwable cause) { + super(msg, cause); + } + + /** + * Creates exception with the specified cause. Consider using + * {@link #JsonIOException(String, Throwable)} instead if you can describe what happened. + * + * @param cause root exception that caused this exception to be thrown. + */ + public JsonIOException(Throwable cause) { + super(cause); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/JsonNull.java b/src/com/massivecraft/core/lib/gson2/JsonNull.java new file mode 100755 index 00000000..b08226ba --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonNull.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +/** + * A class representing a Json {@code null} value. + * + * @author Inderjeet Singh + * @author Joel Leitch + * @since 1.2 + */ +public final class JsonNull extends JsonElement { + /** + * singleton for JsonNull + * + * @since 1.8 + */ + public static final JsonNull INSTANCE = new JsonNull(); + + /** + * Creates a new JsonNull object. + * Deprecated since Gson version 1.8. Use {@link #INSTANCE} instead + */ + @Deprecated + public JsonNull() { + // Do nothing + } + + /** + * All instances of JsonNull have the same hash code since they are indistinguishable + */ + @Override + public int hashCode() { + return JsonNull.class.hashCode(); + } + + /** + * All instances of JsonNull are the same + */ + @Override + public boolean equals(Object other) { + return this == other || other instanceof JsonNull; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/JsonObject.java b/src/com/massivecraft/core/lib/gson2/JsonObject.java new file mode 100755 index 00000000..9a231161 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonObject.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.internal.$Gson$Preconditions; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * A class representing an object type in Json. An object consists of name-value pairs where names + * are strings, and values are any other type of {@link JsonElement}. This allows for a creating a + * tree of JsonElements. The member elements of this object are maintained in order they were added. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public final class JsonObject extends JsonElement { + // We are using a linked hash map because it is important to preserve + // the order in which elements are inserted. This is needed to ensure + // that the fields of an object are inserted in the order they were + // defined in the class. + private final Map members = new LinkedHashMap(); + + /** + * Creates an empty JsonObject. + */ + public JsonObject() { + } + + /** + * Adds a member, which is a name-value pair, to self. The name must be a String, but the value + * can be an arbitrary JsonElement, thereby allowing you to build a full tree of JsonElements + * rooted at this node. + * + * @param property name of the member. + * @param value the member object. + */ + public void add(String property, JsonElement value) { + if (value == null) { + value = JsonNull.INSTANCE; + } + members.put($Gson$Preconditions.checkNotNull(property), value); + } + + /** + * Removes the {@code property} from this {@link JsonObject}. + * + * @param property name of the member that should be removed. + * @return the {@link JsonElement} object that is being removed. + * @since 1.3 + */ + public JsonElement remove(String property) { + return members.remove(property); + } + + /** + * Convenience method to add a primitive member. The specified value is converted to a + * JsonPrimitive of String. + * + * @param property name of the member. + * @param value the string value associated with the member. + */ + public void addProperty(String property, String value) { + add(property, createJsonElement(value)); + } + + /** + * Convenience method to add a primitive member. The specified value is converted to a + * JsonPrimitive of Number. + * + * @param property name of the member. + * @param value the number value associated with the member. + */ + public void addProperty(String property, Number value) { + add(property, createJsonElement(value)); + } + + /** + * Convenience method to add a boolean member. The specified value is converted to a + * JsonPrimitive of Boolean. + * + * @param property name of the member. + * @param value the number value associated with the member. + */ + public void addProperty(String property, Boolean value) { + add(property, createJsonElement(value)); + } + + /** + * Convenience method to add a char member. The specified value is converted to a + * JsonPrimitive of Character. + * + * @param property name of the member. + * @param value the number value associated with the member. + */ + public void addProperty(String property, Character value) { + add(property, createJsonElement(value)); + } + + /** + * Creates the proper {@link JsonElement} object from the given {@code value} object. + * + * @param value the object to generate the {@link JsonElement} for + * @return a {@link JsonPrimitive} if the {@code value} is not null, otherwise a {@link JsonNull} + */ + private JsonElement createJsonElement(Object value) { + return value == null ? JsonNull.INSTANCE : new JsonPrimitive(value); + } + + /** + * Returns a set of members of this object. The set is ordered, and the order is in which the + * elements were added. + * + * @return a set of members of this object. + */ + public Set> entrySet() { + return members.entrySet(); + } + + /** + * Convenience method to check if a member with the specified name is present in this object. + * + * @param memberName name of the member that is being checked for presence. + * @return true if there is a member with the specified name, false otherwise. + */ + public boolean has(String memberName) { + return members.containsKey(memberName); + } + + /** + * Returns the member with the specified name. + * + * @param memberName name of the member that is being requested. + * @return the member matching the name. Null if no such member exists. + */ + public JsonElement get(String memberName) { + if (members.containsKey(memberName)) { + JsonElement member = members.get(memberName); + return member == null ? JsonNull.INSTANCE : member; + } + return null; + } + + /** + * Convenience method to get the specified member as a JsonPrimitive element. + * + * @param memberName name of the member being requested. + * @return the JsonPrimitive corresponding to the specified member. + */ + public JsonPrimitive getAsJsonPrimitive(String memberName) { + return (JsonPrimitive) members.get(memberName); + } + + /** + * Convenience method to get the specified member as a JsonArray. + * + * @param memberName name of the member being requested. + * @return the JsonArray corresponding to the specified member. + */ + public JsonArray getAsJsonArray(String memberName) { + return (JsonArray) members.get(memberName); + } + + /** + * Convenience method to get the specified member as a JsonObject. + * + * @param memberName name of the member being requested. + * @return the JsonObject corresponding to the specified member. + */ + public JsonObject getAsJsonObject(String memberName) { + return (JsonObject) members.get(memberName); + } + + @Override + public boolean equals(Object o) { + return (o == this) || (o instanceof JsonObject + && ((JsonObject) o).members.equals(members)); + } + + @Override + public int hashCode() { + return members.hashCode(); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/JsonParseException.java b/src/com/massivecraft/core/lib/gson2/JsonParseException.java new file mode 100755 index 00000000..61f9beac --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonParseException.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +/** + * This exception is raised if there is a serious issue that occurs during parsing of a Json + * string. One of the main usages for this class is for the Gson infrastructure. If the incoming + * Json is bad/malicious, an instance of this exception is raised. + * + *

This exception is a {@link RuntimeException} because it is exposed to the client. Using a + * {@link RuntimeException} avoids bad coding practices on the client side where they catch the + * exception and do nothing. It is often the case that you want to blow up if there is a parsing + * error (i.e. often clients do not know how to recover from a {@link JsonParseException}.

+ * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public class JsonParseException extends RuntimeException { + static final long serialVersionUID = -4086729973971783390L; + + /** + * Creates exception with the specified message. If you are wrapping another exception, consider + * using {@link #JsonParseException(String, Throwable)} instead. + * + * @param msg error message describing a possible cause of this exception. + */ + public JsonParseException(String msg) { + super(msg); + } + + /** + * Creates exception with the specified message and cause. + * + * @param msg error message describing what happened. + * @param cause root exception that caused this exception to be thrown. + */ + public JsonParseException(String msg, Throwable cause) { + super(msg, cause); + } + + /** + * Creates exception with the specified cause. Consider using + * {@link #JsonParseException(String, Throwable)} instead if you can describe what happened. + * + * @param cause root exception that caused this exception to be thrown. + */ + public JsonParseException(Throwable cause) { + super(cause); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/JsonParser.java b/src/com/massivecraft/core/lib/gson2/JsonParser.java new file mode 100755 index 00000000..79f92fc5 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonParser.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2009 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.internal.Streams; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.MalformedJsonException; + +import java.io.EOFException; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +/** + * A parser to parse Json into a parse tree of {@link JsonElement}s + * + * @author Inderjeet Singh + * @author Joel Leitch + * @since 1.3 + */ +public final class JsonParser { + + /** + * Parses the specified JSON string into a parse tree + * + * @param json JSON text + * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON + * @throws JsonParseException if the specified text is not valid JSON + * @since 1.3 + */ + public JsonElement parse(String json) throws JsonSyntaxException { + return parse(new StringReader(json)); + } + + /** + * Parses the specified JSON string into a parse tree + * + * @param json JSON text + * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON + * @throws JsonParseException if the specified text is not valid JSON + * @since 1.3 + */ + public JsonElement parse(Reader json) throws JsonIOException, JsonSyntaxException { + try { + JsonReader jsonReader = new JsonReader(json); + JsonElement element = parse(jsonReader); + if (!element.isJsonNull() && jsonReader.peek() != JsonToken.END_DOCUMENT) { + throw new JsonSyntaxException("Did not consume the entire document."); + } + return element; + } catch (MalformedJsonException e) { + throw new JsonSyntaxException(e); + } catch (IOException e) { + throw new JsonIOException(e); + } catch (NumberFormatException e) { + throw new JsonSyntaxException(e); + } + } + + /** + * Returns the next value from the JSON stream as a parse tree. + * + * @throws JsonParseException if there is an IOException or if the specified + * text is not valid JSON + * @since 1.6 + */ + public JsonElement parse(JsonReader json) throws JsonIOException, JsonSyntaxException { + boolean lenient = json.isLenient(); + json.setLenient(true); + try { + return Streams.parse(json); + } catch (StackOverflowError e) { + throw new JsonParseException("Failed parsing JSON source: " + json + " to Json", e); + } catch (OutOfMemoryError e) { + throw new JsonParseException("Failed parsing JSON source: " + json + " to Json", e); + } catch (JsonParseException e) { + if (e.getCause() instanceof EOFException) { + return JsonNull.INSTANCE; + } + throw e; + } finally { + json.setLenient(lenient); + } + } +} diff --git a/src/com/massivecraft/core/lib/gson2/JsonPrimitive.java b/src/com/massivecraft/core/lib/gson2/JsonPrimitive.java new file mode 100755 index 00000000..95dec800 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonPrimitive.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.internal.$Gson$Preconditions; +import com.massivecraft.core.lib.gson2.internal.LazilyParsedNumber; + +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * A class representing a Json primitive value. A primitive value + * is either a String, a Java primitive, or a Java primitive + * wrapper type. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public final class JsonPrimitive extends JsonElement { + + private static final Class[] PRIMITIVE_TYPES = { int.class, long.class, short.class, + float.class, double.class, byte.class, boolean.class, char.class, Integer.class, Long.class, + Short.class, Float.class, Double.class, Byte.class, Boolean.class, Character.class }; + + private Object value; + + /** + * Create a primitive containing a boolean value. + * + * @param bool the value to create the primitive with. + */ + public JsonPrimitive(Boolean bool) { + setValue(bool); + } + + /** + * Create a primitive containing a {@link Number}. + * + * @param number the value to create the primitive with. + */ + public JsonPrimitive(Number number) { + setValue(number); + } + + /** + * Create a primitive containing a String value. + * + * @param string the value to create the primitive with. + */ + public JsonPrimitive(String string) { + setValue(string); + } + + /** + * Create a primitive containing a character. The character is turned into a one character String + * since Json only supports String. + * + * @param c the value to create the primitive with. + */ + public JsonPrimitive(Character c) { + setValue(c); + } + + /** + * Create a primitive using the specified Object. It must be an instance of {@link Number}, a + * Java primitive type, or a String. + * + * @param primitive the value to create the primitive with. + */ + JsonPrimitive(Object primitive) { + setValue(primitive); + } + + void setValue(Object primitive) { + if (primitive instanceof Character) { + // convert characters to strings since in JSON, characters are represented as a single + // character string + char c = ((Character) primitive).charValue(); + this.value = String.valueOf(c); + } else { + $Gson$Preconditions.checkArgument(primitive instanceof Number + || isPrimitiveOrString(primitive)); + this.value = primitive; + } + } + + /** + * Check whether this primitive contains a boolean value. + * + * @return true if this primitive contains a boolean value, false otherwise. + */ + public boolean isBoolean() { + return value instanceof Boolean; + } + + /** + * convenience method to get this element as a {@link Boolean}. + * + * @return get this element as a {@link Boolean}. + */ + @Override + Boolean getAsBooleanWrapper() { + return (Boolean) value; + } + + /** + * convenience method to get this element as a boolean value. + * + * @return get this element as a primitive boolean value. + */ + @Override + public boolean getAsBoolean() { + if (isBoolean()) { + return getAsBooleanWrapper().booleanValue(); + } else { + // Check to see if the value as a String is "true" in any case. + return Boolean.parseBoolean(getAsString()); + } + } + + /** + * Check whether this primitive contains a Number. + * + * @return true if this primitive contains a Number, false otherwise. + */ + public boolean isNumber() { + return value instanceof Number; + } + + /** + * convenience method to get this element as a Number. + * + * @return get this element as a Number. + * @throws NumberFormatException if the value contained is not a valid Number. + */ + @Override + public Number getAsNumber() { + return value instanceof String ? new LazilyParsedNumber((String) value) : (Number) value; + } + + /** + * Check whether this primitive contains a String value. + * + * @return true if this primitive contains a String value, false otherwise. + */ + public boolean isString() { + return value instanceof String; + } + + /** + * convenience method to get this element as a String. + * + * @return get this element as a String. + */ + @Override + public String getAsString() { + if (isNumber()) { + return getAsNumber().toString(); + } else if (isBoolean()) { + return getAsBooleanWrapper().toString(); + } else { + return (String) value; + } + } + + /** + * convenience method to get this element as a primitive double. + * + * @return get this element as a primitive double. + * @throws NumberFormatException if the value contained is not a valid double. + */ + @Override + public double getAsDouble() { + return isNumber() ? getAsNumber().doubleValue() : Double.parseDouble(getAsString()); + } + + /** + * convenience method to get this element as a {@link BigDecimal}. + * + * @return get this element as a {@link BigDecimal}. + * @throws NumberFormatException if the value contained is not a valid {@link BigDecimal}. + */ + @Override + public BigDecimal getAsBigDecimal() { + return value instanceof BigDecimal ? (BigDecimal) value : new BigDecimal(value.toString()); + } + + /** + * convenience method to get this element as a {@link BigInteger}. + * + * @return get this element as a {@link BigInteger}. + * @throws NumberFormatException if the value contained is not a valid {@link BigInteger}. + */ + @Override + public BigInteger getAsBigInteger() { + return value instanceof BigInteger ? + (BigInteger) value : new BigInteger(value.toString()); + } + + /** + * convenience method to get this element as a float. + * + * @return get this element as a float. + * @throws NumberFormatException if the value contained is not a valid float. + */ + @Override + public float getAsFloat() { + return isNumber() ? getAsNumber().floatValue() : Float.parseFloat(getAsString()); + } + + /** + * convenience method to get this element as a primitive long. + * + * @return get this element as a primitive long. + * @throws NumberFormatException if the value contained is not a valid long. + */ + @Override + public long getAsLong() { + return isNumber() ? getAsNumber().longValue() : Long.parseLong(getAsString()); + } + + /** + * convenience method to get this element as a primitive short. + * + * @return get this element as a primitive short. + * @throws NumberFormatException if the value contained is not a valid short value. + */ + @Override + public short getAsShort() { + return isNumber() ? getAsNumber().shortValue() : Short.parseShort(getAsString()); + } + + /** + * convenience method to get this element as a primitive integer. + * + * @return get this element as a primitive integer. + * @throws NumberFormatException if the value contained is not a valid integer. + */ + @Override + public int getAsInt() { + return isNumber() ? getAsNumber().intValue() : Integer.parseInt(getAsString()); + } + + @Override + public byte getAsByte() { + return isNumber() ? getAsNumber().byteValue() : Byte.parseByte(getAsString()); + } + + @Override + public char getAsCharacter() { + return getAsString().charAt(0); + } + + private static boolean isPrimitiveOrString(Object target) { + if (target instanceof String) { + return true; + } + + Class classOfPrimitive = target.getClass(); + for (Class standardPrimitive : PRIMITIVE_TYPES) { + if (standardPrimitive.isAssignableFrom(classOfPrimitive)) { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + if (value == null) { + return 31; + } + // Using recommended hashing algorithm from Effective Java for longs and doubles + if (isIntegral(this)) { + long value = getAsNumber().longValue(); + return (int) (value ^ (value >>> 32)); + } + if (value instanceof Number) { + long value = Double.doubleToLongBits(getAsNumber().doubleValue()); + return (int) (value ^ (value >>> 32)); + } + return value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + JsonPrimitive other = (JsonPrimitive)obj; + if (value == null) { + return other.value == null; + } + if (isIntegral(this) && isIntegral(other)) { + return getAsNumber().longValue() == other.getAsNumber().longValue(); + } + if (value instanceof Number && other.value instanceof Number) { + double a = getAsNumber().doubleValue(); + // Java standard types other than double return true for two NaN. So, need + // special handling for double. + double b = other.getAsNumber().doubleValue(); + return a == b || (Double.isNaN(a) && Double.isNaN(b)); + } + return value.equals(other.value); + } + + /** + * Returns true if the specified number is an integral type + * (Long, Integer, Short, Byte, BigInteger) + */ + private static boolean isIntegral(JsonPrimitive primitive) { + if (primitive.value instanceof Number) { + Number number = (Number) primitive.value; + return number instanceof BigInteger || number instanceof Long || number instanceof Integer + || number instanceof Short || number instanceof Byte; + } + return false; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/JsonSerializationContext.java b/src/com/massivecraft/core/lib/gson2/JsonSerializationContext.java new file mode 100755 index 00000000..f13f0377 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonSerializationContext.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.lang.reflect.Type; + +/** + * Context for serialization that is passed to a custom serializer during invocation of its + * {@link JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public interface JsonSerializationContext { + + /** + * Invokes default serialization on the specified object. + * + * @param src the object that needs to be serialized. + * @return a tree of {@link JsonElement}s corresponding to the serialized form of {@code src}. + */ + public JsonElement serialize(Object src); + + /** + * Invokes default serialization on the specified object passing the specific type information. + * It should never be invoked on the element received as a parameter of the + * {@link JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method. Doing + * so will result in an infinite loop since Gson will in-turn call the custom serializer again. + * + * @param src the object that needs to be serialized. + * @param typeOfSrc the actual genericized type of src object. + * @return a tree of {@link JsonElement}s corresponding to the serialized form of {@code src}. + */ + public JsonElement serialize(Object src, Type typeOfSrc); +} diff --git a/src/com/massivecraft/core/lib/gson2/JsonSerializer.java b/src/com/massivecraft/core/lib/gson2/JsonSerializer.java new file mode 100755 index 00000000..9589cec1 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonSerializer.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.lang.reflect.Type; + +/** + * Interface representing a custom serializer for Json. You should write a custom serializer, if + * you are not happy with the default serialization done by Gson. You will also need to register + * this serializer through {@link com.massivecraft.core.lib.gson2.GsonBuilder#registerTypeAdapter(Type, Object)}. + * + *

Let us look at example where defining a serializer will be useful. The {@code Id} class + * defined below has two fields: {@code clazz} and {@code value}.

+ * + *

+ * public class Id<T> {
+ *   private final Class<T> clazz;
+ *   private final long value;
+ *
+ *   public Id(Class<T> clazz, long value) {
+ *     this.clazz = clazz;
+ *     this.value = value;
+ *   }
+ *
+ *   public long getValue() {
+ *     return value;
+ *   }
+ * }
+ * 

+ * + *

The default serialization of {@code Id(com.foo.MyObject.class, 20L)} will be + * {"clazz":com.foo.MyObject,"value":20}. Suppose, you just want the output to be + * the value instead, which is {@code 20} in this case. You can achieve that by writing a custom + * serializer:

+ * + *

+ * class IdSerializer implements JsonSerializer<Id>() {
+ *   public JsonElement serialize(Id id, Type typeOfId, JsonSerializationContext context) {
+ *     return new JsonPrimitive(id.getValue());
+ *   }
+ * }
+ * 

+ * + *

You will also need to register {@code IdSerializer} with Gson as follows:

+ *
+ * Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdSerializer()).create();
+ * 
+ * + * @author Inderjeet Singh + * @author Joel Leitch + * + * @param type for which the serializer is being registered. It is possible that a serializer + * may be asked to serialize a specific generic type of the T. + */ +public interface JsonSerializer { + + /** + * Gson invokes this call-back method during serialization when it encounters a field of the + * specified type. + * + *

In the implementation of this call-back method, you should consider invoking + * {@link JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any + * non-trivial field of the {@code src} object. However, you should never invoke it on the + * {@code src} object itself since that will cause an infinite loop (Gson will call your + * call-back method again).

+ * + * @param src the object that needs to be converted to Json. + * @param typeOfSrc the actual type (fully genericized version) of the source object. + * @return a JsonElement corresponding to the specified object. + */ + public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context); +} diff --git a/src/com/massivecraft/core/lib/gson2/JsonStreamParser.java b/src/com/massivecraft/core/lib/gson2/JsonStreamParser.java new file mode 100755 index 00000000..e01d5571 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonStreamParser.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2009 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.massivecraft.core.lib.gson2; + +import java.io.EOFException; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import com.massivecraft.core.lib.gson2.internal.Streams; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.MalformedJsonException; + +/** + * A streaming parser that allows reading of multiple {@link JsonElement}s from the specified reader + * asynchronously. + * + *

This class is conditionally thread-safe (see Item 70, Effective Java second edition). To + * properly use this class across multiple threads, you will need to add some external + * synchronization. For example: + * + *

+ * JsonStreamParser parser = new JsonStreamParser("['first'] {'second':10} 'third'");
+ * JsonElement element;
+ * synchronized (parser) {  // synchronize on an object shared by threads
+ *   if (parser.hasNext()) {
+ *     element = parser.next();
+ *   }
+ * }
+ * 
+ * + * @author Inderjeet Singh + * @author Joel Leitch + * @since 1.4 + */ +public final class JsonStreamParser implements Iterator { + + private final JsonReader parser; + private final Object lock; + + /** + * @param json The string containing JSON elements concatenated to each other. + * @since 1.4 + */ + public JsonStreamParser(String json) { + this(new StringReader(json)); + } + + /** + * @param reader The data stream containing JSON elements concatenated to each other. + * @since 1.4 + */ + public JsonStreamParser(Reader reader) { + parser = new JsonReader(reader); + parser.setLenient(true); + lock = new Object(); + } + + /** + * Returns the next available {@link JsonElement} on the reader. Null if none available. + * + * @return the next available {@link JsonElement} on the reader. Null if none available. + * @throws JsonParseException if the incoming stream is malformed JSON. + * @since 1.4 + */ + public JsonElement next() throws JsonParseException { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + try { + return Streams.parse(parser); + } catch (StackOverflowError e) { + throw new JsonParseException("Failed parsing JSON source to Json", e); + } catch (OutOfMemoryError e) { + throw new JsonParseException("Failed parsing JSON source to Json", e); + } catch (JsonParseException e) { + throw e.getCause() instanceof EOFException ? new NoSuchElementException() : e; + } + } + + /** + * Returns true if a {@link JsonElement} is available on the input for consumption + * @return true if a {@link JsonElement} is available on the input, false otherwise + * @since 1.4 + */ + public boolean hasNext() { + synchronized (lock) { + try { + return parser.peek() != JsonToken.END_DOCUMENT; + } catch (MalformedJsonException e) { + throw new JsonSyntaxException(e); + } catch (IOException e) { + throw new JsonIOException(e); + } + } + } + + /** + * This optional {@link Iterator} method is not relevant for stream parsing and hence is not + * implemented. + * @since 1.4 + */ + public void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/JsonSyntaxException.java b/src/com/massivecraft/core/lib/gson2/JsonSyntaxException.java new file mode 100755 index 00000000..624c0d2a --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/JsonSyntaxException.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +/** + * This exception is raised when Gson attempts to read (or write) a malformed + * JSON element. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public final class JsonSyntaxException extends JsonParseException { + + private static final long serialVersionUID = 1L; + + public JsonSyntaxException(String msg) { + super(msg); + } + + public JsonSyntaxException(String msg, Throwable cause) { + super(msg, cause); + } + + /** + * Creates exception with the specified cause. Consider using + * {@link #JsonSyntaxException(String, Throwable)} instead if you can + * describe what actually happened. + * + * @param cause root exception that caused this exception to be thrown. + */ + public JsonSyntaxException(Throwable cause) { + super(cause); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/LongSerializationPolicy.java b/src/com/massivecraft/core/lib/gson2/LongSerializationPolicy.java new file mode 100755 index 00000000..c164d027 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/LongSerializationPolicy.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009 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.massivecraft.core.lib.gson2; + +/** + * Defines the expected format for a {@code long} or {@code Long} type when its serialized. + * + * @since 1.3 + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public enum LongSerializationPolicy { + /** + * This is the "default" serialization policy that will output a {@code long} object as a JSON + * number. For example, assume an object has a long field named "f" then the serialized output + * would be: + * {@code {"f":123}}. + */ + DEFAULT(new DefaultStrategy()), + + /** + * Serializes a long value as a quoted string. For example, assume an object has a long field + * named "f" then the serialized output would be: + * {@code {"f":"123"}}. + */ + STRING(new StringStrategy()); + + private final Strategy strategy; + + private LongSerializationPolicy(Strategy strategy) { + this.strategy = strategy; + } + + /** + * Serialize this {@code value} using this serialization policy. + * + * @param value the long value to be serialized into a {@link JsonElement} + * @return the serialized version of {@code value} + */ + public JsonElement serialize(Long value) { + return strategy.serialize(value); + } + + private interface Strategy { + JsonElement serialize(Long value); + } + + private static class DefaultStrategy implements Strategy { + public JsonElement serialize(Long value) { + return new JsonPrimitive(value); + } + } + + private static class StringStrategy implements Strategy { + public JsonElement serialize(Long value) { + return new JsonPrimitive(String.valueOf(value)); + } + } +} diff --git a/src/com/massivecraft/core/lib/gson2/LowerCamelCaseSeparatorNamingPolicy.java b/src/com/massivecraft/core/lib/gson2/LowerCamelCaseSeparatorNamingPolicy.java new file mode 100755 index 00000000..880eeea9 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/LowerCamelCaseSeparatorNamingPolicy.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +/** + * A {@link FieldNamingStrategy2} that ensures the JSON field names consist of only + * lower case letters and are separated by a particular {@code separatorString}. + * + *

The following is an example:

+ *
+ * class StringWrapper {
+ *   public String AStringField = "abcd";
+ * }
+ *
+ * LowerCamelCaseSeparatorNamingPolicy policy = new LowerCamelCaseSeparatorNamingPolicy("_");
+ * String translatedFieldName =
+ *     policy.translateName(StringWrapper.class.getField("AStringField"));
+ *
+ * assert("a_string_field".equals(translatedFieldName));
+ * 
+ * + * @author Joel Leitch + */ +final class LowerCamelCaseSeparatorNamingPolicy extends CompositionFieldNamingPolicy { + + public LowerCamelCaseSeparatorNamingPolicy(String separatorString) { + super(new CamelCaseSeparatorNamingPolicy(separatorString), new LowerCaseNamingPolicy()); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/LowerCaseNamingPolicy.java b/src/com/massivecraft/core/lib/gson2/LowerCaseNamingPolicy.java new file mode 100755 index 00000000..db0c79c0 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/LowerCaseNamingPolicy.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collection; + +/** + * A {@link FieldNamingStrategy2} that ensures the JSON field names consist of only + * lower case letters. + * + *

The following is an example:

+ *
+ * class IntWrapper {
+ *   public int integerField = 0;
+ * }
+ *
+ * LowerCaseNamingPolicy policy = new LowerCaseNamingPolicy();
+ * String translatedFieldName =
+ *     policy.translateName(IntWrapper.class.getField("integerField"));
+ *
+ * assert("integerfield".equals(translatedFieldName));
+ * 
+ * + * @author Inderjeet Singh + * @author Joel Leitch + */ +final class LowerCaseNamingPolicy extends RecursiveFieldNamingPolicy { + + @Override + protected String translateName(String target, Type fieldType, + Collection annotations) { + return target.toLowerCase(); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/LruCache.java b/src/com/massivecraft/core/lib/gson2/LruCache.java new file mode 100755 index 00000000..9348f6be --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/LruCache.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * An implementation of the {@link Cache} interface that evict objects from the cache using an + * LRU (least recently used) algorithm. Object start getting evicted from the cache once the + * {@code maxCapacity} is reached. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +final class LruCache extends LinkedHashMap implements Cache { + private static final long serialVersionUID = 1L; + + private final int maxCapacity; + + public LruCache(int maxCapacity) { + super(maxCapacity, 0.7F, true); + this.maxCapacity = maxCapacity; + } + + public synchronized void addElement(K key, V value) { + put(key, value); + } + + public synchronized V getElement(K key) { + return get(key); + } + + @Override + protected boolean removeEldestEntry(Map.Entry entry) { + return size() > maxCapacity; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/ModifierBasedExclusionStrategy.java b/src/com/massivecraft/core/lib/gson2/ModifierBasedExclusionStrategy.java new file mode 100755 index 00000000..80bcfad5 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/ModifierBasedExclusionStrategy.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.util.Collection; +import java.util.HashSet; + +/** + * Exclude fields based on particular field modifiers. For a list of possible + * modifiers, see {@link java.lang.reflect.Modifier}. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +final class ModifierBasedExclusionStrategy implements ExclusionStrategy { + private final Collection modifiers; + + public ModifierBasedExclusionStrategy(int... modifiers) { + this.modifiers = new HashSet(); + if (modifiers != null) { + for (int modifier : modifiers) { + this.modifiers.add(modifier); + } + } + } + + public boolean shouldSkipField(FieldAttributes f) { + for (int modifier : modifiers) { + if (f.hasModifier(modifier)) { + return true; + } + } + return false; + } + + public boolean shouldSkipClass(Class clazz) { + return false; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/ModifyFirstLetterNamingPolicy.java b/src/com/massivecraft/core/lib/gson2/ModifyFirstLetterNamingPolicy.java new file mode 100755 index 00000000..2da37cdc --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/ModifyFirstLetterNamingPolicy.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.internal.$Gson$Preconditions; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collection; + +/** + * A {@link FieldNamingStrategy2} that ensures the JSON field names begins with + * an upper case letter. + * + *

The following is an example:

+ *
+ * class StringWrapper {
+ *   public String stringField = "abcd";
+ *   public String _stringField = "efg";
+ * }
+ *
+ * ModifyFirstLetterNamingPolicy policy =
+ *     new ModifyFirstLetterNamingPolicy(LetterModifier.UPPER);
+ * String translatedFieldName =
+ *     policy.translateName(StringWrapper.class.getField("stringField"));
+ *
+ * assert("StringField".equals(translatedFieldName));
+ *
+ * String translatedFieldName =
+ *     policy.translateName(StringWrapper.class.getField("_stringField"));
+ *
+ * assert("_StringField".equals(translatedFieldName));
+ * 
+ * + * @author Joel Leitch + */ +final class ModifyFirstLetterNamingPolicy extends RecursiveFieldNamingPolicy { + + public enum LetterModifier { + UPPER, + LOWER; + } + + private final LetterModifier letterModifier; + + /** + * Creates a new ModifyFirstLetterNamingPolicy that will either modify the first letter of the + * target name to either UPPER case or LOWER case depending on the {@code modifier} parameter. + * + * @param modifier the type of modification that should be performed + * @throws IllegalArgumentException if {@code modifier} is null + */ + ModifyFirstLetterNamingPolicy(LetterModifier modifier) { + this.letterModifier = $Gson$Preconditions.checkNotNull(modifier); + } + + @Override + protected String translateName(String target, Type fieldType, + Collection annotations) { + StringBuilder fieldNameBuilder = new StringBuilder(); + int index = 0; + char firstCharacter = target.charAt(index); + + while (index < target.length() - 1) { + if (Character.isLetter(firstCharacter)) { + break; + } + + fieldNameBuilder.append(firstCharacter); + firstCharacter = target.charAt(++index); + } + + if (index == target.length()) { + return fieldNameBuilder.toString(); + } + + boolean capitalizeFirstLetter = (letterModifier == LetterModifier.UPPER); + if (capitalizeFirstLetter && !Character.isUpperCase(firstCharacter)) { + String modifiedTarget = modifyString(Character.toUpperCase(firstCharacter), target, ++index); + return fieldNameBuilder.append(modifiedTarget).toString(); + } else if (!capitalizeFirstLetter && Character.isUpperCase(firstCharacter)) { + String modifiedTarget = modifyString(Character.toLowerCase(firstCharacter), target, ++index); + return fieldNameBuilder.append(modifiedTarget).toString(); + } else { + return target; + } + } + + private String modifyString(char firstCharacter, String srcString, int indexOfSubstring) { + return (indexOfSubstring < srcString.length()) + ? firstCharacter + srcString.substring(indexOfSubstring) + : String.valueOf(firstCharacter); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/RecursiveFieldNamingPolicy.java b/src/com/massivecraft/core/lib/gson2/RecursiveFieldNamingPolicy.java new file mode 100755 index 00000000..3da9f779 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/RecursiveFieldNamingPolicy.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collection; + +/** + * A mechanism for providing custom field naming in Gson. This allows the client code to translate + * field names into a particular convention that is not supported as a normal Java field + * declaration rules. For example, Java does not support "-" characters in a field name. + * + * @author Joel Leitch + */ +abstract class RecursiveFieldNamingPolicy implements FieldNamingStrategy2 { + + public final String translateName(FieldAttributes f) { + return translateName(f.getName(), f.getDeclaredType(), f.getAnnotations()); + } + + /** + * Performs the specific string translation. + * + * @param target the string object that will be manipulation/translated + * @param fieldType the actual type value of the field + * @param annotations the annotations set on the field + * @return the translated field name + */ + protected abstract String translateName(String target, Type fieldType, Collection annotations); +} diff --git a/src/com/massivecraft/core/lib/gson2/SerializedNameAnnotationInterceptingNamingPolicy.java b/src/com/massivecraft/core/lib/gson2/SerializedNameAnnotationInterceptingNamingPolicy.java new file mode 100755 index 00000000..6edba502 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/SerializedNameAnnotationInterceptingNamingPolicy.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.annotations.SerializedName; + +/** + * A {@link FieldNamingStrategy2} that acts as a chain of responsibility. If the + * {@link com.massivecraft.core.lib.gson2.annotations.SerializedName} annotation is applied to a + * field then this strategy will translate the name to the {@code + * serializedName.value()}; otherwise it delegates to the wrapped + * {@link FieldNamingStrategy2}. + * + *

+ * NOTE: this class performs JSON field name validation for any of the fields + * marked with an {@code @SerializedName} annotation. + *

+ * + * @see SerializedName + * + * @author Joel Leitch + */ +final class SerializedNameAnnotationInterceptingNamingPolicy implements FieldNamingStrategy2 { + private final FieldNamingStrategy2 delegate; + + SerializedNameAnnotationInterceptingNamingPolicy(FieldNamingStrategy2 delegate) { + this.delegate = delegate; + } + + public String translateName(FieldAttributes f) { + SerializedName serializedName = f.getAnnotation(SerializedName.class); + return serializedName == null ? delegate.translateName(f) : serializedName.value(); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/SyntheticFieldExclusionStrategy.java b/src/com/massivecraft/core/lib/gson2/SyntheticFieldExclusionStrategy.java new file mode 100755 index 00000000..43a0e2d2 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/SyntheticFieldExclusionStrategy.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2009 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.massivecraft.core.lib.gson2; + +/** + * A data object that stores attributes of a field. + * + *

This class is immutable; therefore, it can be safely shared across threads. + * + * @author Inderjeet Singh + * @author Joel Leitch + * + * @since 1.4 + */ +final class SyntheticFieldExclusionStrategy implements ExclusionStrategy { + private final boolean skipSyntheticFields; + + SyntheticFieldExclusionStrategy(boolean skipSyntheticFields) { + this.skipSyntheticFields = skipSyntheticFields; + } + + public boolean shouldSkipClass(Class clazz) { + return false; + } + + public boolean shouldSkipField(FieldAttributes f) { + return skipSyntheticFields && f.isSynthetic(); + } + +} diff --git a/src/com/massivecraft/core/lib/gson2/UpperCamelCaseSeparatorNamingPolicy.java b/src/com/massivecraft/core/lib/gson2/UpperCamelCaseSeparatorNamingPolicy.java new file mode 100755 index 00000000..f3901be0 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/UpperCamelCaseSeparatorNamingPolicy.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +/** + * A {@link FieldNamingStrategy2} that ensures the JSON field names consist of mixed + * case letters starting with a capital and are separated by a particular + * {@code separatorString}. + * + *

The following is an example:

+ *
+ * class StringWrapper {
+ *   public String AStringField = "abcd";
+ * }
+ *
+ * UpperCamelCaseSeparatorNamingPolicy policy = new UpperCamelCaseSeparatorNamingPolicy("_");
+ * String translatedFieldName =
+ *     policy.translateName(StringWrapper.class.getField("AStringField"));
+ *
+ * assert("A_String_Field".equals(translatedFieldName));
+ * 
+ * + * @author Joel Leitch + */ +final class UpperCamelCaseSeparatorNamingPolicy extends CompositionFieldNamingPolicy { + + public UpperCamelCaseSeparatorNamingPolicy(String separatorString) { + super(new CamelCaseSeparatorNamingPolicy(separatorString), + new ModifyFirstLetterNamingPolicy(ModifyFirstLetterNamingPolicy.LetterModifier.UPPER)); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/UpperCaseNamingPolicy.java b/src/com/massivecraft/core/lib/gson2/UpperCaseNamingPolicy.java new file mode 100755 index 00000000..557e328c --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/UpperCaseNamingPolicy.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collection; + +/** + * A {@link FieldNamingStrategy2} that ensures the JSON field names consist of only + * upper case letters. + * + *

The following is an example:

+ *
+ * class IntWrapper {
+ *   public int integerField = 0;
+ * }
+ *
+ * UpperCaseNamingPolicy policy = new UpperCaseNamingPolicy();
+ * String translatedFieldName =
+ *     policy.translateName(IntWrapper.class.getField("integerField"));
+ *
+ * assert("INTEGERFIELD".equals(translatedFieldName));
+ * 
+ * + * @author Joel Leitch + */ +final class UpperCaseNamingPolicy extends RecursiveFieldNamingPolicy { + + @Override + protected String translateName(String target, Type fieldType, Collection annotations) { + return target.toUpperCase(); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/VersionConstants.java b/src/com/massivecraft/core/lib/gson2/VersionConstants.java new file mode 100755 index 00000000..ce904f69 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/VersionConstants.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +/** + * Class contain all constants for versioning support. + * + * @author Joel Leitch + */ +final class VersionConstants { + // Prevent instantiation + private VersionConstants() { } + + static final double IGNORE_VERSIONS = -1D; +} diff --git a/src/com/massivecraft/core/lib/gson2/VersionExclusionStrategy.java b/src/com/massivecraft/core/lib/gson2/VersionExclusionStrategy.java new file mode 100755 index 00000000..cb6dd545 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/VersionExclusionStrategy.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2; + +import com.massivecraft.core.lib.gson2.annotations.Since; +import com.massivecraft.core.lib.gson2.annotations.Until; +import com.massivecraft.core.lib.gson2.internal.$Gson$Preconditions; + +/** + * This strategy will exclude any files and/or class that are passed the + * {@link #version} value. + * + * @author Joel Leitch + */ +final class VersionExclusionStrategy implements ExclusionStrategy { + private final double version; + + VersionExclusionStrategy(double version) { + $Gson$Preconditions.checkArgument(version >= 0.0D); + this.version = version; + } + + public boolean shouldSkipField(FieldAttributes f) { + return !isValidVersion(f.getAnnotation(Since.class), f.getAnnotation(Until.class)); + } + + public boolean shouldSkipClass(Class clazz) { + return !isValidVersion(clazz.getAnnotation(Since.class), clazz.getAnnotation(Until.class)); + } + + private boolean isValidVersion(Since since, Until until) { + return (isValidSince(since) && isValidUntil(until)); + } + + private boolean isValidSince(Since annotation) { + if (annotation != null) { + double annotationVersion = annotation.value(); + if (annotationVersion > version) { + return false; + } + } + return true; + } + + private boolean isValidUntil(Until annotation) { + if (annotation != null) { + double annotationVersion = annotation.value(); + if (annotationVersion <= version) { + return false; + } + } + return true; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/annotations/Expose.java b/src/com/massivecraft/core/lib/gson2/annotations/Expose.java new file mode 100755 index 00000000..1a1f4987 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/annotations/Expose.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that indicates this member should be exposed for JSON + * serialization or deserialization. + * + *

This annotation has no effect unless you build {@link com.massivecraft.core.lib.gson2.Gson} + * with a {@link com.massivecraft.core.lib.gson2.GsonBuilder} and invoke + * {@link com.massivecraft.core.lib.gson2.GsonBuilder#excludeFieldsWithoutExposeAnnotation()} + * method.

+ * + *

Here is an example of how this annotation is meant to be used: + *

+ * public class User {
+ *   @Expose private String firstName;
+ *   @Expose(serialize = false) private String lastName;
+ *   @Expose (serialize = false, deserialize = false) private String emailAddress;
+ *   private String password;
+ * }
+ * 

+ * If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()} + * methods will use the {@code password} field along-with {@code firstName}, {@code lastName}, + * and {@code emailAddress} for serialization and deserialization. However, if you created Gson + * with {@code Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()} + * then the {@code toJson()} and {@code fromJson()} methods of Gson will exclude the + * {@code password} field. This is because the {@code password} field is not marked with the + * {@code @Expose} annotation. Gson will also exclude {@code lastName} and {@code emailAddress} + * from serialization since {@code serialize} is set to {@code false}. Similarly, Gson will + * exclude {@code emailAddress} from deserialization since {@code deserialize} is set to false. + * + *

Note that another way to achieve the same effect would have been to just mark the + * {@code password} field as {@code transient}, and Gson would have excluded it even with default + * settings. The {@code @Expose} annotation is useful in a style of programming where you want to + * explicitly specify all fields that should get considered for serialization or deserialization. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Expose { + + /** + * If {@code true}, the field marked with this annotation is written out in the JSON while + * serializing. If {@code false}, the field marked with this annotation is skipped from the + * serialized output. Defaults to {@code true}. + * @since 1.4 + */ + public boolean serialize() default true; + + /** + * If {@code true}, the field marked with this annotation is deserialized from the JSON. + * If {@code false}, the field marked with this annotation is skipped during deserialization. + * Defaults to {@code true}. + * @since 1.4 + */ + public boolean deserialize() default true; +} diff --git a/src/com/massivecraft/core/lib/gson2/annotations/SerializedName.java b/src/com/massivecraft/core/lib/gson2/annotations/SerializedName.java new file mode 100755 index 00000000..a3bb0044 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/annotations/SerializedName.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that indicates this member should be serialized to JSON with + * the provided name value as its field name. + * + *

This annotation will override any {@link com.massivecraft.core.lib.gson2.FieldNamingPolicy}, including + * the default field naming policy, that may have been set on the {@link com.massivecraft.core.lib.gson2.Gson} + * instance. A different naming policy can set using the {@code GsonBuilder} class. See + * {@link com.massivecraft.core.lib.gson2.GsonBuilder#setFieldNamingPolicy(com.massivecraft.core.lib.gson2.FieldNamingPolicy)} + * for more information.

+ * + *

Here is an example of how this annotation is meant to be used:

+ *
+ * public class SomeClassWithFields {
+ *   @SerializedName("name") private final String someField;
+ *   private final String someOtherField;
+ *
+ *   public SomeClassWithFields(String a, String b) {
+ *     this.someField = a;
+ *     this.someOtherField = b;
+ *   }
+ * }
+ * 
+ * + *

The following shows the output that is generated when serializing an instance of the + * above example class:

+ *
+ * SomeClassWithFields objectToSerialize = new SomeClassWithFields("a", "b");
+ * Gson gson = new Gson();
+ * String jsonRepresentation = gson.toJson(objectToSerialize);
+ * System.out.println(jsonRepresentation);
+ *
+ * ===== OUTPUT =====
+ * {"name":"a","someOtherField":"b"}
+ * 
+ * + *

NOTE: The value you specify in this annotation must be a valid JSON field name.

+ * + * @see com.massivecraft.core.lib.gson2.FieldNamingPolicy + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SerializedName { + + /** + * @return the desired name of the field when it is serialized + */ + String value(); +} diff --git a/src/com/massivecraft/core/lib/gson2/annotations/Since.java b/src/com/massivecraft/core/lib/gson2/annotations/Since.java new file mode 100755 index 00000000..01453203 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/annotations/Since.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that indicates the version number since a member or a type has been present. + * This annotation is useful to manage versioning of your Json classes for a web-service. + * + *

+ * This annotation has no effect unless you build {@link com.massivecraft.core.lib.gson2.Gson} with a + * {@link com.massivecraft.core.lib.gson2.GsonBuilder} and invoke + * {@link com.massivecraft.core.lib.gson2.GsonBuilder#setVersion(double)} method. + * + *

Here is an example of how this annotation is meant to be used:

+ *
+ * public class User {
+ *   private String firstName;
+ *   private String lastName;
+ *   @Since(1.0) private String emailAddress;
+ *   @Since(1.0) private String password;
+ *   @Since(1.1) private Address address;
+ * }
+ * 
+ * + *

If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()} + * methods will use all the fields for serialization and deserialization. However, if you created + * Gson with {@code Gson gson = new GsonBuilder().setVersion(1.0).create()} then the + * {@code toJson()} and {@code fromJson()} methods of Gson will exclude the {@code address} field + * since it's version number is set to {@code 1.1}.

+ * + * @author Inderjeet Singh + * @author Joel Leitch + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.TYPE}) +public @interface Since { + /** + * the value indicating a version number since this member + * or type has been present. + */ + double value(); +} diff --git a/src/com/massivecraft/core/lib/gson2/annotations/Until.java b/src/com/massivecraft/core/lib/gson2/annotations/Until.java new file mode 100755 index 00000000..1cbcaea6 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/annotations/Until.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that indicates the version number until a member or a type should be present. + * Basically, if Gson is created with a version number that exceeds the value stored in the + * {@code Until} annotation then the field will be ignored from the JSON output. This annotation + * is useful to manage versioning of your JSON classes for a web-service. + * + *

+ * This annotation has no effect unless you build {@link com.massivecraft.core.lib.gson2.Gson} with a + * {@link com.massivecraft.core.lib.gson2.GsonBuilder} and invoke + * {@link com.massivecraft.core.lib.gson2.GsonBuilder#setVersion(double)} method. + * + *

Here is an example of how this annotation is meant to be used:

+ *
+ * public class User {
+ *   private String firstName;
+ *   private String lastName;
+ *   @Until(1.1) private String emailAddress;
+ *   @Until(1.1) private String password;
+ * }
+ * 
+ * + *

If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()} + * methods will use all the fields for serialization and deserialization. However, if you created + * Gson with {@code Gson gson = new GsonBuilder().setVersion(1.2).create()} then the + * {@code toJson()} and {@code fromJson()} methods of Gson will exclude the {@code emailAddress} + * and {@code password} fields from the example above, because the version number passed to the + * GsonBuilder, {@code 1.2}, exceeds the version number set on the {@code Until} annotation, + * {@code 1.1}, for those fields. + * + * @author Inderjeet Singh + * @author Joel Leitch + * @since 1.3 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.TYPE}) +public @interface Until { + + /** + * the value indicating a version number until this member + * or type should be ignored. + */ + double value(); +} diff --git a/src/com/massivecraft/core/lib/gson2/annotations/package-info.java b/src/com/massivecraft/core/lib/gson2/annotations/package-info.java new file mode 100755 index 00000000..015cf9a2 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/annotations/package-info.java @@ -0,0 +1,6 @@ +/** + * This package provides annotations that can be used with {@link com.massivecraft.core.lib.gson2.Gson}. + * + * @author Inderjeet Singh, Joel Leitch + */ +package com.massivecraft.core.lib.gson2.annotations; \ No newline at end of file diff --git a/src/com/massivecraft/core/lib/gson2/internal/$Gson$Preconditions.java b/src/com/massivecraft/core/lib/gson2/internal/$Gson$Preconditions.java new file mode 100755 index 00000000..bc08dbcf --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/$Gson$Preconditions.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2.internal; + +/** + * A simple utility class used to check method Preconditions. + * + *

+ * public long divideBy(long value) {
+ *   Preconditions.checkArgument(value != 0);
+ *   return this.value / value;
+ * }
+ * 
+ * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public final class $Gson$Preconditions { + public static T checkNotNull(T obj) { + if (obj == null) { + throw new NullPointerException(); + } + return obj; + } + + public static void checkArgument(boolean condition) { + if (!condition) { + throw new IllegalArgumentException(); + } + } + + public static void checkState(boolean condition) { + if (!condition) { + throw new IllegalStateException(); + } + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/$Gson$Types.java b/src/com/massivecraft/core/lib/gson2/internal/$Gson$Types.java new file mode 100755 index 00000000..e3970a77 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/$Gson$Types.java @@ -0,0 +1,592 @@ +/** + * Copyright (C) 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.massivecraft.core.lib.gson2.internal; + +import static com.massivecraft.core.lib.gson2.internal.$Gson$Preconditions.checkArgument; +import static com.massivecraft.core.lib.gson2.internal.$Gson$Preconditions.checkNotNull; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Properties; + +/** + * Static methods for working with types. + * + * @author Bob Lee + * @author Jesse Wilson + */ +public final class $Gson$Types { + static final Type[] EMPTY_TYPE_ARRAY = new Type[] {}; + + private $Gson$Types() {} + + /** + * Returns a new parameterized type, applying {@code typeArguments} to + * {@code rawType} and enclosed by {@code ownerType}. + * + * @return a {@link java.io.Serializable serializable} parameterized type. + */ + public static ParameterizedType newParameterizedTypeWithOwner( + Type ownerType, Type rawType, Type... typeArguments) { + return new ParameterizedTypeImpl(ownerType, rawType, typeArguments); + } + + /** + * Returns an array type whose elements are all instances of + * {@code componentType}. + * + * @return a {@link java.io.Serializable serializable} generic array type. + */ + public static GenericArrayType arrayOf(Type componentType) { + return new GenericArrayTypeImpl(componentType); + } + + /** + * Returns a type that represents an unknown type that extends {@code bound}. + * For example, if {@code bound} is {@code CharSequence.class}, this returns + * {@code ? extends CharSequence}. If {@code bound} is {@code Object.class}, + * this returns {@code ?}, which is shorthand for {@code ? extends Object}. + */ + public static WildcardType subtypeOf(Type bound) { + return new WildcardTypeImpl(new Type[] { bound }, EMPTY_TYPE_ARRAY); + } + + /** + * Returns a type that represents an unknown supertype of {@code bound}. For + * example, if {@code bound} is {@code String.class}, this returns {@code ? + * super String}. + */ + public static WildcardType supertypeOf(Type bound) { + return new WildcardTypeImpl(new Type[] { Object.class }, new Type[] { bound }); + } + + /** + * Returns a type that is functionally equal but not necessarily equal + * according to {@link Object#equals(Object) Object.equals()}. The returned + * type is {@link java.io.Serializable}. + */ + public static Type canonicalize(Type type) { + if (type instanceof Class) { + Class c = (Class) type; + return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c; + + } else if (type instanceof ParameterizedType) { + ParameterizedType p = (ParameterizedType) type; + return new ParameterizedTypeImpl(p.getOwnerType(), + p.getRawType(), p.getActualTypeArguments()); + + } else if (type instanceof GenericArrayType) { + GenericArrayType g = (GenericArrayType) type; + return new GenericArrayTypeImpl(g.getGenericComponentType()); + + } else if (type instanceof WildcardType) { + WildcardType w = (WildcardType) type; + return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds()); + + } else { + // type is either serializable as-is or unsupported + return type; + } + } + + public static Class getRawType(Type type) { + if (type instanceof Class) { + // type is a normal class. + return (Class) type; + + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + + // I'm not exactly sure why getRawType() returns Type instead of Class. + // Neal isn't either but suspects some pathological case related + // to nested classes exists. + Type rawType = parameterizedType.getRawType(); + checkArgument(rawType instanceof Class); + return (Class) rawType; + + } else if (type instanceof GenericArrayType) { + Type componentType = ((GenericArrayType)type).getGenericComponentType(); + return Array.newInstance(getRawType(componentType), 0).getClass(); + + } else if (type instanceof TypeVariable) { + // we could use the variable's bounds, but that won't work if there are multiple. + // having a raw type that's more general than necessary is okay + return Object.class; + + } else if (type instanceof WildcardType) { + return getRawType(((WildcardType) type).getUpperBounds()[0]); + + } else { + String className = type == null ? "null" : type.getClass().getName(); + throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " + + "GenericArrayType, but <" + type + "> is of type " + className); + } + } + + static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + /** + * Returns true if {@code a} and {@code b} are equal. + */ + public static boolean equals(Type a, Type b) { + if (a == b) { + // also handles (a == null && b == null) + return true; + + } else if (a instanceof Class) { + // Class already specifies equals(). + return a.equals(b); + + } else if (a instanceof ParameterizedType) { + if (!(b instanceof ParameterizedType)) { + return false; + } + + // TODO: save a .clone() call + ParameterizedType pa = (ParameterizedType) a; + ParameterizedType pb = (ParameterizedType) b; + return equal(pa.getOwnerType(), pb.getOwnerType()) + && pa.getRawType().equals(pb.getRawType()) + && Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments()); + + } else if (a instanceof GenericArrayType) { + if (!(b instanceof GenericArrayType)) { + return false; + } + + GenericArrayType ga = (GenericArrayType) a; + GenericArrayType gb = (GenericArrayType) b; + return equals(ga.getGenericComponentType(), gb.getGenericComponentType()); + + } else if (a instanceof WildcardType) { + if (!(b instanceof WildcardType)) { + return false; + } + + WildcardType wa = (WildcardType) a; + WildcardType wb = (WildcardType) b; + return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds()) + && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds()); + + } else if (a instanceof TypeVariable) { + if (!(b instanceof TypeVariable)) { + return false; + } + TypeVariable va = (TypeVariable) a; + TypeVariable vb = (TypeVariable) b; + return va.getGenericDeclaration() == vb.getGenericDeclaration() + && va.getName().equals(vb.getName()); + + } else { + // This isn't a type we support. Could be a generic array type, wildcard type, etc. + return false; + } + } + + private static int hashCodeOrZero(Object o) { + return o != null ? o.hashCode() : 0; + } + + public static String typeToString(Type type) { + return type instanceof Class ? ((Class) type).getName() : type.toString(); + } + + /** + * Returns the generic supertype for {@code supertype}. For example, given a class {@code + * IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set} and the + * result when the supertype is {@code Collection.class} is {@code Collection}. + */ + static Type getGenericSupertype(Type context, Class rawType, Class toResolve) { + if (toResolve == rawType) { + return context; + } + + // we skip searching through interfaces if unknown is an interface + if (toResolve.isInterface()) { + Class[] interfaces = rawType.getInterfaces(); + for (int i = 0, length = interfaces.length; i < length; i++) { + if (interfaces[i] == toResolve) { + return rawType.getGenericInterfaces()[i]; + } else if (toResolve.isAssignableFrom(interfaces[i])) { + return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve); + } + } + } + + // check our supertypes + if (!rawType.isInterface()) { + while (rawType != Object.class) { + Class rawSupertype = rawType.getSuperclass(); + if (rawSupertype == toResolve) { + return rawType.getGenericSuperclass(); + } else if (toResolve.isAssignableFrom(rawSupertype)) { + return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve); + } + rawType = rawSupertype; + } + } + + // we can't resolve this further + return toResolve; + } + + /** + * Returns the generic form of {@code supertype}. For example, if this is {@code + * ArrayList}, this returns {@code Iterable} given the input {@code + * Iterable.class}. + * + * @param supertype a superclass of, or interface implemented by, this. + */ + static Type getSupertype(Type context, Class contextRawType, Class supertype) { + checkArgument(supertype.isAssignableFrom(contextRawType)); + return resolve(context, contextRawType, + $Gson$Types.getGenericSupertype(context, contextRawType, supertype)); + } + + /** + * Returns true if this type is an array. + */ + public static boolean isArray(Type type) { + return type instanceof GenericArrayType + || (type instanceof Class && ((Class) type).isArray()); + } + + /** + * Returns the component type of this array type. + * @throws ClassCastException if this type is not an array. + */ + public static Type getArrayComponentType(Type array) { + return array instanceof GenericArrayType + ? ((GenericArrayType) array).getGenericComponentType() + : ((Class) array).getComponentType(); + } + + /** + * Returns the element type of this collection type. + * @throws IllegalArgumentException if this type is not a collection. + */ + public static Type getCollectionElementType(Type context, Class contextRawType) { + Type collectionType = getSupertype(context, contextRawType, Collection.class); + + if (collectionType instanceof WildcardType) { + collectionType = ((WildcardType)collectionType).getUpperBounds()[0]; + } + if (collectionType instanceof ParameterizedType) { + return ((ParameterizedType) collectionType).getActualTypeArguments()[0]; + } + return Object.class; + } + + /** + * Returns a two element array containing this map's key and value types in + * positions 0 and 1 respectively. + */ + public static Type[] getMapKeyAndValueTypes(Type context, Class contextRawType) { + /* + * Work around a problem with the declaration of java.util.Properties. That + * class should extend Hashtable, but it's declared to + * extend Hashtable. + */ + if (context == Properties.class) { + return new Type[] { String.class, String.class }; // TODO: test subclasses of Properties! + } + + Type mapType = getSupertype(context, contextRawType, Map.class); + // TODO: strip wildcards? + if (mapType instanceof ParameterizedType) { + ParameterizedType mapParameterizedType = (ParameterizedType) mapType; + return mapParameterizedType.getActualTypeArguments(); + } + return new Type[] { Object.class, Object.class }; + } + + public static Type resolve(Type context, Class contextRawType, Type toResolve) { + // this implementation is made a little more complicated in an attempt to avoid object-creation + while (true) { + if (toResolve instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) toResolve; + toResolve = resolveTypeVariable(context, contextRawType, typeVariable); + if (toResolve == typeVariable) { + return toResolve; + } + + } else if (toResolve instanceof Class && ((Class) toResolve).isArray()) { + Class original = (Class) toResolve; + Type componentType = original.getComponentType(); + Type newComponentType = resolve(context, contextRawType, componentType); + return componentType == newComponentType + ? original + : arrayOf(newComponentType); + + } else if (toResolve instanceof GenericArrayType) { + GenericArrayType original = (GenericArrayType) toResolve; + Type componentType = original.getGenericComponentType(); + Type newComponentType = resolve(context, contextRawType, componentType); + return componentType == newComponentType + ? original + : arrayOf(newComponentType); + + } else if (toResolve instanceof ParameterizedType) { + ParameterizedType original = (ParameterizedType) toResolve; + Type ownerType = original.getOwnerType(); + Type newOwnerType = resolve(context, contextRawType, ownerType); + boolean changed = newOwnerType != ownerType; + + Type[] args = original.getActualTypeArguments(); + for (int t = 0, length = args.length; t < length; t++) { + Type resolvedTypeArgument = resolve(context, contextRawType, args[t]); + if (resolvedTypeArgument != args[t]) { + if (!changed) { + args = args.clone(); + changed = true; + } + args[t] = resolvedTypeArgument; + } + } + + return changed + ? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args) + : original; + + } else if (toResolve instanceof WildcardType) { + WildcardType original = (WildcardType) toResolve; + Type[] originalLowerBound = original.getLowerBounds(); + Type[] originalUpperBound = original.getUpperBounds(); + + if (originalLowerBound.length == 1) { + Type lowerBound = resolve(context, contextRawType, originalLowerBound[0]); + if (lowerBound != originalLowerBound[0]) { + return supertypeOf(lowerBound); + } + } else if (originalUpperBound.length == 1) { + Type upperBound = resolve(context, contextRawType, originalUpperBound[0]); + if (upperBound != originalUpperBound[0]) { + return subtypeOf(upperBound); + } + } + return original; + + } else { + return toResolve; + } + } + } + + static Type resolveTypeVariable(Type context, Class contextRawType, TypeVariable unknown) { + Class declaredByRaw = declaringClassOf(unknown); + + // we can't reduce this further + if (declaredByRaw == null) { + return unknown; + } + + Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw); + if (declaredBy instanceof ParameterizedType) { + int index = indexOf(declaredByRaw.getTypeParameters(), unknown); + return ((ParameterizedType) declaredBy).getActualTypeArguments()[index]; + } + + return unknown; + } + + private static int indexOf(Object[] array, Object toFind) { + for (int i = 0; i < array.length; i++) { + if (toFind.equals(array[i])) { + return i; + } + } + throw new NoSuchElementException(); + } + + /** + * Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by + * a class. + */ + private static Class declaringClassOf(TypeVariable typeVariable) { + GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration(); + return genericDeclaration instanceof Class + ? (Class) genericDeclaration + : null; + } + + private static void checkNotPrimitive(Type type) { + checkArgument(!(type instanceof Class) || !((Class) type).isPrimitive()); + } + + private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable { + private final Type ownerType; + private final Type rawType; + private final Type[] typeArguments; + + public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) { + // require an owner type if the raw type needs it + if (rawType instanceof Class) { + Class rawTypeAsClass = (Class) rawType; + checkArgument(ownerType != null || rawTypeAsClass.getEnclosingClass() == null); + checkArgument(ownerType == null || rawTypeAsClass.getEnclosingClass() != null); + } + + this.ownerType = ownerType == null ? null : canonicalize(ownerType); + this.rawType = canonicalize(rawType); + this.typeArguments = typeArguments.clone(); + for (int t = 0; t < this.typeArguments.length; t++) { + checkNotNull(this.typeArguments[t]); + checkNotPrimitive(this.typeArguments[t]); + this.typeArguments[t] = canonicalize(this.typeArguments[t]); + } + } + + public Type[] getActualTypeArguments() { + return typeArguments.clone(); + } + + public Type getRawType() { + return rawType; + } + + public Type getOwnerType() { + return ownerType; + } + + @Override public boolean equals(Object other) { + return other instanceof ParameterizedType + && $Gson$Types.equals(this, (ParameterizedType) other); + } + + @Override public int hashCode() { + return Arrays.hashCode(typeArguments) + ^ rawType.hashCode() + ^ hashCodeOrZero(ownerType); + } + + @Override public String toString() { + StringBuilder stringBuilder = new StringBuilder(30 * (typeArguments.length + 1)); + stringBuilder.append(typeToString(rawType)); + + if (typeArguments.length == 0) { + return stringBuilder.toString(); + } + + stringBuilder.append("<").append(typeToString(typeArguments[0])); + for (int i = 1; i < typeArguments.length; i++) { + stringBuilder.append(", ").append(typeToString(typeArguments[i])); + } + return stringBuilder.append(">").toString(); + } + + private static final long serialVersionUID = 0; + } + + private static final class GenericArrayTypeImpl implements GenericArrayType, Serializable { + private final Type componentType; + + public GenericArrayTypeImpl(Type componentType) { + this.componentType = canonicalize(componentType); + } + + public Type getGenericComponentType() { + return componentType; + } + + @Override public boolean equals(Object o) { + return o instanceof GenericArrayType + && $Gson$Types.equals(this, (GenericArrayType) o); + } + + @Override public int hashCode() { + return componentType.hashCode(); + } + + @Override public String toString() { + return typeToString(componentType) + "[]"; + } + + private static final long serialVersionUID = 0; + } + + /** + * The WildcardType interface supports multiple upper bounds and multiple + * lower bounds. We only support what the Java 6 language needs - at most one + * bound. If a lower bound is set, the upper bound must be Object.class. + */ + private static final class WildcardTypeImpl implements WildcardType, Serializable { + private final Type upperBound; + private final Type lowerBound; + + public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) { + checkArgument(lowerBounds.length <= 1); + checkArgument(upperBounds.length == 1); + + if (lowerBounds.length == 1) { + checkNotNull(lowerBounds[0]); + checkNotPrimitive(lowerBounds[0]); + checkArgument(upperBounds[0] == Object.class); + this.lowerBound = canonicalize(lowerBounds[0]); + this.upperBound = Object.class; + + } else { + checkNotNull(upperBounds[0]); + checkNotPrimitive(upperBounds[0]); + this.lowerBound = null; + this.upperBound = canonicalize(upperBounds[0]); + } + } + + public Type[] getUpperBounds() { + return new Type[] { upperBound }; + } + + public Type[] getLowerBounds() { + return lowerBound != null ? new Type[] { lowerBound } : EMPTY_TYPE_ARRAY; + } + + @Override public boolean equals(Object other) { + return other instanceof WildcardType + && $Gson$Types.equals(this, (WildcardType) other); + } + + @Override public int hashCode() { + // this equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()); + return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) + ^ (31 + upperBound.hashCode()); + } + + @Override public String toString() { + if (lowerBound != null) { + return "? super " + typeToString(lowerBound); + } else if (upperBound == Object.class) { + return "?"; + } else { + return "? extends " + typeToString(upperBound); + } + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/ConstructorConstructor.java b/src/com/massivecraft/core/lib/gson2/internal/ConstructorConstructor.java new file mode 100755 index 00000000..ca3b7c22 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/ConstructorConstructor.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal; + +import com.massivecraft.core.lib.gson2.InstanceCreator; +import com.massivecraft.core.lib.gson2.reflect.TypeToken; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * Returns a function that can construct an instance of a requested type. + */ +public final class ConstructorConstructor { + private final ParameterizedTypeHandlerMap> instanceCreators; + + public ConstructorConstructor(ParameterizedTypeHandlerMap> instanceCreators) { + this.instanceCreators = instanceCreators; + } + + public ConstructorConstructor() { + this(new ParameterizedTypeHandlerMap>()); + } + + public ObjectConstructor getConstructor(TypeToken typeToken) { + final Type type = typeToken.getType(); + final Class rawType = typeToken.getRawType(); + + // first try an instance creator + + @SuppressWarnings("unchecked") // types must agree + final InstanceCreator creator + = (InstanceCreator) instanceCreators.getHandlerFor(type, false); + if (creator != null) { + return new ObjectConstructor() { + public T construct() { + return creator.createInstance(type); + } + }; + } + + ObjectConstructor defaultConstructor = newDefaultConstructor(rawType); + if (defaultConstructor != null) { + return defaultConstructor; + } + + ObjectConstructor defaultImplementation = newDefaultImplementationConstructor(rawType); + if (defaultImplementation != null) { + return defaultImplementation; + } + + // finally try unsafe + return newUnsafeAllocator(type, rawType); + } + + private ObjectConstructor newDefaultConstructor(Class rawType) { + try { + final Constructor constructor = rawType.getDeclaredConstructor(); + if (!constructor.isAccessible()) { + constructor.setAccessible(true); + } + return new ObjectConstructor() { + @SuppressWarnings("unchecked") // T is the same raw type as is requested + public T construct() { + try { + Object[] args = null; + return (T) constructor.newInstance(args); + } catch (InstantiationException e) { + // TODO: JsonParseException ? + throw new RuntimeException("Failed to invoke " + constructor + " with no args", e); + } catch (InvocationTargetException e) { + // TODO: don't wrap if cause is unchecked! + // TODO: JsonParseException ? + throw new RuntimeException("Failed to invoke " + constructor + " with no args", + e.getTargetException()); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + }; + } catch (NoSuchMethodException e) { + return null; + } + } + + /** + * Constructors for common interface types like Map and List and their + * subytpes. + */ + @SuppressWarnings("unchecked") // use runtime checks to guarantee that 'T' is what it is + private ObjectConstructor newDefaultImplementationConstructor(Class rawType) { + if (Collection.class.isAssignableFrom(rawType)) { + if (SortedSet.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + public T construct() { + return (T) new TreeSet(); + } + }; + } else if (Set.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + public T construct() { + return (T) new LinkedHashSet(); + } + }; + } else if (Queue.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + public T construct() { + return (T) new LinkedList(); + } + }; + } else { + return new ObjectConstructor() { + public T construct() { + return (T) new ArrayList(); + } + }; + } + } + + if (Map.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + public T construct() { + return (T) new LinkedHashMap(); + } + }; + // TODO: SortedMap ? + } + + return null; + } + + private ObjectConstructor newUnsafeAllocator( + final Type type, final Class rawType) { + return new ObjectConstructor() { + private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create(); + @SuppressWarnings("unchecked") + public T construct() { + try { + Object newInstance = unsafeAllocator.newInstance(rawType); + return (T) newInstance; + } catch (Exception e) { + throw new RuntimeException(("Unable to invoke no-args constructor for " + type + ". " + + "Register an InstanceCreator with Gson for this type may fix this problem."), e); + } + } + }; + } + + @Override public String toString() { + return instanceCreators.toString(); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/LazilyParsedNumber.java b/src/com/massivecraft/core/lib/gson2/internal/LazilyParsedNumber.java new file mode 100755 index 00000000..0674ab16 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/LazilyParsedNumber.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal; + +import java.math.BigInteger; + +/** + * This class holds a number value that is lazily converted to a specific number type + * + * @author Inderjeet Singh + */ +@SuppressWarnings("serial") +public final class LazilyParsedNumber extends Number { + private final String value; + + public LazilyParsedNumber(String value) { + this.value = value; + } + + @Override + public int intValue() { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + try { + return (int) Long.parseLong(value); + } catch (NumberFormatException nfe) { + return new BigInteger(value).intValue(); + } + } + } + + @Override + public long longValue() { + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return new BigInteger(value).longValue(); + } + } + + @Override + public float floatValue() { + return Float.parseFloat(value); + } + + @Override + public double doubleValue() { + return Double.parseDouble(value); + } + + @Override + public String toString() { + return value; + } +} \ No newline at end of file diff --git a/src/com/massivecraft/core/lib/gson2/internal/ObjectConstructor.java b/src/com/massivecraft/core/lib/gson2/internal/ObjectConstructor.java new file mode 100755 index 00000000..c506b1a6 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/ObjectConstructor.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2.internal; + +/** + * Defines a generic object construction factory. The purpose of this class + * is to construct a default instance of a class that can be used for object + * navigation while deserialization from its JSON representation. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public interface ObjectConstructor { + + /** + * Returns a new instance. + */ + public T construct(); +} \ No newline at end of file diff --git a/src/com/massivecraft/core/lib/gson2/internal/Pair.java b/src/com/massivecraft/core/lib/gson2/internal/Pair.java new file mode 100755 index 00000000..3a7c1ec6 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/Pair.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2009 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.massivecraft.core.lib.gson2.internal; + +/** + * A simple object that holds onto a pair of object references, first and second. + * + * @author Inderjeet Singh + * @author Joel Leitch + * + * @param + * @param + */ +public final class Pair { + public final FIRST first; + public final SECOND second; + + public Pair(FIRST first, SECOND second) { + this.first = first; + this.second = second; + } + + @Override + public int hashCode() { + return 17 * ((first != null) ? first.hashCode() : 0) + + 17 * ((second != null) ? second.hashCode() : 0); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Pair)) { + return false; + } + + Pair that = (Pair) o; + return equal(this.first, that.first) && equal(this.second, that.second); + } + + private static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + @Override + public String toString() { + return String.format("{%s,%s}", first, second); + } +} \ No newline at end of file diff --git a/src/com/massivecraft/core/lib/gson2/internal/ParameterizedTypeHandlerMap.java b/src/com/massivecraft/core/lib/gson2/internal/ParameterizedTypeHandlerMap.java new file mode 100755 index 00000000..5945f97f --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/ParameterizedTypeHandlerMap.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2.internal; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A map that provides ability to associate handlers for a specific type or all + * of its sub-types + * + * @author Inderjeet Singh + * @author Joel Leitch + * + * @param The handler that will be looked up by type + */ +public final class ParameterizedTypeHandlerMap { + + private static final Logger logger = + Logger.getLogger(ParameterizedTypeHandlerMap.class.getName()); + /** + * Map that is meant for storing default type adapters + */ + private final Map systemMap = new HashMap(); + private final Map userMap = new HashMap(); + /** + * List of default type hierarchy adapters + */ + private final List, T>> systemTypeHierarchyList = new ArrayList, T>>(); + private final List, T>> userTypeHierarchyList = new ArrayList, T>>(); + private boolean modifiable = true; + + public synchronized void registerForTypeHierarchy(Class typeOfT, T value, boolean isSystem) { + Pair, T> pair = new Pair, T>(typeOfT, value); + registerForTypeHierarchy(pair, isSystem); + } + + public synchronized void registerForTypeHierarchy(Pair, T> pair, boolean isSystem) { + if (!modifiable) { + throw new IllegalStateException("Attempted to modify an unmodifiable map."); + } + List, T>> typeHierarchyList = isSystem ? systemTypeHierarchyList : userTypeHierarchyList; + int index = getIndexOfSpecificHandlerForTypeHierarchy(pair.first, typeHierarchyList); + if (index >= 0) { + logger.log(Level.WARNING, "Overriding the existing type handler for {0}", pair.first); + typeHierarchyList.remove(index); + } + index = getIndexOfAnOverriddenHandler(pair.first, typeHierarchyList); + if (index >= 0) { + throw new IllegalArgumentException("The specified type handler for type " + pair.first + + " hides the previously registered type hierarchy handler for " + + typeHierarchyList.get(index).first + ". Gson does not allow this."); + } + // We want stack behavior for adding to this list. A type adapter added subsequently should + // override a previously registered one. + typeHierarchyList.add(0, pair); + } + + private static int getIndexOfAnOverriddenHandler(Class type, List, T>> typeHierarchyList) { + for (int i = typeHierarchyList.size()-1; i >= 0; --i) { + Pair, T> entry = typeHierarchyList.get(i); + if (type.isAssignableFrom(entry.first)) { + return i; + } + } + return -1; + } + + public synchronized void register(Type typeOfT, T value, boolean isSystem) { + if (!modifiable) { + throw new IllegalStateException("Attempted to modify an unmodifiable map."); + } + if (hasSpecificHandlerFor(typeOfT)) { + logger.log(Level.WARNING, "Overriding the existing type handler for {0}", typeOfT); + } + Map map = isSystem ? systemMap : userMap; + map.put(typeOfT, value); + } + + public synchronized void registerIfAbsent(ParameterizedTypeHandlerMap other) { + if (!modifiable) { + throw new IllegalStateException("Attempted to modify an unmodifiable map."); + } + for (Map.Entry entry : other.userMap.entrySet()) { + if (!userMap.containsKey(entry.getKey())) { + register(entry.getKey(), entry.getValue(), false); + } + } + for (Map.Entry entry : other.systemMap.entrySet()) { + if (!systemMap.containsKey(entry.getKey())) { + register(entry.getKey(), entry.getValue(), true); + } + } + // Quite important to traverse the typeHierarchyList from stack bottom first since + // we want to register the handlers in the same order to preserve priority order + for (int i = other.userTypeHierarchyList.size()-1; i >= 0; --i) { + Pair, T> entry = other.userTypeHierarchyList.get(i); + int index = getIndexOfSpecificHandlerForTypeHierarchy(entry.first, userTypeHierarchyList); + if (index < 0) { + registerForTypeHierarchy(entry, false); + } + } + for (int i = other.systemTypeHierarchyList.size()-1; i >= 0; --i) { + Pair, T> entry = other.systemTypeHierarchyList.get(i); + int index = getIndexOfSpecificHandlerForTypeHierarchy(entry.first, systemTypeHierarchyList); + if (index < 0) { + registerForTypeHierarchy(entry, true); + } + } + } + + public synchronized ParameterizedTypeHandlerMap makeUnmodifiable() { + modifiable = false; + return this; + } + + public synchronized T getHandlerFor(Type type, boolean systemOnly) { + T handler; + if (!systemOnly) { + handler = userMap.get(type); + if (handler != null) { + return handler; + } + } + handler = systemMap.get(type); + if (handler != null) { + return handler; + } + Class rawClass = $Gson$Types.getRawType(type); + if (rawClass != type) { + handler = getHandlerFor(rawClass, systemOnly); + if (handler != null) { + return handler; + } + } + // check if something registered for type hierarchy + handler = getHandlerForTypeHierarchy(rawClass, systemOnly); + return handler; + } + + private T getHandlerForTypeHierarchy(Class type, boolean systemOnly) { + if (!systemOnly) { + for (Pair, T> entry : userTypeHierarchyList) { + if (entry.first.isAssignableFrom(type)) { + return entry.second; + } + } + } + for (Pair, T> entry : systemTypeHierarchyList) { + if (entry.first.isAssignableFrom(type)) { + return entry.second; + } + } + return null; + } + + public synchronized boolean hasSpecificHandlerFor(Type type) { + return userMap.containsKey(type) || systemMap.containsKey(type); + } + + private static int getIndexOfSpecificHandlerForTypeHierarchy( + Class type, List, T>> typeHierarchyList) { + for (int i = typeHierarchyList.size()-1; i >= 0; --i) { + if (type.equals(typeHierarchyList.get(i).first)) { + return i; + } + } + return -1; + } + + public synchronized ParameterizedTypeHandlerMap copyOf() { + ParameterizedTypeHandlerMap copy = new ParameterizedTypeHandlerMap(); + // Instead of individually registering entries in the map, make an efficient copy + // of the list and map + + // TODO (inder): Performance optimization. We can probably just share the + // systemMap and systemTypeHierarchyList instead of making copies + copy.systemMap.putAll(systemMap); + copy.userMap.putAll(userMap); + copy.systemTypeHierarchyList.addAll(systemTypeHierarchyList); + copy.userTypeHierarchyList.addAll(userTypeHierarchyList); + return copy; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("{userTypeHierarchyList:{"); + appendList(sb, userTypeHierarchyList); + sb.append("},systemTypeHierarchyList:{"); + appendList(sb, systemTypeHierarchyList); + sb.append("},userMap:{"); + appendMap(sb, userMap); + sb.append("},systemMap:{"); + appendMap(sb, systemMap); + sb.append("}"); + return sb.toString(); + } + + private void appendList(StringBuilder sb, List,T>> list) { + boolean first = true; + for (Pair, T> entry : list) { + if (first) { + first = false; + } else { + sb.append(','); + } + sb.append(typeToString(entry.first)).append(':'); + sb.append(entry.second); + } + } + + private void appendMap(StringBuilder sb, Map map) { + boolean first = true; + for (Map.Entry entry : map.entrySet()) { + if (first) { + first = false; + } else { + sb.append(','); + } + sb.append(typeToString(entry.getKey())).append(':'); + sb.append(entry.getValue()); + } + } + + private String typeToString(Type type) { + return $Gson$Types.getRawType(type).getSimpleName(); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/Primitives.java b/src/com/massivecraft/core/lib/gson2/internal/Primitives.java new file mode 100755 index 00000000..4bab26fe --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/Primitives.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2.internal; + + +import com.massivecraft.core.lib.gson2.internal.$Gson$Preconditions; + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Contains static utility methods pertaining to primitive types and their + * corresponding wrapper types. + * + * @author Kevin Bourrillion + */ +public final class Primitives { + private Primitives() {} + + /** A map from primitive types to their corresponding wrapper types. */ + private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPE; + + /** A map from wrapper types to their corresponding primitive types. */ + private static final Map, Class> WRAPPER_TO_PRIMITIVE_TYPE; + + // Sad that we can't use a BiMap. :( + + static { + Map, Class> primToWrap = new HashMap, Class>(16); + Map, Class> wrapToPrim = new HashMap, Class>(16); + + add(primToWrap, wrapToPrim, boolean.class, Boolean.class); + add(primToWrap, wrapToPrim, byte.class, Byte.class); + add(primToWrap, wrapToPrim, char.class, Character.class); + add(primToWrap, wrapToPrim, double.class, Double.class); + add(primToWrap, wrapToPrim, float.class, Float.class); + add(primToWrap, wrapToPrim, int.class, Integer.class); + add(primToWrap, wrapToPrim, long.class, Long.class); + add(primToWrap, wrapToPrim, short.class, Short.class); + add(primToWrap, wrapToPrim, void.class, Void.class); + + PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap); + WRAPPER_TO_PRIMITIVE_TYPE = Collections.unmodifiableMap(wrapToPrim); + } + + private static void add(Map, Class> forward, + Map, Class> backward, Class key, Class value) { + forward.put(key, value); + backward.put(value, key); + } + + /** + * Returns true if this type is a primitive. + */ + public static boolean isPrimitive(Type type) { + return PRIMITIVE_TO_WRAPPER_TYPE.containsKey(type); + } + + /** + * Returns {@code true} if {@code type} is one of the nine + * primitive-wrapper types, such as {@link Integer}. + * + * @see Class#isPrimitive + */ + public static boolean isWrapperType(Type type) { + return WRAPPER_TO_PRIMITIVE_TYPE.containsKey( + $Gson$Preconditions.checkNotNull(type)); + } + + /** + * Returns the corresponding wrapper type of {@code type} if it is a primitive + * type; otherwise returns {@code type} itself. Idempotent. + *
+   *     wrap(int.class) == Integer.class
+   *     wrap(Integer.class) == Integer.class
+   *     wrap(String.class) == String.class
+   * 
+ */ + public static Class wrap(Class type) { + // cast is safe: long.class and Long.class are both of type Class + @SuppressWarnings("unchecked") + Class wrapped = (Class) PRIMITIVE_TO_WRAPPER_TYPE.get( + $Gson$Preconditions.checkNotNull(type)); + return (wrapped == null) ? type : wrapped; + } + + /** + * Returns the corresponding primitive type of {@code type} if it is a + * wrapper type; otherwise returns {@code type} itself. Idempotent. + *
+   *     unwrap(Integer.class) == int.class
+   *     unwrap(int.class) == int.class
+   *     unwrap(String.class) == String.class
+   * 
+ */ + public static Class unwrap(Class type) { + // cast is safe: long.class and Long.class are both of type Class + @SuppressWarnings("unchecked") + Class unwrapped = (Class) WRAPPER_TO_PRIMITIVE_TYPE.get( + $Gson$Preconditions.checkNotNull(type)); + return (unwrapped == null) ? type : unwrapped; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/Streams.java b/src/com/massivecraft/core/lib/gson2/internal/Streams.java new file mode 100755 index 00000000..04b659f6 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/Streams.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 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.massivecraft.core.lib.gson2.internal; + +import com.massivecraft.core.lib.gson2.JsonElement; +import com.massivecraft.core.lib.gson2.JsonIOException; +import com.massivecraft.core.lib.gson2.JsonNull; +import com.massivecraft.core.lib.gson2.JsonParseException; +import com.massivecraft.core.lib.gson2.JsonSyntaxException; +import com.massivecraft.core.lib.gson2.internal.bind.TypeAdapters; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; +import com.massivecraft.core.lib.gson2.stream.MalformedJsonException; + +import java.io.EOFException; +import java.io.IOException; +import java.io.Writer; + +/** + * Reads and writes GSON parse trees over streams. + */ +public final class Streams { + /** + * Takes a reader in any state and returns the next value as a JsonElement. + */ + public static JsonElement parse(JsonReader reader) throws JsonParseException { + boolean isEmpty = true; + try { + reader.peek(); + isEmpty = false; + return TypeAdapters.JSON_ELEMENT.read(reader); + } catch (EOFException e) { + /* + * For compatibility with JSON 1.5 and earlier, we return a JsonNull for + * empty documents instead of throwing. + */ + if (isEmpty) { + return JsonNull.INSTANCE; + } + throw new JsonIOException(e); + } catch (MalformedJsonException e) { + throw new JsonSyntaxException(e); + } catch (IOException e) { + throw new JsonIOException(e); + } catch (NumberFormatException e) { + throw new JsonSyntaxException(e); + } + } + + /** + * Writes the JSON element to the writer, recursively. + */ + public static void write(JsonElement element, JsonWriter writer) throws IOException { + TypeAdapters.JSON_ELEMENT.write(writer, element); + } + + public static Writer writerForAppendable(Appendable appendable) { + return appendable instanceof Writer ? (Writer) appendable : new AppendableWriter(appendable); + } + + /** + * Adapts an {@link Appendable} so it can be passed anywhere a {@link Writer} + * is used. + */ + private static class AppendableWriter extends Writer { + private final Appendable appendable; + private final CurrentWrite currentWrite = new CurrentWrite(); + + private AppendableWriter(Appendable appendable) { + this.appendable = appendable; + } + + @Override public void write(char[] chars, int offset, int length) throws IOException { + currentWrite.chars = chars; + appendable.append(currentWrite, offset, offset + length); + } + + @Override public void write(int i) throws IOException { + appendable.append((char) i); + } + + @Override public void flush() {} + @Override public void close() {} + + /** + * A mutable char sequence pointing at a single char[]. + */ + static class CurrentWrite implements CharSequence { + char[] chars; + public int length() { + return chars.length; + } + public char charAt(int i) { + return chars[i]; + } + public CharSequence subSequence(int start, int end) { + return new String(chars, start, end - start); + } + } + } + +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/UnsafeAllocator.java b/src/com/massivecraft/core/lib/gson2/internal/UnsafeAllocator.java new file mode 100755 index 00000000..d448219f --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/UnsafeAllocator.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal; + +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Do sneaky things to allocate objects without invoking their constructors. + * + * @author Joel Leitch + * @author Jesse Wilson + */ +public abstract class UnsafeAllocator { + public abstract T newInstance(Class c) throws Exception; + + public static UnsafeAllocator create() { + // try JVM + // public class Unsafe { + // public Object allocateInstance(Class type); + // } + try { + Class unsafeClass = Class.forName("sun.misc.Unsafe"); + Field f = unsafeClass.getDeclaredField("theUnsafe"); + f.setAccessible(true); + final Object unsafe = f.get(null); + final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class); + return new UnsafeAllocator() { + @Override + @SuppressWarnings("unchecked") + public T newInstance(Class c) throws Exception { + return (T) allocateInstance.invoke(unsafe, c); + } + }; + } catch (Exception ignored) { + } + + // try dalvikvm, pre-gingerbread + // public class ObjectInputStream { + // private static native Object newInstance( + // Class instantiationClass, Class constructorClass); + // } + try { + final Method newInstance = ObjectInputStream.class + .getDeclaredMethod("newInstance", Class.class, Class.class); + newInstance.setAccessible(true); + return new UnsafeAllocator() { + @Override + @SuppressWarnings("unchecked") + public T newInstance(Class c) throws Exception { + return (T) newInstance.invoke(null, c, Object.class); + } + }; + } catch (Exception ignored) { + } + + // try dalvikvm, post-gingerbread + // public class ObjectStreamClass { + // private static native int getConstructorId(Class c); + // private static native Object newInstance(Class instantiationClass, int methodId); + // } + try { + Method getConstructorId = ObjectStreamClass.class + .getDeclaredMethod("getConstructorId", Class.class); + getConstructorId.setAccessible(true); + final int constructorId = (Integer) getConstructorId.invoke(null, Object.class); + final Method newInstance = ObjectStreamClass.class + .getDeclaredMethod("newInstance", Class.class, int.class); + newInstance.setAccessible(true); + return new UnsafeAllocator() { + @Override + @SuppressWarnings("unchecked") + public T newInstance(Class c) throws Exception { + return (T) newInstance.invoke(null, c, constructorId); + } + }; + } catch (Exception ignored) { + } + + // give up + return new UnsafeAllocator() { + @Override + public T newInstance(Class c) { + throw new UnsupportedOperationException("Cannot allocate " + c); + } + }; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/ArrayTypeAdapter.java b/src/com/massivecraft/core/lib/gson2/internal/bind/ArrayTypeAdapter.java new file mode 100755 index 00000000..d86bf813 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/ArrayTypeAdapter.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import com.massivecraft.core.lib.gson2.internal.$Gson$Types; +import com.massivecraft.core.lib.gson2.reflect.TypeToken; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +/** + * Adapt an array of objects. + */ +public final class ArrayTypeAdapter extends TypeAdapter { + public static final Factory FACTORY = new Factory() { + @SuppressWarnings({"unchecked", "rawtypes"}) + public TypeAdapter create(MiniGson context, TypeToken typeToken) { + Type type = typeToken.getType(); + if (!(type instanceof GenericArrayType || type instanceof Class && ((Class) type).isArray())) { + return null; + } + + Type componentType = $Gson$Types.getArrayComponentType(type); + TypeAdapter componentTypeAdapter = context.getAdapter(TypeToken.get(componentType)); + // create() doesn't define a type parameter + TypeAdapter result = new ArrayTypeAdapter( + context, componentTypeAdapter, $Gson$Types.getRawType(componentType)); + return result; + } + }; + + private final Class componentType; + private final TypeAdapter componentTypeAdapter; + + public ArrayTypeAdapter(MiniGson context, TypeAdapter componentTypeAdapter, Class componentType) { + this.componentTypeAdapter = + new TypeAdapterRuntimeTypeWrapper(context, componentTypeAdapter, componentType); + this.componentType = componentType; + } + + public Object read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + + List list = new ArrayList(); + reader.beginArray(); + while (reader.hasNext()) { + E instance = componentTypeAdapter.read(reader); + list.add(instance); + } + reader.endArray(); + Object array = Array.newInstance(componentType, list.size()); + for (int i = 0; i < list.size(); i++) { + Array.set(array, i, list.get(i)); + } + return array; + } + + @SuppressWarnings("unchecked") + @Override public void write(JsonWriter writer, Object array) throws IOException { + if (array == null) { + writer.nullValue(); // TODO: better policy here? + return; + } + + writer.beginArray(); + for (int i = 0, length = Array.getLength(array); i < length; i++) { + final E value = (E) Array.get(array, i); + componentTypeAdapter.write(writer, value); + } + writer.endArray(); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/BigDecimalTypeAdapter.java b/src/com/massivecraft/core/lib/gson2/internal/bind/BigDecimalTypeAdapter.java new file mode 100755 index 00000000..61ac6f60 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/BigDecimalTypeAdapter.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.JsonSyntaxException; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.math.BigDecimal; + +/** + * Adapts a BigDecimal type to and from its JSON representation. + * + * @author Joel Leitch + */ +public final class BigDecimalTypeAdapter extends TypeAdapter { + + @Override + public BigDecimal read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + try { + return new BigDecimal(reader.nextString()); + } catch (NumberFormatException e) { + throw new JsonSyntaxException(e); + } + } + + @Override + public void write(JsonWriter writer, BigDecimal value) throws IOException { + writer.value(value); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/BigIntegerTypeAdapter.java b/src/com/massivecraft/core/lib/gson2/internal/bind/BigIntegerTypeAdapter.java new file mode 100755 index 00000000..386322e8 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/BigIntegerTypeAdapter.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.JsonSyntaxException; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.math.BigInteger; + +/** + * Adapts a BigInteger type to and from its JSON representation. + * + * @author Joel Leitch + */ +public final class BigIntegerTypeAdapter extends TypeAdapter { + + @Override + public BigInteger read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + try { + return new BigInteger(reader.nextString()); + } catch (NumberFormatException e) { + throw new JsonSyntaxException(e); + } + } + + @Override + public void write(JsonWriter writer, BigInteger value) throws IOException { + writer.value(value); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/CollectionTypeAdapterFactory.java b/src/com/massivecraft/core/lib/gson2/internal/bind/CollectionTypeAdapterFactory.java new file mode 100755 index 00000000..f3b147a3 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/CollectionTypeAdapterFactory.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.internal.$Gson$Types; +import com.massivecraft.core.lib.gson2.internal.ConstructorConstructor; +import com.massivecraft.core.lib.gson2.internal.ObjectConstructor; +import com.massivecraft.core.lib.gson2.reflect.TypeToken; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Collection; + +/** + * Adapt a homogeneous collection of objects. + */ +public final class CollectionTypeAdapterFactory implements TypeAdapter.Factory { + private final ConstructorConstructor constructorConstructor; + + public CollectionTypeAdapterFactory(ConstructorConstructor constructorConstructor) { + this.constructorConstructor = constructorConstructor; + } + + public TypeAdapter create(MiniGson context, TypeToken typeToken) { + Type type = typeToken.getType(); + + Class rawType = typeToken.getRawType(); + if (!Collection.class.isAssignableFrom(rawType)) { + return null; + } + + Type elementType = $Gson$Types.getCollectionElementType(type, rawType); + TypeAdapter elementTypeAdapter = context.getAdapter(TypeToken.get(elementType)); + ObjectConstructor constructor = constructorConstructor.getConstructor(typeToken); + + @SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter + TypeAdapter result = new Adapter(context, elementType, elementTypeAdapter, constructor); + return result; + } + + private final class Adapter extends TypeAdapter> { + private final TypeAdapter elementTypeAdapter; + private final ObjectConstructor> constructor; + + public Adapter(MiniGson context, Type elementType, + TypeAdapter elementTypeAdapter, + ObjectConstructor> constructor) { + this.elementTypeAdapter = + new TypeAdapterRuntimeTypeWrapper(context, elementTypeAdapter, elementType); + this.constructor = constructor; + } + + public Collection read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + + Collection collection = constructor.construct(); + reader.beginArray(); + while (reader.hasNext()) { + E instance = elementTypeAdapter.read(reader); + collection.add(instance); + } + reader.endArray(); + return collection; + } + + public void write(JsonWriter writer, Collection collection) throws IOException { + if (collection == null) { + writer.nullValue(); // TODO: better policy here? + return; + } + + writer.beginArray(); + for (E element : collection) { + elementTypeAdapter.write(writer, element); + } + writer.endArray(); + } + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/DateTypeAdapter.java b/src/com/massivecraft/core/lib/gson2/internal/bind/DateTypeAdapter.java new file mode 100755 index 00000000..db18b715 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/DateTypeAdapter.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.JsonSyntaxException; +import com.massivecraft.core.lib.gson2.reflect.TypeToken; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Adapter for Date. Although this class appears stateless, it is not. + * DateFormat captures its time zone and locale when it is created, which gives + * this class state. DateFormat isn't thread safe either, so this class has + * to synchronize its read and write methods. + */ +public final class DateTypeAdapter extends TypeAdapter { + public static final Factory FACTORY = new Factory() { + @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal + public TypeAdapter create(MiniGson context, TypeToken typeToken) { + return typeToken.getRawType() == Date.class ? (TypeAdapter) new DateTypeAdapter() : null; + } + }; + + private final DateFormat enUsFormat + = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US); + private final DateFormat localFormat + = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT); + private final DateFormat iso8601Format = buildIso8601Format(); + + private static DateFormat buildIso8601Format() { + DateFormat iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC")); + return iso8601Format; + } + + @Override public Date read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + return deserializeToDate(reader.nextString()); + } + + private synchronized Date deserializeToDate(String json) { + try { + return localFormat.parse(json); + } catch (ParseException ignored) { + } + try { + return enUsFormat.parse(json); + } catch (ParseException ignored) { + } + try { + return iso8601Format.parse(json); + } catch (ParseException e) { + throw new JsonSyntaxException(json, e); + } + } + + @Override public synchronized void write(JsonWriter writer, Date value) throws IOException { + if (value == null) { + writer.nullValue(); + return; + } + String dateFormatAsString = enUsFormat.format(value); + writer.value(dateFormatAsString); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/ExcludedTypeAdapterFactory.java b/src/com/massivecraft/core/lib/gson2/internal/bind/ExcludedTypeAdapterFactory.java new file mode 100755 index 00000000..9122bd12 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/ExcludedTypeAdapterFactory.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.ExclusionStrategy; +import com.massivecraft.core.lib.gson2.reflect.TypeToken; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; + +/** + * This type adapter skips values using an exclusion strategy. It may delegate + * to another type adapter if only one direction is excluded. + */ +public final class ExcludedTypeAdapterFactory implements TypeAdapter.Factory { + private final ExclusionStrategy serializationExclusionStrategy; + private final ExclusionStrategy deserializationExclusionStrategy; + + public ExcludedTypeAdapterFactory(ExclusionStrategy serializationExclusionStrategy, + ExclusionStrategy deserializationExclusionStrategy) { + this.serializationExclusionStrategy = serializationExclusionStrategy; + this.deserializationExclusionStrategy = deserializationExclusionStrategy; + } + + public TypeAdapter create(final MiniGson context, final TypeToken type) { + Class rawType = type.getRawType(); + final boolean skipSerialize = serializationExclusionStrategy.shouldSkipClass(rawType); + final boolean skipDeserialize = deserializationExclusionStrategy.shouldSkipClass(rawType); + + if (!skipSerialize && !skipDeserialize) { + return null; + } + + return new TypeAdapter() { + /** + * The delegate is lazily created because it may not be needed, and + * creating it may fail. + */ + private TypeAdapter delegate; + + @Override public T read(JsonReader reader) throws IOException { + if (skipDeserialize) { + reader.skipValue(); + return null; + } + return delegate().read(reader); + } + + @Override public void write(JsonWriter writer, T value) throws IOException { + if (skipSerialize) { + writer.nullValue(); + return; + } + delegate().write(writer, value); + } + + private TypeAdapter delegate() { + TypeAdapter d = delegate; + return d != null + ? d + : (delegate = context.getNextAdapter(ExcludedTypeAdapterFactory.this, type)); + } + }; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/JsonElementReader.java b/src/com/massivecraft/core/lib/gson2/internal/bind/JsonElementReader.java new file mode 100755 index 00000000..f3f4f6b9 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/JsonElementReader.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.JsonArray; +import com.massivecraft.core.lib.gson2.JsonElement; +import com.massivecraft.core.lib.gson2.JsonNull; +import com.massivecraft.core.lib.gson2.JsonObject; +import com.massivecraft.core.lib.gson2.JsonPrimitive; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * This reader walks the elements of a JsonElement as if it was coming from a + * character stream. + * + * @author Jesse Wilson + */ +public final class JsonElementReader extends JsonReader { + private static final Reader UNREADABLE_READER = new Reader() { + @Override public int read(char[] buffer, int offset, int count) throws IOException { + throw new AssertionError(); + } + @Override public void close() throws IOException { + throw new AssertionError(); + } + }; + private static final Object SENTINEL_CLOSED = new Object(); + + private final List stack = new ArrayList(); + + public JsonElementReader(JsonElement element) { + super(UNREADABLE_READER); + stack.add(element); + } + + @Override public void beginArray() throws IOException { + expect(JsonToken.BEGIN_ARRAY); + JsonArray array = (JsonArray) peekStack(); + stack.add(array.iterator()); + } + + @Override public void endArray() throws IOException { + expect(JsonToken.END_ARRAY); + popStack(); // empty iterator + popStack(); // array + } + + @Override public void beginObject() throws IOException { + expect(JsonToken.BEGIN_OBJECT); + JsonObject object = (JsonObject) peekStack(); + stack.add(object.entrySet().iterator()); + } + + @Override public void endObject() throws IOException { + expect(JsonToken.END_OBJECT); + popStack(); // empty iterator + popStack(); // object + } + + @Override public boolean hasNext() throws IOException { + JsonToken token = peek(); + return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY; + } + + @Override public JsonToken peek() throws IOException { + if (stack.isEmpty()) { + return JsonToken.END_DOCUMENT; + } + + Object o = peekStack(); + if (o instanceof Iterator) { + boolean isObject = stack.get(stack.size() - 2) instanceof JsonObject; + Iterator iterator = (Iterator) o; + if (iterator.hasNext()) { + if (isObject) { + return JsonToken.NAME; + } else { + stack.add(iterator.next()); + return peek(); + } + } else { + return isObject ? JsonToken.END_OBJECT : JsonToken.END_ARRAY; + } + } else if (o instanceof JsonObject) { + return JsonToken.BEGIN_OBJECT; + } else if (o instanceof JsonArray) { + return JsonToken.BEGIN_ARRAY; + } else if (o instanceof JsonPrimitive) { + JsonPrimitive primitive = (JsonPrimitive) o; + if (primitive.isString()) { + return JsonToken.STRING; + } else if (primitive.isBoolean()) { + return JsonToken.BOOLEAN; + } else if (primitive.isNumber()) { + return JsonToken.NUMBER; + } else { + throw new AssertionError(); + } + } else if (o instanceof JsonNull) { + return JsonToken.NULL; + } else if (o == SENTINEL_CLOSED) { + throw new IllegalStateException("JsonReader is closed"); + } else { + throw new AssertionError(); + } + } + + private Object peekStack() { + return stack.get(stack.size() - 1); + } + + private Object popStack() { + return stack.remove(stack.size() - 1); + } + + private void expect(JsonToken expected) throws IOException { + if (peek() != expected) { + throw new IllegalStateException("Expected " + expected + " but was " + peek()); + } + } + + @Override public String nextName() throws IOException { + expect(JsonToken.NAME); + Iterator i = (Iterator) peekStack(); + Map.Entry entry = (Map.Entry) i.next(); + stack.add(entry.getValue()); + return (String) entry.getKey(); + } + + @Override public String nextString() throws IOException { + JsonToken token = peek(); + if (token != JsonToken.STRING && token != JsonToken.NUMBER) { + throw new IllegalStateException("Expected " + JsonToken.STRING + " but was " + token); + } + return ((JsonPrimitive) popStack()).getAsString(); + } + + @Override public boolean nextBoolean() throws IOException { + expect(JsonToken.BOOLEAN); + return ((JsonPrimitive) popStack()).getAsBoolean(); + } + + @Override public void nextNull() throws IOException { + expect(JsonToken.NULL); + popStack(); + } + + @Override public double nextDouble() throws IOException { + JsonToken token = peek(); + if (token != JsonToken.NUMBER && token != JsonToken.STRING) { + throw new IllegalStateException("Expected " + JsonToken.NUMBER + " but was " + token); + } + double result = ((JsonPrimitive) peekStack()).getAsDouble(); + if (!isLenient() && (Double.isNaN(result) || Double.isInfinite(result))) { + throw new NumberFormatException("JSON forbids NaN and infinities: " + result); + } + popStack(); + return result; + } + + @Override public long nextLong() throws IOException { + JsonToken token = peek(); + if (token != JsonToken.NUMBER && token != JsonToken.STRING) { + throw new IllegalStateException("Expected " + JsonToken.NUMBER + " but was " + token); + } + long result = ((JsonPrimitive) peekStack()).getAsLong(); + popStack(); + return result; + } + + @Override public int nextInt() throws IOException { + JsonToken token = peek(); + if (token != JsonToken.NUMBER && token != JsonToken.STRING) { + throw new IllegalStateException("Expected " + JsonToken.NUMBER + " but was " + token); + } + int result = ((JsonPrimitive) peekStack()).getAsInt(); + popStack(); + return result; + } + + @Override public void close() throws IOException { + stack.clear(); + stack.add(SENTINEL_CLOSED); + } + + @Override public void skipValue() throws IOException { + if (peek() == JsonToken.NAME) { + nextName(); + } else { + popStack(); + } + } + + @Override public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/JsonElementWriter.java b/src/com/massivecraft/core/lib/gson2/internal/bind/JsonElementWriter.java new file mode 100755 index 00000000..f0a91fa3 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/JsonElementWriter.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.JsonArray; +import com.massivecraft.core.lib.gson2.JsonElement; +import com.massivecraft.core.lib.gson2.JsonNull; +import com.massivecraft.core.lib.gson2.JsonObject; +import com.massivecraft.core.lib.gson2.JsonPrimitive; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; + +/** + * This writer creates a JsonElement. + */ +public final class JsonElementWriter extends JsonWriter { + private static final Writer UNWRITABLE_WRITER = new Writer() { + @Override public void write(char[] buffer, int offset, int counter) { + throw new AssertionError(); + } + @Override public void flush() throws IOException { + throw new AssertionError(); + } + @Override public void close() throws IOException { + throw new AssertionError(); + } + }; + /** Added to the top of the stack when this writer is closed to cause following ops to fail. */ + private static final JsonPrimitive SENTINEL_CLOSED = new JsonPrimitive("closed"); + + /** The JsonElements and JsonArrays under modification, outermost to innermost. */ + private final List stack = new ArrayList(); + + /** The name for the next JSON object value. If non-null, the top of the stack is a JsonObject. */ + private String pendingName; + + /** the JSON element constructed by this writer. */ + private JsonElement product = JsonNull.INSTANCE; // TODO: is this really what we want?; + + public JsonElementWriter() { + super(UNWRITABLE_WRITER); + } + + /** + * Returns the top level object produced by this writer. + */ + public JsonElement get() { + if (!stack.isEmpty()) { + throw new IllegalStateException("Expected one JSON element but was " + stack); + } + return product; + } + + private JsonElement peek() { + return stack.get(stack.size() - 1); + } + + private void put(JsonElement value) { + if (pendingName != null) { + if (!value.isJsonNull() || getSerializeNulls()) { + JsonObject object = (JsonObject) peek(); + object.add(pendingName, value); + } + pendingName = null; + } else if (stack.isEmpty()) { + product = value; + } else { + JsonElement element = peek(); + if (element instanceof JsonArray) { + ((JsonArray) element).add(value); + } else { + throw new IllegalStateException(); + } + } + } + + @Override public JsonWriter beginArray() throws IOException { + JsonArray array = new JsonArray(); + put(array); + stack.add(array); + return this; + } + + @Override public JsonWriter endArray() throws IOException { + if (stack.isEmpty() || pendingName != null) { + throw new IllegalStateException(); + } + JsonElement element = peek(); + if (element instanceof JsonArray) { + stack.remove(stack.size() - 1); + return this; + } + throw new IllegalStateException(); + } + + @Override public JsonWriter beginObject() throws IOException { + JsonObject object = new JsonObject(); + put(object); + stack.add(object); + return this; + } + + @Override public JsonWriter endObject() throws IOException { + if (stack.isEmpty() || pendingName != null) { + throw new IllegalStateException(); + } + JsonElement element = peek(); + if (element instanceof JsonObject) { + stack.remove(stack.size() - 1); + return this; + } + throw new IllegalStateException(); + } + + @Override public JsonWriter name(String name) throws IOException { + if (stack.isEmpty() || pendingName != null) { + throw new IllegalStateException(); + } + JsonElement element = peek(); + if (element instanceof JsonObject) { + pendingName = name; + return this; + } + throw new IllegalStateException(); + } + + @Override public JsonWriter value(String value) throws IOException { + if (value == null) { + return nullValue(); + } + put(new JsonPrimitive(value)); + return this; + } + + @Override public JsonWriter nullValue() throws IOException { + put(JsonNull.INSTANCE); + return this; + } + + @Override public JsonWriter value(boolean value) throws IOException { + put(new JsonPrimitive(value)); + return this; + } + + @Override public JsonWriter value(double value) throws IOException { + if (!isLenient() && (Double.isNaN(value) || Double.isInfinite(value))) { + throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value); + } + put(new JsonPrimitive(value)); + return this; + } + + @Override public JsonWriter value(long value) throws IOException { + put(new JsonPrimitive(value)); + return this; + } + + @Override public JsonWriter value(Number value) throws IOException { + if (value == null) { + return nullValue(); + } + + if (!isLenient()) { + double d = value.doubleValue(); + if (Double.isNaN(d) || Double.isInfinite(d)) { + throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value); + } + } + + put(new JsonPrimitive(value)); + return this; + } + + @Override public void flush() throws IOException { + } + + @Override public void close() throws IOException { + if (!stack.isEmpty()) { + throw new IOException("Incomplete document"); + } + stack.add(SENTINEL_CLOSED); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/MapTypeAdapterFactory.java b/src/com/massivecraft/core/lib/gson2/internal/bind/MapTypeAdapterFactory.java new file mode 100755 index 00000000..1ee2ad26 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/MapTypeAdapterFactory.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.JsonElement; +import com.massivecraft.core.lib.gson2.JsonPrimitive; +import com.massivecraft.core.lib.gson2.JsonSyntaxException; +import com.massivecraft.core.lib.gson2.internal.$Gson$Types; +import com.massivecraft.core.lib.gson2.internal.ConstructorConstructor; +import com.massivecraft.core.lib.gson2.internal.ObjectConstructor; +import com.massivecraft.core.lib.gson2.internal.Streams; +import com.massivecraft.core.lib.gson2.reflect.TypeToken; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Adapts maps to either JSON objects or JSON arrays. + * + *

Maps as JSON objects

+ * For primitive keys or when complex map key serialization is not enabled, this + * converts Java {@link Map Maps} to JSON Objects. This requires that map keys + * can be serialized as strings; this is insufficient for some key types. For + * example, consider a map whose keys are points on a grid. The default JSON + * form encodes reasonably:
   {@code
+ *   Map original = new LinkedHashMap();
+ *   original.put(new Point(5, 6), "a");
+ *   original.put(new Point(8, 8), "b");
+ *   System.out.println(gson.toJson(original, type));
+ * }
+ * The above code prints this JSON object:
   {@code
+ *   {
+ *     "(5,6)": "a",
+ *     "(8,8)": "b"
+ *   }
+ * }
+ * But GSON is unable to deserialize this value because the JSON string name is + * just the {@link Object#toString() toString()} of the map key. Attempting to + * convert the above JSON to an object fails with a parse exception: + *
com.google.gson.JsonParseException: Expecting object found: "(5,6)"
+ *   at com.google.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler
+ *   at com.google.gson.ObjectNavigator.navigateClassFields
+ *   ...
+ * + *

Maps as JSON arrays

+ * An alternative approach taken by this type adapter when it is required and + * complex map key serialization is enabled is to encode maps as arrays of map + * entries. Each map entry is a two element array containing a key and a value. + * This approach is more flexible because any type can be used as the map's key; + * not just strings. But it's also less portable because the receiver of such + * JSON must be aware of the map entry convention. + * + *

Register this adapter when you are creating your GSON instance. + *

   {@code
+ *   Gson gson = new GsonBuilder()
+ *     .registerTypeAdapter(Map.class, new MapAsArrayTypeAdapter())
+ *     .create();
+ * }
+ * This will change the structure of the JSON emitted by the code above. Now we + * get an array. In this case the arrays elements are map entries: + *
   {@code
+ *   [
+ *     [
+ *       {
+ *         "x": 5,
+ *         "y": 6
+ *       },
+ *       "a",
+ *     ],
+ *     [
+ *       {
+ *         "x": 8,
+ *         "y": 8
+ *       },
+ *       "b"
+ *     ]
+ *   ]
+ * }
+ * This format will serialize and deserialize just fine as long as this adapter + * is registered. + */ +public final class MapTypeAdapterFactory implements TypeAdapter.Factory { + private final ConstructorConstructor constructorConstructor; + private final boolean complexMapKeySerialization; + + public MapTypeAdapterFactory(ConstructorConstructor constructorConstructor, + boolean complexMapKeySerialization) { + this.constructorConstructor = constructorConstructor; + this.complexMapKeySerialization = complexMapKeySerialization; + } + + public TypeAdapter create(MiniGson context, TypeToken typeToken) { + Type type = typeToken.getType(); + + Class rawType = typeToken.getRawType(); + if (!Map.class.isAssignableFrom(rawType)) { + return null; + } + + Class rawTypeOfSrc = $Gson$Types.getRawType(type); + Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawTypeOfSrc); + TypeAdapter keyAdapter = getKeyAdapter(context, keyAndValueTypes[0]); + TypeAdapter valueAdapter = context.getAdapter(TypeToken.get(keyAndValueTypes[1])); + ObjectConstructor constructor = constructorConstructor.getConstructor(typeToken); + + @SuppressWarnings({"unchecked", "rawtypes"}) + // we don't define a type parameter for the key or value types + TypeAdapter result = new Adapter(context, keyAndValueTypes[0], keyAdapter, + keyAndValueTypes[1], valueAdapter, constructor); + return result; + } + + /** + * Returns a type adapter that writes the value as a string. + */ + private TypeAdapter getKeyAdapter(MiniGson context, Type keyType) { + return (keyType == boolean.class || keyType == Boolean.class) + ? TypeAdapters.BOOLEAN_AS_STRING + : context.getAdapter(TypeToken.get(keyType)); + } + + private final class Adapter extends TypeAdapter> { + private final TypeAdapter keyTypeAdapter; + private final TypeAdapter valueTypeAdapter; + private final ObjectConstructor> constructor; + + public Adapter(MiniGson context, Type keyType, TypeAdapter keyTypeAdapter, + Type valueType, TypeAdapter valueTypeAdapter, + ObjectConstructor> constructor) { + this.keyTypeAdapter = + new TypeAdapterRuntimeTypeWrapper(context, keyTypeAdapter, keyType); + this.valueTypeAdapter = + new TypeAdapterRuntimeTypeWrapper(context, valueTypeAdapter, valueType); + this.constructor = constructor; + } + + public Map read(JsonReader reader) throws IOException { + JsonToken peek = reader.peek(); + if (peek == JsonToken.NULL) { + reader.nextNull(); + return null; + } + + Map map = constructor.construct(); + + if (peek == JsonToken.BEGIN_ARRAY) { + reader.beginArray(); + while (reader.hasNext()) { + reader.beginArray(); // entry array + K key = keyTypeAdapter.read(reader); + V value = valueTypeAdapter.read(reader); + V replaced = map.put(key, value); + if (replaced != null) { + throw new JsonSyntaxException("duplicate key: " + key); + } + reader.endArray(); + } + reader.endArray(); + } else { + reader.beginObject(); + while (reader.hasNext()) { + String keyString = reader.nextName(); + K key = keyTypeAdapter.fromJsonElement(new JsonPrimitive(keyString)); + V value = valueTypeAdapter.read(reader); + V replaced = map.put(key, value); + if (replaced != null) { + throw new JsonSyntaxException("duplicate key: " + key); + } + } + reader.endObject(); + } + return map; + } + + public void write(JsonWriter writer, Map map) throws IOException { + if (map == null) { + writer.nullValue(); // TODO: better policy here? + return; + } + + if (!complexMapKeySerialization) { + writer.beginObject(); + for (Map.Entry entry : map.entrySet()) { + writer.name(String.valueOf(entry.getKey())); + valueTypeAdapter.write(writer, entry.getValue()); + } + writer.endObject(); + return; + } + + boolean hasComplexKeys = false; + List keys = new ArrayList(map.size()); + + List values = new ArrayList(map.size()); + for (Map.Entry entry : map.entrySet()) { + JsonElement keyElement = keyTypeAdapter.toJsonElement(entry.getKey()); + keys.add(keyElement); + values.add(entry.getValue()); + hasComplexKeys |= keyElement.isJsonArray() || keyElement.isJsonObject(); + } + + if (hasComplexKeys) { + writer.beginArray(); + for (int i = 0; i < keys.size(); i++) { + writer.beginArray(); // entry array + Streams.write(keys.get(i), writer); + valueTypeAdapter.write(writer, values.get(i)); + writer.endArray(); + } + writer.endArray(); + } else { + writer.beginObject(); + for (int i = 0; i < keys.size(); i++) { + JsonElement keyElement = keys.get(i); + writer.name(keyToString(keyElement)); + valueTypeAdapter.write(writer, values.get(i)); + } + writer.endObject(); + } + } + + private String keyToString(JsonElement keyElement) { + if (keyElement.isJsonPrimitive()) { + JsonPrimitive primitive = keyElement.getAsJsonPrimitive(); + if (primitive.isNumber()) { + return String.valueOf(primitive.getAsNumber()); + } else if (primitive.isBoolean()) { + return Boolean.toString(primitive.getAsBoolean()); + } else if (primitive.isString()) { + return primitive.getAsString(); + } else { + throw new AssertionError(); + } + } else if (keyElement.isJsonNull()) { + return "null"; + } else { + throw new AssertionError(); + } + } + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/MiniGson.java b/src/com/massivecraft/core/lib/gson2/internal/bind/MiniGson.java new file mode 100755 index 00000000..35443348 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/MiniGson.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.internal.ConstructorConstructor; +import com.massivecraft.core.lib.gson2.reflect.TypeToken; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A basic binding between JSON and Java objects. + */ +public final class MiniGson { + /** + * This thread local guards against reentrant calls to getAdapter(). In + * certain object graphs, creating an adapter for a type may recursively + * require an adapter for the same type! Without intervention, the recursive + * lookup would stack overflow. We cheat by returning a proxy type adapter. + * The proxy is wired up once the initial adapter has been created. + */ + private final ThreadLocal, FutureTypeAdapter>> calls + = new ThreadLocal, FutureTypeAdapter>>() { + @Override protected Map, FutureTypeAdapter> initialValue() { + return new HashMap, FutureTypeAdapter>(); + } + }; + + private final List factories; + + private MiniGson(Builder builder) { + ConstructorConstructor constructorConstructor = new ConstructorConstructor(); + List factories = new ArrayList(); + if (builder.addDefaultFactories) { + factories.add(TypeAdapters.BOOLEAN_FACTORY); + factories.add(TypeAdapters.INTEGER_FACTORY); + factories.add(TypeAdapters.DOUBLE_FACTORY); + factories.add(TypeAdapters.FLOAT_FACTORY); + factories.add(TypeAdapters.LONG_FACTORY); + factories.add(TypeAdapters.STRING_FACTORY); + } + factories.addAll(builder.factories); + if (builder.addDefaultFactories) { + factories.add(new CollectionTypeAdapterFactory(constructorConstructor)); + factories.add(new StringToValueMapTypeAdapterFactory(constructorConstructor)); + factories.add(ArrayTypeAdapter.FACTORY); + factories.add(ObjectTypeAdapter.FACTORY); + factories.add(new ReflectiveTypeAdapterFactory(constructorConstructor)); + } + this.factories = Collections.unmodifiableList(factories); + } + + /** + * Returns the type adapter for {@code} type. + * + * @throws IllegalArgumentException if this GSON cannot serialize and + * deserialize {@code type}. + */ + public TypeAdapter getAdapter(TypeToken type) { + // TODO: create a cache! + + Map, FutureTypeAdapter> threadCalls = calls.get(); + @SuppressWarnings("unchecked") // the key and value type parameters always agree + FutureTypeAdapter ongoingCall = (FutureTypeAdapter) threadCalls.get(type); + if (ongoingCall != null) { + return ongoingCall; + } + + FutureTypeAdapter call = new FutureTypeAdapter(); + threadCalls.put(type, call); + try { + for (TypeAdapter.Factory factory : factories) { + TypeAdapter candidate = factory.create(this, type); + if (candidate != null) { + call.setDelegate(candidate); + return candidate; + } + } + throw new IllegalArgumentException("This MiniGSON cannot handle " + type); + } finally { + threadCalls.remove(type); + } + } + + /** + * Returns a type adapter for {@code} type that isn't {@code skipPast}. This + * can be used for type adapters to compose other, simpler type adapters. + * + * @throws IllegalArgumentException if this GSON cannot serialize and + * deserialize {@code type}. + */ + public TypeAdapter getNextAdapter(TypeAdapter.Factory skipPast, TypeToken type) { + boolean skipPastFound = false; + + for (TypeAdapter.Factory factory : factories) { + if (!skipPastFound) { + if (factory == skipPast) { + skipPastFound = true; + } + continue; + } + + TypeAdapter candidate = factory.create(this, type); + if (candidate != null) { + return candidate; + } + } + + throw new IllegalArgumentException("This MiniGSON cannot serialize " + type); + } + + static class FutureTypeAdapter extends TypeAdapter { + private TypeAdapter delegate; + + public void setDelegate(TypeAdapter typeAdapter) { + if (delegate != null) { + throw new AssertionError(); + } + delegate = typeAdapter; + } + + @Override public T read(JsonReader reader) throws IOException { + if (delegate == null) { + throw new IllegalStateException(); + } + return delegate.read(reader); + } + + @Override public void write(JsonWriter writer, T value) throws IOException { + if (delegate == null) { + throw new IllegalStateException(); + } + delegate.write(writer, value); + } + } + + /** + * Returns the type adapter for {@code} type. + * + * @throws IllegalArgumentException if this GSON cannot serialize and + * deserialize {@code type}. + */ + public TypeAdapter getAdapter(Class type) { + return getAdapter(TypeToken.get(type)); + } + + /** + * Returns the type adapters of this context in order of precedence. + */ + public List getFactories() { + return factories; + } + + public static final class Builder { + private final List factories = new ArrayList(); + boolean addDefaultFactories = true; + + public Builder factory(TypeAdapter.Factory factory) { + factories.add(factory); + return this; + } + + public Builder withoutDefaultFactories() { + this.addDefaultFactories = false; + return this; + } + + public Builder typeAdapter(final Class type, final TypeAdapter typeAdapter) { + factories.add(TypeAdapters.newFactory(type, typeAdapter)); + return this; + } + + public Builder typeAdapter(TypeToken type, TypeAdapter typeAdapter) { + factories.add(TypeAdapters.newFactory(type, typeAdapter)); + return this; + } + + public Builder typeHierarchyAdapter(Class type, TypeAdapter typeAdapter) { + factories.add(TypeAdapters.newTypeHierarchyFactory(type, typeAdapter)); + return this; + } + + public MiniGson build() { + return new MiniGson(this); + } + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/ObjectTypeAdapter.java b/src/com/massivecraft/core/lib/gson2/internal/bind/ObjectTypeAdapter.java new file mode 100755 index 00000000..132cf83d --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/ObjectTypeAdapter.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.reflect.TypeToken; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Adapts types whose static type is only 'Object'. Uses getClass() on + * serialization and a primitive/Map/List on deserialization. + */ +public final class ObjectTypeAdapter extends TypeAdapter { + public static final Factory FACTORY = new Factory() { + @SuppressWarnings("unchecked") + public TypeAdapter create(MiniGson context, TypeToken type) { + if (type.getRawType() == Object.class) { + return (TypeAdapter) new ObjectTypeAdapter(context); + } + return null; + } + }; + + private final MiniGson miniGson; + + private ObjectTypeAdapter(MiniGson miniGson) { + this.miniGson = miniGson; + } + + @Override public Object read(JsonReader reader) throws IOException { + JsonToken token = reader.peek(); + switch (token) { + case BEGIN_ARRAY: + List list = new ArrayList(); + reader.beginArray(); + while (reader.hasNext()) { + list.add(read(reader)); + } + reader.endArray(); + return list; + + case BEGIN_OBJECT: + Map map = new LinkedHashMap(); + reader.beginObject(); + while (reader.hasNext()) { + map.put(reader.nextName(), read(reader)); + } + reader.endObject(); + return map; + + case STRING: + return reader.nextString(); + + case NUMBER: + return reader.nextDouble(); + + case BOOLEAN: + return reader.nextBoolean(); + + case NULL: + reader.nextNull(); + return null; + + } + throw new IllegalStateException(); + } + + @SuppressWarnings("unchecked") + @Override public void write(JsonWriter writer, Object value) throws IOException { + if (value == null) { + writer.nullValue(); // TODO: better policy here? + return; + } + + TypeAdapter typeAdapter = (TypeAdapter) miniGson.getAdapter(value.getClass()); + if (typeAdapter instanceof ObjectTypeAdapter) { + writer.beginObject(); + writer.endObject(); + return; + } + + typeAdapter.write(writer, value); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/Reflection.java b/src/com/massivecraft/core/lib/gson2/internal/bind/Reflection.java new file mode 100755 index 00000000..4f34a48b --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/Reflection.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +final class Reflection { + /** + * Finds a compatible runtime type if it is more specific + */ + public static Type getRuntimeTypeIfMoreSpecific(Type type, Object value) { + if (value != null + && (type == Object.class || type instanceof TypeVariable || type instanceof Class)) { + type = value.getClass(); + } + return type; + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/ReflectiveTypeAdapterFactory.java b/src/com/massivecraft/core/lib/gson2/internal/bind/ReflectiveTypeAdapterFactory.java new file mode 100755 index 00000000..98f66e32 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/ReflectiveTypeAdapterFactory.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.JsonSyntaxException; +import com.massivecraft.core.lib.gson2.internal.$Gson$Types; +import com.massivecraft.core.lib.gson2.internal.ConstructorConstructor; +import com.massivecraft.core.lib.gson2.internal.ObjectConstructor; +import com.massivecraft.core.lib.gson2.internal.Primitives; +import com.massivecraft.core.lib.gson2.reflect.TypeToken; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Type adapter that reflects over the fields and methods of a class. + */ +public class ReflectiveTypeAdapterFactory implements TypeAdapter.Factory { + private final ConstructorConstructor constructorConstructor; + + public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor) { + this.constructorConstructor = constructorConstructor; + } + + protected boolean serializeField(Class declaringClazz, Field f, Type declaredType) { + return !f.isSynthetic(); + } + + protected boolean deserializeField(Class declaringClazz, Field f, Type declaredType) { + return !f.isSynthetic(); + } + + protected String getFieldName(Class declaringClazz, Field f, Type declaredType) { + return f.getName(); + } + + public TypeAdapter create(MiniGson context, final TypeToken type) { + Class raw = type.getRawType(); + + if (!Object.class.isAssignableFrom(raw)) { + return null; // it's a primitive! + } + + ObjectConstructor constructor = constructorConstructor.getConstructor(type); + return new Adapter(constructor, getBoundFields(context, type, raw)); + } + + private ReflectiveTypeAdapterFactory.BoundField createBoundField( + final MiniGson context, final Field field, final String name, + final TypeToken fieldType, boolean serialize, boolean deserialize) { + final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType()); + + // special casing primitives here saves ~5% on Android... + return new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) { + final TypeAdapter typeAdapter = context.getAdapter(fieldType); + @SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree + @Override void write(JsonWriter writer, Object value) + throws IOException, IllegalAccessException { + Object fieldValue = field.get(value); + TypeAdapter t = + new TypeAdapterRuntimeTypeWrapper(context, this.typeAdapter, fieldType.getType()); + t.write(writer, fieldValue); + } + @Override void read(JsonReader reader, Object value) + throws IOException, IllegalAccessException { + Object fieldValue = typeAdapter.read(reader); + if (fieldValue != null || !isPrimitive) { + field.set(value, fieldValue); + } + } + }; + } + + private Map getBoundFields( + MiniGson context, TypeToken type, Class raw) { + Map result = new LinkedHashMap(); + if (raw.isInterface()) { + return result; + } + + Type declaredType = type.getType(); + while (raw != Object.class) { + Field[] fields = raw.getDeclaredFields(); + AccessibleObject.setAccessible(fields, true); + for (Field field : fields) { + boolean serialize = serializeField(raw, field, declaredType); + boolean deserialize = deserializeField(raw, field, declaredType); + if (!serialize && !deserialize) { + continue; + } + Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType()); + BoundField boundField = createBoundField(context, field, getFieldName(raw, field, declaredType), + TypeToken.get(fieldType), serialize, deserialize); + BoundField previous = result.put(boundField.name, boundField); + if (previous != null) { + throw new IllegalArgumentException(declaredType + + " declares multiple JSON fields named " + previous.name); + } + } + type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass())); + raw = type.getRawType(); + } + return result; + } + + static abstract class BoundField { + final String name; + final boolean serialized; + final boolean deserialized; + + protected BoundField(String name, boolean serialized, boolean deserialized) { + this.name = name; + this.serialized = serialized; + this.deserialized = deserialized; + } + + abstract void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException; + abstract void read(JsonReader reader, Object value) throws IOException, IllegalAccessException; + } + + public final class Adapter extends TypeAdapter { + private final ObjectConstructor constructor; + private final Map boundFields; + + private Adapter(ObjectConstructor constructor, Map boundFields) { + this.constructor = constructor; + this.boundFields = boundFields; + } + + @Override + public T read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + + T instance = constructor.construct(); + + // TODO: null out the other fields? + + try { + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + BoundField field = boundFields.get(name); + if (field == null || !field.deserialized) { + // TODO: define a better policy + reader.skipValue(); + } else { + field.read(reader, instance); + } + } + } catch (IllegalStateException e) { + throw new JsonSyntaxException(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + reader.endObject(); + return instance; + } + + @Override + public void write(JsonWriter writer, T value) throws IOException { + if (value == null) { + writer.nullValue(); // TODO: better policy here? + return; + } + + writer.beginObject(); + try { + for (BoundField boundField : boundFields.values()) { + if (boundField.serialized) { + writer.name(boundField.name); + boundField.write(writer, value); + } + } + } catch (IllegalAccessException e) { + throw new AssertionError(); + } + writer.endObject(); + } + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/SqlDateTypeAdapter.java b/src/com/massivecraft/core/lib/gson2/internal/bind/SqlDateTypeAdapter.java new file mode 100755 index 00000000..cec7addd --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/SqlDateTypeAdapter.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.JsonSyntaxException; +import com.massivecraft.core.lib.gson2.reflect.TypeToken; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; + +/** + * Adapter for java.sql.Date. Although this class appears stateless, it is not. + * DateFormat captures its time zone and locale when it is created, which gives + * this class state. DateFormat isn't thread safe either, so this class has + * to synchronize its read and write methods. + */ +public final class SqlDateTypeAdapter extends TypeAdapter { + public static final Factory FACTORY = new Factory() { + @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal + public TypeAdapter create(MiniGson context, TypeToken typeToken) { + return typeToken.getRawType() == java.sql.Date.class + ? (TypeAdapter) new SqlDateTypeAdapter() : null; + } + }; + + private final DateFormat format = new SimpleDateFormat("MMM d, yyyy"); + + @Override + public synchronized java.sql.Date read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + try { + final long utilDate = format.parse(reader.nextString()).getTime(); + return new java.sql.Date(utilDate); + } catch (ParseException e) { + throw new JsonSyntaxException(e); + } + } + + @Override + public synchronized void write(JsonWriter writer, java.sql.Date value) throws IOException { + writer.value(value == null ? null : format.format(value)); + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/StringToValueMapTypeAdapterFactory.java b/src/com/massivecraft/core/lib/gson2/internal/bind/StringToValueMapTypeAdapterFactory.java new file mode 100755 index 00000000..9157f99d --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/StringToValueMapTypeAdapterFactory.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.internal.$Gson$Types; +import com.massivecraft.core.lib.gson2.internal.ConstructorConstructor; +import com.massivecraft.core.lib.gson2.internal.ObjectConstructor; +import com.massivecraft.core.lib.gson2.reflect.TypeToken; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +/** + * Adapt a map whose keys are strings. + */ +public final class StringToValueMapTypeAdapterFactory implements TypeAdapter.Factory { + private final ConstructorConstructor constructorConstructor; + + public StringToValueMapTypeAdapterFactory(ConstructorConstructor constructorConstructor) { + this.constructorConstructor = constructorConstructor; + } + + public TypeAdapter create(MiniGson context, TypeToken typeToken) { + Type type = typeToken.getType(); + if (!(type instanceof ParameterizedType)) { + return null; + } + + Class rawType = typeToken.getRawType(); + if (!Map.class.isAssignableFrom(rawType)) { + return null; + } + + Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawType); + if (keyAndValueTypes[0] != String.class) { + return null; + } + TypeAdapter valueAdapter = context.getAdapter(TypeToken.get(keyAndValueTypes[1])); + + ObjectConstructor constructor = constructorConstructor.getConstructor(typeToken); + + @SuppressWarnings({"unchecked", "rawtypes"}) + // we don't define a type parameter for the key or value types + TypeAdapter result = new Adapter(valueAdapter, constructor); + return result; + } + + private final class Adapter extends TypeAdapter> { + private final TypeAdapter valueTypeAdapter; + private final ObjectConstructor> constructor; + + public Adapter(TypeAdapter valueTypeAdapter, + ObjectConstructor> constructor) { + this.valueTypeAdapter = valueTypeAdapter; + this.constructor = constructor; + } + + public Map read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } + + Map map = constructor.construct(); + reader.beginObject(); + while (reader.hasNext()) { + String key = reader.nextName(); + V value = valueTypeAdapter.read(reader); + map.put(key, value); + } + reader.endObject(); + return map; + } + + public void write(JsonWriter writer, Map map) throws IOException { + if (map == null) { + writer.nullValue(); // TODO: better policy here? + return; + } + + writer.beginObject(); + for (Map.Entry entry : map.entrySet()) { + writer.name(entry.getKey()); + valueTypeAdapter.write(writer, entry.getValue()); + } + writer.endObject(); + } + } +} diff --git a/src/com/massivecraft/core/lib/gson2/internal/bind/TimeTypeAdapter.java b/src/com/massivecraft/core/lib/gson2/internal/bind/TimeTypeAdapter.java new file mode 100755 index 00000000..3f5172a3 --- /dev/null +++ b/src/com/massivecraft/core/lib/gson2/internal/bind/TimeTypeAdapter.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 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.massivecraft.core.lib.gson2.internal.bind; + +import com.massivecraft.core.lib.gson2.JsonSyntaxException; +import com.massivecraft.core.lib.gson2.reflect.TypeToken; +import com.massivecraft.core.lib.gson2.stream.JsonReader; +import com.massivecraft.core.lib.gson2.stream.JsonToken; +import com.massivecraft.core.lib.gson2.stream.JsonWriter; + +import java.io.IOException; +import java.sql.Time; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Adapter for Time. Although this class appears stateless, it is not. + * DateFormat captures its time zone and locale when it is created, which gives + * this class state. DateFormat isn't thread safe either, so this class has + * to synchronize its read and write methods. + */ +public final class TimeTypeAdapter extends TypeAdapter