Package openperipheral.adapter.method

Source Code of openperipheral.adapter.method.MethodDeclaration$CallWrap

package openperipheral.adapter.method;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.Callable;

import openmods.Log;
import openmods.utils.AnnotationMap;
import openmods.utils.ReflectionHelper;
import openperipheral.TypeConversionRegistry;
import openperipheral.adapter.IDescriptable;
import openperipheral.api.*;

import org.apache.logging.log4j.Level;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.*;

public class MethodDeclaration implements IDescriptable {

  private final List<String> names;
  private final Method method;
  private final String description;
  private final LuaType[] returnTypes;

  private final boolean validateReturn;

  private final Map<String, Integer> namedArgs = Maps.newHashMap();
  private final Set<String> allowedNames = Sets.newHashSet();

  private final List<Class<?>> javaArgs;
  private final List<Argument> luaArgs;

  private static boolean checkOptional(boolean currentState, AnnotationMap annotations) {
    return currentState || annotations.get(Optionals.class) != null;
  }

  private Argument createLuaArg(ArgumentBuilder builder, AnnotationMap annotations, Class<?> javaArgType, int index) {
    Arg arg = annotations.get(Arg.class);
    Preconditions.checkNotNull(arg, "Argument %d has no annotation", index);
    try {
      return builder.build(arg.name(), arg.description(), arg.type(), javaArgType, index);
    } catch (Exception e) {
      throw new IllegalArgumentException(String.format("Argument %d from method '%s' in invalid", index, method), e);
    }
  }

  private static List<String> getNames(Method method, String mainName) {
    ImmutableList.Builder<String> names = ImmutableList.builder();
    names.add(mainName);

    Alias alias = method.getAnnotation(Alias.class);
    if (alias != null) names.add(alias.value());
    return names.build();
  }

  public MethodDeclaration(Method method, LuaMethod luaMethod) {
    this.method = method;

    String luaName = luaMethod.name();
    names = getNames(method, (LuaMethod.USE_METHOD_NAME.equals(luaName))? method.getName() : luaName);

    this.description = luaMethod.description();
    this.returnTypes = new LuaType[] { luaMethod.returnType() };
    this.validateReturn = false;

    final Class<?> methodArgs[] = method.getParameterTypes();
    final Arg declaredLuaArgs[] = luaMethod.args();
    final Annotation[][] argsAnnotations = method.getParameterAnnotations();
    final int luaArgsStart = methodArgs.length - declaredLuaArgs.length;
    final boolean isVarArg = method.isVarArgs();

    Preconditions.checkArgument(luaArgsStart >= 0, "Method %s has less arguments than declared", method);

    boolean isOptional = false;

    ImmutableList.Builder<Argument> luaArgs = ImmutableList.builder();
    for (int arg = 0; arg < declaredLuaArgs.length; arg++) {
      boolean isLastArg = arg == (declaredLuaArgs.length - 1);

      AnnotationMap annotations = new AnnotationMap(argsAnnotations[arg]);
      Arg ann = declaredLuaArgs[arg];
      annotations.put(ann);
      int javaArgIndex = luaArgsStart + arg;

      isOptional = checkOptional(isOptional, annotations);

      ArgumentBuilder builder = new ArgumentBuilder();
      builder.setVararg(isLastArg && isVarArg);
      builder.setOptional(isOptional);
      builder.setNullable(ann.isNullable());

      luaArgs.add(createLuaArg(builder, annotations, methodArgs[javaArgIndex], javaArgIndex));
    }

    this.luaArgs = luaArgs.build();
    this.javaArgs = ImmutableList.copyOf(Arrays.copyOf(methodArgs, luaArgsStart));

    for (int arg = 0; arg < luaArgsStart; arg++) {
      AnnotationMap annotations = new AnnotationMap(argsAnnotations[arg]);
      Named named = annotations.get(Named.class);
      if (named != null) namedArgs.put(named.value(), arg);
      Preconditions.checkState(annotations.get(Optionals.class) == null, "@Optionals does not work for java arguments (method %s)", method);
    }
  }

