All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.coursera.courier.android.runtime.UnionAdapterFactory Maven / Gradle / Ivy

package org.coursera.courier.android.runtime;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
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.JsonWriter;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * GSON {@link com.google.gson.TypeAdapterFactory} for Pegasus style unions.
 *
 * For example, consider a union "AnswerFormat" with the union member options of "TextEntry" and
 * "MultipleChoice".
 *
 * Example JSON:
 *
 * 
 *   {
 *     "org.example.TextEntry": { "textEntryField1": ... }
 *   }
 * 
 *
 * Example Usage with a GSON Java class:
 *
 * 
 * {@literal}JsonAdapter(AnswerFormat.Adapter.class)
 * interface AnswerFormat {
 *   public final class TextEntryMember implements AnswerFormat {
 *     private static final String MEMBER_KEY = "org.example.TextEntry";
 *
 *      {@literal}SerializedName(MEMBER_KEY)
 *      public TextEntry member;
 *   }
 *
 *   public final class MultipleChoiceMember implements AnswerFormat {
 *     // ...
 *   }
 *
 *   final class Adapter extends UnionAdapterFactory<AnswerFormat> {
 *     Adapter() {
 *       super(AnswerFormat.class, new Resolver<AnswerFormat>() {
 *         public Class<? extends AnswerFormat> resolve(String memberKey) {
 *           switch(memberKey) {
 *             case AnswerFormat.MEMBER_KEY: return TextEntryMember.class;
 *             case MultipleChoice.MEMBER_KEY: return MultipleChoice.class;
 *             // ...
 *           }
 *         }
 *       });
 *     }
 *   }
 * }
 * 
 *
 * @param  provides the marker interface that identifies the union. All union members wrappers
 *           must implement this interface.
 */
public class UnionAdapterFactory implements TypeAdapterFactory {

  /**
   * Provides a mapping of typedDefinition "typeName"s (which are the union tags) to their
   * corresponding Java classes.
   *
   * @param  provides the marker interface that identifies the union.
   */
  public interface Resolver {
    public Class resolve(String memberKey);
  }

  private Class unionClass;
  private Resolver resolver;

  public UnionAdapterFactory(Class unionClass, Resolver resolver) {
    this.unionClass = unionClass;
    this.resolver = resolver;
  }

  @Override
  public  TypeAdapter create(Gson gson, TypeToken type) {
    if (unionClass.equals(type.getType())) {
      return new UnionAdapter(gson);
    } else {
      return null;
    }
  }

  private class UnionAdapter extends TypeAdapter {
    private Gson gson;

    public UnionAdapter(Gson gson) {
      this.gson = gson;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void write(JsonWriter out, T value) throws IOException {
      if (!unionClass.equals(value.getClass())) {
        TypeToken actualType = TypeToken.get((Class)value.getClass());
        gson.getDelegateAdapter(UnionAdapterFactory.this, actualType).write(out, value);
      }
      // else GSON is actually trying to serialize an abstract class, which would be a GSON bug
    }

    @Override
    @SuppressWarnings("unchecked")
    public T read(JsonReader reader) throws IOException {
      // Ideally we would stream the data from the reader here, but we need to inspect the
      // 'typeName' field, which may appear after the 'definition' field.

      JsonElement element = new JsonParser().parse(reader);
      Set> entries = element.getAsJsonObject().entrySet();
      if (entries.size() != 1) {
        StringBuilder keys = new StringBuilder();
        Iterator> iter = entries.iterator();
        while (iter.hasNext()) {
          keys.append("'").append(iter.next().getKey()).append("'");
          if (iter.hasNext()) keys.append(", ");
        }
        throw new IOException(
            "JSON object for '" + unionClass.getName() + "' union must contain exactly one " +
            "'memberKey' field but contains " +
                (keys.length() > 0 ? keys.toString() : "no fields") +  " at path " + reader.getPath());
      }
      Map.Entry member = entries.iterator().next();
      Class clazz = (Class) resolver.resolve(member.getKey());
      return gson.getAdapter(clazz).fromJsonTree(element);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy