Package ch.softappeal.yass.ts

Source Code of ch.softappeal.yass.ts.ContractGenerator$ServiceDesc

package ch.softappeal.yass.ts;

import ch.softappeal.yass.Version;
import ch.softappeal.yass.core.remote.ContractId;
import ch.softappeal.yass.core.remote.MethodMapper;
import ch.softappeal.yass.serialize.fast.AbstractFastSerializer;
import ch.softappeal.yass.serialize.fast.ClassTypeHandler;
import ch.softappeal.yass.serialize.fast.JsFastSerializer;
import ch.softappeal.yass.serialize.fast.TypeDesc;
import ch.softappeal.yass.serialize.fast.TypeHandler;
import ch.softappeal.yass.util.Check;
import ch.softappeal.yass.util.Nullable;

import java.io.BufferedReader;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.stream.Collectors;

/**
* Note: You must use the -parameters option for javac to get the real method parameter names.
*/
public final class ContractGenerator extends Generator {

  private final String rootPackage;
  private final LinkedHashMap<Class<?>, Integer> type2id = new LinkedHashMap<>();
  private final SortedMap<Integer, TypeHandler> id2typeHandler;
  private final Set<Class<?>> visitedClasses = new HashSet<>();
  private final MethodMapper.Factory methodMapperFactory;

  private void checkType(final Class<?> type) {
    if (!type.getCanonicalName().startsWith(rootPackage)) {
      throw new RuntimeException("type '" + type.getCanonicalName() + "' has wrong root package");
    }
  }

  private String jsType(final Class<?> type) {
    return type.getCanonicalName().substring(rootPackage.length());
  }

  private interface TypeGenerator {
    void generateType(String name);
  }

  private void generateType(final Class<?> type, final TypeGenerator typeGenerator) {
    checkType(type);
    final String jsType = jsType(type);
    final int dot = jsType.lastIndexOf('.');
    final String name = type.getSimpleName();
    if (dot < 0) {
      typeGenerator.generateType(name);
    } else {
      tabsln("export module %s {", jsType.substring(0, dot));
      inc();
      typeGenerator.generateType(name);
      dec();
      tabsln("}");
    }
    println();
  }

  private void generateEnum(final Class<? extends Enum<?>> type) {
    generateType(type, name -> {
      tabsln("export class %s extends yass.Enum {", name);
      inc();
      for (final Enum<?> e : type.getEnumConstants()) {
        tabsln("static %s = new %s(%s, '%s');", e.name(), name, e.ordinal(), e.name());
      }
      tabsln("static TYPE_DESC = yass.enumDesc(%s, %s);", type2id.get(type), name);
      dec();
      tabsln("}");
    });
  }

  private static final Set<Class<?>> ROOT_CLASSES = new HashSet<>(Arrays.asList(
    Object.class, Exception.class, RuntimeException.class, Error.class, Throwable.class
  ));

  private String type(final Type type) {
    if (type instanceof ParameterizedType) {
      final ParameterizedType parameterizedType = (ParameterizedType)type;
      if (parameterizedType.getRawType() == List.class) {
        return type(parameterizedType.getActualTypeArguments()[0]) + "[]";
      } else {
        throw new RuntimeException("unexpected type '" + parameterizedType + '\'');
      }
    } else if ((type == int.class) || (type == Integer.class)) {
      return "number";
    } else if ((type == boolean.class) || (type == Boolean.class)) {
      return "boolean";
    } else if (type == String.class) {
      return "string";
    } else if (type == byte[].class) {
      return "Uint8Array";
    } else if (ROOT_CLASSES.contains(type)) {
      return "any";
    } else if (type == void.class) {
      return "void";
    }
    return jsType((Class<?>)type);
  }