  public MethodDeclaration(Method method, LuaCallable meta) {
    this.method = method;

    String luaName = meta.name();
    this.names = getNames(method, (LuaCallable.USE_METHOD_NAME.equals(luaName))? method.getName() : luaName);

    this.description = meta.description();
    this.returnTypes = meta.returnTypes();
    this.validateReturn = meta.validateReturn();

    if (validateReturn) validateResultCount();

    final Class<?> methodArgs[] = method.getParameterTypes();
    final Annotation[][] argsAnnotations = method.getParameterAnnotations();
    final boolean isVarArg = method.isVarArgs();

    ImmutableList.Builder<Argument> luaArgs = ImmutableList.builder();
    ImmutableList.Builder<Class<?>> javaArgs = ImmutableList.builder();
    boolean isInLuaArgs = false;
    boolean isOptional = false;

    for (int i = 0; i < methodArgs.length; i++) {
      boolean isLastArg = i == (methodArgs.length - 1);
      final Class<?> cls = methodArgs[i];

      AnnotationMap annotations = new AnnotationMap(argsAnnotations[i]);

      boolean isLuaArg = false;

      Arg tmp = annotations.get(Arg.class);
      if (tmp != null) {
        isOptional = checkOptional(isOptional, annotations);

        ArgumentBuilder builder = new ArgumentBuilder();
        builder.setVararg(isLastArg && isVarArg);
        builder.setOptional(isOptional);
        builder.setNullable(tmp.isNullable());

        luaArgs.add(createLuaArg(builder, annotations, cls, i));

        isLuaArg = true;
        isInLuaArgs = true;
      }

      Preconditions.checkState(!isInLuaArgs || isLuaArg, "Argument %s in method %s look like Java arg, but is in Lua part (perhaps missing Arg annotation?)", i, method);

      Named named = annotations.get(Named.class);
      if (named != null) {
        Preconditions.checkState(!isInLuaArgs, "Argument %s in method %s is Lua arg, but has Named annotation", i, method);
        namedArgs.put(named.value(), i);
      }

      Preconditions.checkState(isInLuaArgs || annotations.get(Optionals.class) == null, "@Optionals does not work for java arguments (method %s)", method);

      if (!isLuaArg) javaArgs.add(cls);
    }

    this.luaArgs = luaArgs.build();
    this.javaArgs = javaArgs.build();
  }

  private void validateResultCount() {
    Class<?> javaReturn = method.getReturnType();

    final int returnLength = returnTypes.length;

    for (LuaType t : returnTypes)
      Preconditions.checkArgument(t != LuaType.VOID, "Method '%s' declares Void as return type. Use empty list instead.", method);

    if (javaReturn == void.class) {
      Preconditions.checkArgument(returnLength == 0, "Method '%s' returns nothing, but declares at least one Lua result", method);
    }

    if (returnLength == 0) {
      Preconditions.checkArgument(javaReturn == void.class, "Method '%s' returns '%s', but declares no Lua results", method, javaReturn);
    }

    if (returnLength > 1) {
      Preconditions.checkArgument(javaReturn == IMultiReturn.class, "Method '%s' declared more than one Lua result, but returns single '%s' instead of '%s'", method, javaReturn, IMultiReturn.class);
    }
  }

  private Object[] validateResult(Object... result) {
    for (int i = 0; i < result.length; i++)
      result[i] = TypeConversionRegistry.INSTANCE.toLua(result[i]);

    if (validateReturn) {
      if (returnTypes.length == 0) {
        Preconditions.checkArgument(result.length == 1 && result[0] == null, "Returning value from null method");
      } else {
        Preconditions.checkArgument(result.length == returnTypes.length, "Returning invalid number of values from method %s, expected %s, got %s", method, returnTypes.length, result.length);
        for (int i = 0; i < result.length; i++) {
          final LuaType expected = returnTypes[i];
          final Class<?> expectedType = expected.getJavaType();
          final Object got = result[i];
          Preconditions.checkArgument(got == null || expectedType.isInstance(got) || ReflectionHelper.compareTypes(expectedType, got.getClass()), "Invalid type of return value %s: expected %s, got %s", i, expected, got);
        }
      }
    }

    return result;
  }

  public class CallWrap implements Callable<Object[]> {
    private final Object[] args = new Object[javaArgs.size() + luaArgs.size()];
    private final Set<Integer> isSet = Sets.newHashSet();
    private final Object target;

    public CallWrap(Object target) {
      this.target = target;
    }

    private CallWrap setArg(int position, Object value) {
      boolean newlyAdded = isSet.add(position);
      Preconditions.checkState(newlyAdded, "Trying to set already defined argument %s in method %s", position, method);
      args[position] = value;
      return this;
    }

