Package com.getperka.flatpack.codexes

Source Code of com.getperka.flatpack.codexes.AnnotationCodex

package com.getperka.flatpack.codexes;

/*
* #%L
* FlatPack serialization code
* %%
* Copyright (C) 2012 - 2013 Perka 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.
* #L%
*/

import static com.getperka.flatpack.util.FlatPackCollections.listForAny;
import static com.getperka.flatpack.util.FlatPackCollections.sortedMapForIteration;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.slf4j.Logger;

import com.getperka.flatpack.ext.DeserializationContext;
import com.getperka.flatpack.ext.JsonKind;
import com.getperka.flatpack.ext.SerializationContext;
import com.getperka.flatpack.ext.Type;
import com.getperka.flatpack.ext.TypeContext;
import com.getperka.flatpack.ext.TypeHint;
import com.getperka.flatpack.inject.FlatPackLogger;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.stream.JsonWriter;

/**
* Encodes Java annotations as simple datastructures. If an annotation being deserialized is not
* available in the classpath, it will be replaced with an {@link UnknownAnnotation}. The annotation
* instances produced by this codex will also implement the {@link AnnotationInfo} interface, which
* allows map-based access to annotation properties.
*/
public class AnnotationCodex extends ValueCodex<Annotation> {
  static class Handler implements InvocationHandler, AnnotationInfo {
    private final Class<? extends Annotation> annotationType;
    private final String annotationTypeName;
    private final Map<String, Object> values;

    Handler(Class<? extends Annotation> annotationType, Map<String, Object> values) {
      this.annotationType = annotationType;
      this.annotationTypeName = annotationType.getName();
      this.values = values;
    }

    Handler(String annotationTypeName, Map<String, Object> values) {
      this.annotationType = UnknownAnnotation.class;
      this.annotationTypeName = annotationTypeName;
      this.values = values;
    }

    @Override
    public String getAnnotationTypeName() {
      return annotationTypeName;
    }

    @Override
    public Map<String, Object> getAnnotationValues() {
      return values;
    }

    @Override
    public int hashCode() {
      return values.hashCode();
    }

    @Override
    public Object invoke(Object instance, Method m, Object[] args) throws Throwable {
      if (Object.class.equals(m.getDeclaringClass())) {
        if (m.getName().equals("equals")) {
          return equals((Annotation) instance, args[0]);
        }
        return m.invoke(this, args);
      }
      if (AnnotationInfo.class.equals(m.getDeclaringClass())) {
        return m.invoke(this, args);
      }
      if (m.getName().equals("annotationType")) {
        return annotationType;
      }
      Object toReturn = values.get(m.getName());
      if (toReturn == null) {
        return m.getDefaultValue();
      }
      return toReturn;
    }

    @Override
    public String toString() {
      return values.toString();
    }

    private List<?> asList(Object array) {
      List<Object> toReturn = listForAny();
      for (int i = 0, j = Array.getLength(array); i < j; i++) {
        toReturn.add(Array.get(array, i));
      }
      return toReturn;
    }

    private boolean equals(Annotation instance, Object obj) {
      // Ensure the incoming object is an annotation of the same type
      if (!(obj instanceof Annotation)
        || !annotationType.equals(((Annotation) obj).annotationType())) {
        return false;
      }

      if (obj instanceof AnnotationInfo) {
        AnnotationInfo info = (AnnotationInfo) obj;
        if (!annotationTypeName.equals(info.getAnnotationTypeName())) {
          return false;
        }
      }

      // Quick test for comparison to self
      if (Proxy.isProxyClass(obj.getClass()) && Proxy.getInvocationHandler(obj) instanceof Handler) {
        Handler handler = (Handler) Proxy.getInvocationHandler(obj);
        if (this == handler) {
          return true;
        }
      }
      return extractValues(instance).equals(extractValues((Annotation) obj));
    }

    private Map<String, Object> extractValues(Annotation obj) {
      Map<String, Object> compareTo = sortedMapForIteration();

      // Support for UnknownAnnotation
      if (obj instanceof AnnotationInfo) {
        AnnotationInfo info = (AnnotationInfo) obj;
        for (Map.Entry<String, Object> entry : info.getAnnotationValues().entrySet()) {
          Object value = entry.getValue();
          if (value.getClass().isArray()) {
            value = asList(value);
          }
          compareTo.put(entry.getKey(), value);
          System.out.println(entry.getKey() + " " + value.getClass().getName() + " " + value);
        }
        return compareTo;
      }

      for (Method m : annotationType.getDeclaredMethods()) {
        m.setAccessible(true);
        Throwable ex;
        try {
          Object value = m.invoke(obj);
          if (m.getReturnType().isArray()) {
            value = asList(value);
          }
          compareTo.put(m.getName(), value);
          continue;
        } catch (IllegalAccessException e) {
          // Unexpected, since interface methods are public
          ex = e;
        } catch (InvocationTargetException e) {
          ex = e.getCause();
        }
        throw new RuntimeException("Could not extract annotation value", ex);
      }
      return compareTo;
    }
  }