  private String typeDescOwner(final ClassTypeHandler.FieldDesc fieldDesc) {
    final TypeHandler typeHandler = fieldDesc.handler.typeHandler();
    if (TypeDesc.LIST.handler == typeHandler) {
      return "yass.LIST_DESC";
    } else if (JsFastSerializer.BOOLEAN_TYPEDESC.handler == typeHandler) {
      return "yass.BOOLEAN_DESC";
    } else if (JsFastSerializer.INTEGER_TYPEDESC.handler == typeHandler) {
      return "yass.INTEGER_DESC";
    } else if (JsFastSerializer.STRING_TYPEDESC.handler == typeHandler) {
      return "yass.STRING_DESC";
    } else if (JsFastSerializer.BYTES_TYPEDESC.handler == typeHandler) {
      return "yass.BYTES_DESC";
    } else if (typeHandler == null) {
      return "null";
    }
    return jsType(typeHandler.type) + ".TYPE_DESC";
  }

  private void generateClass(final Class<?> type) {
    if (!visitedClasses.add(type)) {
      return;
    }
    Class<?> sc = type.getSuperclass();
    if (ROOT_CLASSES.contains(sc)) {
      sc = null;
    } else {
      generateClass(sc);
    }
    final Class<?> superClass = sc;
    generateType(type, name -> {
      final List<Field> fields = AbstractFastSerializer.ownFields(type);
      Collections.sort(fields, (field1, field2) -> field1.getName().compareTo(field2.getName()));
      tabsln("export class %s extends %s {", name, (superClass == null) ? "yass.Type" : jsType(superClass));
      inc();
      for (final Field field : fields) {
        tabsln("%s: %s;", field.getName(), type(field.getGenericType()));
      }
      final Integer id = type2id.get(type);
      if (id != null) {
        tabs("static TYPE_DESC = yass.classDesc(%s, %s", id, name);
        inc();
        for (final ClassTypeHandler.FieldDesc fieldDesc : ((ClassTypeHandler)id2typeHandler.get(id)).fieldDescs()) {
          println(",");
          tabs("new yass.FieldDesc(%s, '%s', %s)", fieldDesc.id, fieldDesc.handler.field.getName(), typeDescOwner(fieldDesc));
        }
        println();
        dec();
        tabsln(");");
      }
      dec();
      tabsln("}");
    });
  }

  private static final class ServiceDesc {
    final String name;
    final ContractId<?> contractId;
    ServiceDesc(final String name, final ContractId<?> contractId) {
      this.name = name;
      this.contractId = contractId;
    }
  }

  private static List<ServiceDesc> getServiceDescs(final Class<?> services) throws Exception {
    final List<ServiceDesc> serviceDescs = new ArrayList<>();
    for (final Field field : services.getFields()) {
      if (Modifier.isStatic(field.getModifiers()) && (field.getType() == ContractId.class)) {
        serviceDescs.add(new ServiceDesc(field.getName(), (ContractId<?>)field.get(null)));
      }
    }
    Collections.sort(
      serviceDescs,
      (serviceDesc1, serviceDesc2) -> ((Integer)serviceDesc1.contractId.id).compareTo((Integer)serviceDesc2.contractId.id)
    );
    return serviceDescs;
  }

  private static Set<Class<?>> getInterfaces(final Class<?> services) throws Exception {
    return getServiceDescs(services).stream().map(serviceDesc -> serviceDesc.contractId.contract).collect(Collectors.toSet());
  }

  private static Method[] getMethods(final Class<?> type) {
    final Method[] methods = type.getMethods();
    Arrays.sort(methods, (method1, method2) -> method1.getName().compareTo(method2.getName()));
    return methods;
  }

  private void generateInterface(final Class<?> type) {
    checkType(type);
    final Method[] methods = getMethods(type);
    final MethodMapper methodMapper = methodMapperFactory.create(type);
    generateType(type, new TypeGenerator() {
      void generateInterface(final String name, final boolean proxy) {
        tabsln("export interface %s {", name + (proxy ? "_PROXY" : ""));
        inc();
        for (final Method method : methods) {
          tabs("%s(", method.getName());
          boolean first = true;
          for (final Parameter parameter : method.getParameters()) {
            if (!first) {
              print(", ");
            }
            first = false;
            print("%s: %s", parameter.getName(), type(parameter.getParameterizedType()));
          }
          print("): ");
          if (methodMapper.mapMethod(method).oneWay) {
            print("void");
          } else {
            final String type = type(method.getGenericReturnType());
            if (proxy) {
              print("yass.Promise<%s>", type);
            } else {
              print(type);
            }
          }
          println(";");
        }
        dec();
        tabsln("}");
      }
      @Override public void generateType(final String name) {
        generateInterface(name, false);
        generateInterface(name, true);
        tabs("export var %s_MAPPER = new yass.MethodMapper<%s>(", name, name);
        inc();
        boolean first = true;
        for (final Method method : methods) {
          if (!first) {
            print(",");
          }
          first = false;
          println();
          final MethodMapper.Mapping mapping = methodMapper.mapMethod(method);
          tabs("new yass.MethodMapping('%s', %s, %s)", mapping.method.getName(), mapping.id, mapping.oneWay);
        }
        println();
        dec();
        tabsln(");");
      }
    });
  }