    public CallWrap setJavaArg(String name, Object value) {
      Integer position = namedArgs.get(name);
      if (position != null) setArg(position, value);
      return this;
    }

    public CallWrap setLuaArgs(Object[] luaValues) {
      Iterator<Object> it = Iterators.forArray(luaValues);
      try {
        for (Argument arg : luaArgs) {
          Object value = arg.convert(it);
          setArg(arg.javaArgIndex, value);
        }

        Preconditions.checkState(!it.hasNext(), "Too many arguments!");
      } catch (ArrayIndexOutOfBoundsException e) {
        Log.log(Level.TRACE, e, "Trying to access arg index, args = %s", Arrays.toString(luaValues));
        throw new IllegalArgumentException(String.format("Invalid Lua parameter count, needs %s, got %s", luaArgs.size(), luaValues.length));
      }

      return this;
    }

    @Override
    public Object[] call() throws Exception {
      for (int i = 0; i < args.length; i++)
        Preconditions.checkState(isSet.contains(i), "Parameter %s value not set", i);

      Object result;
      try {
        result = method.invoke(target, args);
      } catch (InvocationTargetException e) {
        Throwable wrapper = e.getCause();
        throw Throwables.propagate(wrapper != null? wrapper : e);
      }

      if (result instanceof IMultiReturn) return validateResult(((IMultiReturn)result).getObjects());
      else return validateResult(result);
    }
  }

  public CallWrap createWrapper(Object target) {
    return new CallWrap(target);
  }

  public void nameJavaArg(int index, String name) {
    Preconditions.checkArgument(index < javaArgs.size(),
        "Can't assign name '%s' to argument %s in method '%s'. Possible missing argument or @Freeform?",
        name, index, method);
    Integer prev = namedArgs.put(name, index);
    Preconditions.checkArgument(prev == null || prev == index, "Trying to replace '%s' mapping from  %s, got %s", name, prev, index);
  }

  public void declareJavaArgType(String name, Class<?> cls) {
    allowedNames.add(name);
    Integer index = namedArgs.get(name);
    if (index != null) {
      final Class<?> expected = javaArgs.get(index);
      Preconditions.checkArgument(expected.isAssignableFrom(cls), "Invalid argument type in method %s, was %s, got %s", method, expected, cls);
    }
  }

  public void validate() {
    Set<String> unknown = Sets.difference(namedArgs.keySet(), allowedNames);
    Preconditions.checkState(unknown.isEmpty(), "Unknown named arg(s) %s in method '%s'. Allowed args: %s", unknown, method, allowedNames);

    Set<Integer> needed = Sets.newHashSet();
    for (int i = 0; i < javaArgs.size(); i++)
      needed.add(i);

    Set<Integer> named = Sets.newHashSet(namedArgs.values());
    Set<Integer> missing = Sets.difference(needed, named);
    Preconditions.checkState(missing.isEmpty(), "Arguments %s from method %s are not named", missing, method);

    Set<Integer> extra = Sets.difference(named, needed);
    Preconditions.checkState(missing.isEmpty(), "Lua arguments %s from method %s are named", extra, method);
  }

  @Override
  public Map<String, Object> describe() {
    Map<String, Object> result = Maps.newHashMap();
    result.put(IDescriptable.DESCRIPTION, description);

    {
      List<String> returns = Lists.newArrayList();
      for (LuaType t : returnTypes)
        returns.add(t.toString());
      result.put(IDescriptable.RETURN_TYPES, returns);
    }

    {
      List<Map<String, Object>> args = Lists.newArrayList();
      for (Argument arg : luaArgs)
        args.add(arg.describe());
      result.put(IDescriptable.ARGS, args);
    }
    return result;
  }

  @Override
  public String signature() {
    return "(" + Joiner.on(",").join(luaArgs) + ")";
  }

  @Override
  public List<String> getNames() {
    return names;
  }

  public Class<?>[] getLuaArgTypes() {
    Class<?>[] result = new Class<?>[luaArgs.size()];

    int index = 0;
    for (Argument arg : luaArgs)
      result[index++] = arg.javaType;

    return result;
  }
}
TOP

Related Classes of openperipheral.adapter.method.MethodDeclaration$CallWrap

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.