  private static final String TYPE_KEY = "@";

  private static final Type TYPE = new Type.Builder()
      .withJsonKind(JsonKind.ANY)
      .withTypeHint(TypeHint.create(Annotation.class))
      .build();

  @Inject
  private DynamicCodex dynamicCodex;
  @FlatPackLogger
  @Inject
  private Logger logger;
  @Inject
  private TypeContext typeContext;

  /**
   * Requires injection.
   */
  protected AnnotationCodex() {}

  @Override
  public Type describe() {
    return TYPE;
  }

  @Override
  public Annotation readNotNull(JsonElement element, DeserializationContext context)
      throws Exception {
    JsonObject obj = element.getAsJsonObject();
    if (!obj.has(TYPE_KEY)) {
      logger.error("Incoming annotation has no @ member");
      return null;
    }

    String typeName = obj.get(TYPE_KEY).getAsString();
    Map<String, Object> values = sortedMapForIteration();

    Class<? extends Annotation> annotationType;
    Handler h;
    try {
      annotationType =
          Class.forName(typeName, false, Thread.currentThread().getContextClassLoader())
              .asSubclass(Annotation.class);

      for (Method m : annotationType.getDeclaredMethods()) {
        JsonElement elt = obj.get(m.getName());
        if (elt == null || elt.isJsonNull()) {
          continue;
        }
        Object value = typeContext.getCodex(m.getGenericReturnType()).read(elt, context);
        values.put(m.getName(), value);
      }
      h = new Handler(annotationType, Collections.unmodifiableMap(values));
    } catch (ClassCastException e) {
      logger.warn(
          "Attempting to decode an annotation type @{} which is not assignable to Annotation",
          typeName);
      return null;
    } catch (ClassNotFoundException e) {
      annotationType = UnknownAnnotation.class;

      for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
        if (TYPE_KEY.equals(entry.getKey())) {
          continue;
        }
        Object value;
        if (entry.getValue().isJsonObject() && entry.getValue().getAsJsonObject().has(TYPE_KEY)) {
          // Try to decode nested annotations
          value = readNotNull(entry.getValue(), context);
        } else {
          // Guess at value types
          value = dynamicCodex.read(entry.getValue(), context);
        }
        values.put(entry.getKey(), value);
      }
      h = new Handler(typeName, Collections.unmodifiableMap(values));
    }

    Annotation a = annotationType.cast(
        Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
            new Class<?>[] { annotationType, AnnotationInfo.class }, h));
    return a;
  }

  @Override
  public void writeNotNull(Annotation a, SerializationContext context) throws Exception {
    JsonWriter writer = context.getWriter().beginObject().name(TYPE_KEY);
    if (a instanceof AnnotationInfo) {
      // Support for UnknownAnnotation
      AnnotationInfo info = (AnnotationInfo) a;
      writer.value(info.getAnnotationTypeName());
      for (Map.Entry<String, Object> entry : info.getAnnotationValues().entrySet()) {
        writer.name(entry.getKey());
        dynamicCodex.write(entry.getValue(), context);
      }
    } else {
      // The usual case
      Class<? extends Annotation> annotationType = a.annotationType();
      writer.value(annotationType.getName());
      for (Method m : annotationType.getDeclaredMethods()) {
        m.setAccessible(true);
        Object value = m.invoke(a);
        writer.name(m.getName());
        typeContext.getCodex(m.getReturnType()).write(value, context);
      }
    }
    writer.endObject();
  }
}
TOP

Related Classes of com.getperka.flatpack.codexes.AnnotationCodex

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
t.html" title="Examples of com.sencha.gxt.core.client.dom.XElement">com.sencha.gxt.core.client.dom.XElement
  • com.vividsolutions.jts.geom.Coordinate
  • com.vividsolutions.jts.geom.Envelope
  • TOP
    Copyright © 2018 www.massapi.com. All rights reserved.
    All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.