  private void generateServices(final Class<?> services) throws Exception {
    tabsln("export module %s {", jsType(services));
    inc();
    for (final ServiceDesc serviceDesc : getServiceDescs(services)) {
      final String name = jsType(serviceDesc.contractId.contract);
      tabsln("export var %s = new yass.ContractId<%s, %s_PROXY>(%s, %s_MAPPER);", serviceDesc.name, name, name, serviceDesc.contractId.id, name);
    }
    dec();
    tabsln("}");
    println();
  }

  /**
   * @param methodMapperFactory Note: You must provide a factory that doesn't allow overloading due to JavaScript restrictions!
   */
  @SuppressWarnings("unchecked")
  public ContractGenerator(
    final Package rootPackage, final JsFastSerializer serializer, final MethodMapper.Factory methodMapperFactory,
    final String yassModulePath, final String contractFilePath, @Nullable final String baseTypesFilePath
  ) throws Exception {
    super(contractFilePath);
    this.rootPackage = rootPackage.getName() + '.';
    this.methodMapperFactory = Check.notNull(methodMapperFactory);
    id2typeHandler = serializer.id2typeHandler();
    for (final Map.Entry<Integer, TypeHandler> entry : id2typeHandler.entrySet()) {
      final int id = entry.getKey();
      if (id >= JsFastSerializer.FIRST_ID) {
        type2id.put(entry.getValue().type, id);
      }
    }
    tabsln("import yass = require('%s');", Check.notNull(yassModulePath));
    println();
    tabsln("export var GENERATED_BY_YASS_VERSION = '%s';", Version.VALUE);
    println();
    if (baseTypesFilePath != null) {
      try (BufferedReader baseTypes = new BufferedReader(new FileReader(baseTypesFilePath))) {
        while (true) {
          final String line = baseTypes.readLine();
          if (line == null) {
            break;
          }
          println(line);
        }
      }
      println();
    }
    for (final Map.Entry<Integer, TypeHandler> entry : id2typeHandler.entrySet()) {
      final Class<?> type = entry.getValue().type;
      if (type.isEnum()) {
        generateEnum((Class<Enum<?>>)type);
      }
    }
    for (final Map.Entry<Integer, TypeHandler> entry : id2typeHandler.entrySet()) {
      final TypeHandler typeHandler = entry.getValue();
      final Class<?> type = typeHandler.type;
      if (typeHandler instanceof ClassTypeHandler) {
        generateClass(type);
      }
    }
    final Class<?> clientServices = Class.forName(this.rootPackage + "ClientServices");
    final Class<?> serverServices = Class.forName(this.rootPackage + "ServerServices");
    final Set<Class<?>> interfaceSet = getInterfaces(clientServices);
    interfaceSet.addAll(getInterfaces(serverServices));
    final List<Class<?>> interfaceList = new ArrayList<>(interfaceSet);
    Collections.sort(interfaceList, (type1, type2) -> type1.getCanonicalName().compareTo(type2.getCanonicalName()));
    interfaceList.forEach(this::generateInterface);
    generateServices(clientServices);
    generateServices(serverServices);
    tabs("export var SERIALIZER = new yass.JsFastSerializer(");
    inc();
    boolean first = true;
    for (final Class<?> type : type2id.keySet()) {
      if (!first) {
        print(",");
      }
      first = false;
      println();
      tabs(jsType(type));
    }
    println();
    dec();
    tabsln(");");
    close();
  }

}
TOP

Related Classes of ch.softappeal.yass.ts.ContractGenerator$ServiceDesc

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.