| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.jclouds.json.internal; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static org.jclouds.reflect.Reflection2.typeToken; |
| |
| import java.io.IOException; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Modifier; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.jclouds.json.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory; |
| import org.jclouds.json.gson.internal.ConstructorConstructor; |
| import org.jclouds.json.gson.internal.Excluder; |
| import org.jclouds.json.gson.internal.bind.ReflectiveTypeAdapterFactory; |
| import org.jclouds.json.internal.NamingStrategies.AnnotationConstructorNamingStrategy; |
| |
| import com.google.common.base.MoreObjects; |
| import com.google.common.base.Objects; |
| import com.google.common.base.Optional; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableMap.Builder; |
| import com.google.common.reflect.Invokable; |
| import com.google.common.reflect.Parameter; |
| import com.google.gson.FieldNamingStrategy; |
| import com.google.gson.Gson; |
| import com.google.gson.JsonSyntaxException; |
| import com.google.gson.TypeAdapter; |
| import com.google.gson.TypeAdapterFactory; |
| import com.google.gson.reflect.TypeToken; |
| import com.google.gson.stream.JsonReader; |
| import com.google.gson.stream.JsonToken; |
| import com.google.gson.stream.JsonWriter; |
| |
| /** |
| * Creates type adapters for types handled in the following ways: |
| * <p/> |
| * <ul> |
| * <li>Deserialization</li> |
| * If there's an annotation designating a parameterized constructor, invoke that for fields correlating to named |
| * parameter annotations. Otherwise, use {@link ConstructorConstructor}, and set fields via reflection. |
| * <p/> |
| * Notes: primitive constructor params are set to the Java defaults (0 or false) if not present; and the empty object |
| * ({}) is treated as a null if the constructor for the object throws an NPE. |
| * <li>Serialization</li> Serialize based on reflective access to fields, delegating to ReflectiveTypeAdaptor. |
| * </ul> |
| * <h3>Example: Using javax inject to select a constructor and corresponding named parameters</h3> |
| * <p/> |
| * |
| * <pre> |
| * |
| * import NamingStrategies.*; |
| * |
| * serializationStrategy = new AnnotationOrNameFieldNamingStrategy( |
| * new ExtractSerializedName(), new ExtractNamed()); |
| * |
| * deserializationStrategy = new AnnotationConstructorNamingStrategy( |
| * ImmutableSet.of(javax.inject.Inject.class), |
| * ImmutableSet.of(new ExtractNamed())); |
| * |
| * factory = new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(), |
| * serializationStrategy, Excluder.DEFAULT, deserializationStrategy); |
| * |
| * gson = new GsonBuilder(serializationStrategy).registerTypeAdapterFactory(factory).create(); |
| * |
| * </pre> |
| * <p/> |
| * The above would work fine on the following class, which has no gson-specific annotations: |
| * <p/> |
| * |
| * <pre> |
| * private static class ImmutableAndVerifiedInCtor { |
| * final int foo; |
| * @Named("_bar") |
| * final int bar; |
| * |
| * @Inject |
| * ImmutableAndVerifiedInCtor(@Named("foo") int foo, @Named("_bar") int bar) { |
| * if (foo < 0) |
| * throw new IllegalArgumentException("negative!"); |
| * this.foo = foo; |
| * this.bar = bar; |
| * } |
| * } |
| * </pre> |
| * <p/> |
| * <br/> |
| */ |
| public final class DeserializationConstructorAndReflectiveTypeAdapterFactory implements TypeAdapterFactory { |
| private final AnnotationConstructorNamingStrategy constructorFieldNamingPolicy; |
| private final ReflectiveTypeAdapterFactory delegateFactory; |
| |
| /** |
| * @see ReflectiveTypeAdapterFactory |
| */ |
| public DeserializationConstructorAndReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor, |
| FieldNamingStrategy serializationFieldNamingPolicy, Excluder excluder, |
| AnnotationConstructorNamingStrategy deserializationFieldNamingPolicy) { |
| this.constructorFieldNamingPolicy = checkNotNull(deserializationFieldNamingPolicy, |
| "deserializationFieldNamingPolicy"); |
| this.delegateFactory = new ReflectiveTypeAdapterFactory(constructorConstructor, checkNotNull( |
| serializationFieldNamingPolicy, "fieldNamingPolicy"), checkNotNull(excluder, "excluder"), |
| new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor)); |
| } |
| |
| public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { |
| com.google.common.reflect.TypeToken<T> token = typeToken(type.getType()); |
| Invokable<T, T> deserializationTarget = constructorFieldNamingPolicy.getDeserializer(token); |
| |
| if (deserializationTarget == null) { |
| return null; // allow GSON to choose the correct Adapter (can't simply return delegateFactory.create()) |
| } |
| // @AutoValue is SOURCE retention, which means it cannot be looked up at runtime. |
| // Assume abstract types built by static methods are AutoValue. |
| if (Modifier.isAbstract(type.getRawType().getModifiers()) && deserializationTarget.isStatic()) { |
| // Lookup the generated AutoValue class, whose fields must be read for serialization. |
| String packageName = type.getRawType().getPackage().getName(); |
| String autoClassName = type.getRawType().getName().replace('$', '_') |
| .replace(packageName + ".", packageName + ".AutoValue_"); |
| try { |
| type = (TypeToken<T>) TypeToken.get(type.getRawType().getClassLoader().loadClass(autoClassName)); |
| } catch (ClassNotFoundException ignored) { |
| } |
| } |
| return new DeserializeIntoParameterizedConstructor<T>(delegateFactory.create(gson, type), deserializationTarget, |
| getParameterReaders(gson, deserializationTarget)); |
| } |
| |
| private static final class DeserializeIntoParameterizedConstructor<T> extends TypeAdapter<T> { |
| private final TypeAdapter<T> serializer; |
| private final Invokable<T, T> parameterizedCtor; |
| private final Map<String, ParameterReader<?>> parameterReaders; |
| |
| private DeserializeIntoParameterizedConstructor(TypeAdapter<T> serializer, Invokable<T, T> deserializationCtor, |
| Map<String, ParameterReader<?>> parameterReaders) { |
| this.serializer = serializer; |
| this.parameterizedCtor = deserializationCtor; |
| this.parameterReaders = parameterReaders; |
| } |
| |
| @Override |
| public T read(JsonReader in) throws IOException { |
| if (in.peek() == JsonToken.NULL) { |
| in.nextNull(); |
| return null; |
| } |
| |
| List<Parameter> params = parameterizedCtor.getParameters(); |
| Object[] values = new Object[params.size()]; |
| boolean empty = true; |
| |
| // Set all primitive constructor params to defaults |
| for (Parameter param : params) { |
| if (param.getType().getRawType() == boolean.class) { |
| values[param.hashCode()] = Boolean.FALSE; |
| } else if (param.getType().getRawType().isPrimitive()) { |
| values[param.hashCode()] = 0; |
| } |
| } |
| |
| try { |
| in.beginObject(); |
| while (in.hasNext()) { |
| empty = false; |
| String name = in.nextName(); |
| ParameterReader<?> parameter = parameterReaders.get(name); |
| if (parameter == null || in.peek() == JsonToken.NULL) { |
| in.skipValue(); |
| } else { |
| Object value = parameter.read(in); |
| if (value != null) |
| values[parameter.position] = value; |
| } |
| } |
| } catch (IllegalStateException e) { |
| throw new JsonSyntaxException(e); |
| } |
| |
| for (Parameter param : params) { |
| if (param.getType().getRawType().isPrimitive()) { |
| checkArgument(values[param.hashCode()] != null, |
| "Primitive param[%s] in constructor %s cannot be absent!", param.hashCode(), parameterizedCtor); |
| } else if (param.getType().getRawType() == Optional.class && values[param.hashCode()] == null) { |
| values[param.hashCode()] = Optional.absent(); |
| } |
| } |
| in.endObject(); |
| |
| try { |
| return newInstance(values); |
| } catch (NullPointerException ex) { |
| // If {} was found and constructor threw NPE, we treat the field as null |
| if (empty && values.length > 0) { |
| return null; |
| } |
| throw ex; |
| } |
| } |
| |
| /** |
| * pass to delegate |
| */ |
| @Override |
| public void write(JsonWriter out, T value) throws IOException { |
| serializer.write(out, value); |
| } |
| |
| private T newInstance(Object[] ctorParams) throws AssertionError { |
| try { |
| return parameterizedCtor.invoke(null, ctorParams); |
| } catch (IllegalAccessException e) { |
| throw new AssertionError(e); |
| } catch (InvocationTargetException e) { |
| if (e.getCause() instanceof RuntimeException) |
| throw RuntimeException.class.cast(e.getCause()); |
| throw new AssertionError(e); |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(serializer, parameterizedCtor, parameterReaders); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) |
| return true; |
| if (obj == null || getClass() != obj.getClass()) |
| return false; |
| DeserializeIntoParameterizedConstructor<?> that = DeserializeIntoParameterizedConstructor.class.cast(obj); |
| return Objects.equal(this.serializer, that.serializer) |
| && Objects.equal(this.parameterizedCtor, that.parameterizedCtor) |
| && Objects.equal(this.parameterReaders, that.parameterReaders); |
| } |
| |
| @Override |
| public String toString() { |
| return MoreObjects.toStringHelper(this).add("parameterizedCtor", parameterizedCtor) |
| .add("parameterReaders", parameterReaders).add("serializer", serializer).toString(); |
| } |
| |
| } |
| |
| // logic borrowed from ReflectiveTypeAdapterFactory |
| static class ParameterReader<T> { |
| final String name; |
| final int position; |
| final TypeAdapter<T> typeAdapter; |
| |
| ParameterReader(int position, String name, TypeAdapter<T> typeAdapter) { |
| this.name = name; |
| this.position = position; |
| this.typeAdapter = typeAdapter; |
| } |
| |
| public Object read(JsonReader reader) throws IOException { |
| return typeAdapter.read(reader); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof ParameterReader) { |
| ParameterReader<?> that = ParameterReader.class.cast(obj); |
| return position == that.position && name.equals(that.name); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(position, name); |
| } |
| |
| @Override |
| public String toString() { |
| return typeAdapter + " arg" + position; |
| } |
| } |
| |
| private <T> Map<String, ParameterReader<?>> getParameterReaders(Gson context, Invokable<T, T> deserializationCtor) { |
| Builder<String, ParameterReader<?>> result = ImmutableMap.builder(); |
| for (Parameter param : deserializationCtor.getParameters()) { |
| TypeAdapter<?> adapter = context.getAdapter(TypeToken.get(param.getType().getType())); |
| String parameterName = constructorFieldNamingPolicy.translateName(deserializationCtor, param.hashCode()); |
| checkArgument(parameterName != null, deserializationCtor + " parameter " + 0 + " failed to be named by " |
| + constructorFieldNamingPolicy); |
| @SuppressWarnings({ "rawtypes", "unchecked" }) |
| ParameterReader<?> parameterReader = new ParameterReader(param.hashCode(), parameterName, adapter); |
| result.put(parameterReader.name, parameterReader); |
| } |
| return result.build(); |
| } |
| } |