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

org.coodex.concrete.apitools.AbstractAngularRenderer Maven / Gradle / Ivy

There is a newer version: 0.5.3-RC1
Show newest version
/*
 * Copyright (c) 2018 coodex.org ([email protected])
 *
 * 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.
 */

package org.coodex.concrete.apitools;

import org.coodex.concrete.apitools.jaxrs.angular.meta.*;
import org.coodex.concrete.common.modules.AbstractModule;
import org.coodex.concrete.common.modules.AbstractParam;
import org.coodex.concrete.common.modules.AbstractUnit;
import org.coodex.util.Common;

import java.io.IOException;
import java.lang.reflect.*;
import java.util.*;

import static org.coodex.concrete.common.ConcreteHelper.isConcreteService;
import static org.coodex.util.GenericTypeHelper.solveFromType;

public abstract class AbstractAngularRenderer, U extends AbstractUnit>
        extends AbstractRenderer {

    protected static final ThreadLocal, TSClass>>> CLASSES = new ThreadLocal<>();
    private static final Class[] NUMBERS = new Class[]{
            byte.class, int.class, short.class, long.class, float.class, double.class
    };

    /// TODO: 处理方式不妥。如果Service method定义变换顺序后,会导致基于原版本的code全部出问题
    @SuppressWarnings("WeakerAccess")
    protected String getMethodName(String name, Set methods) {
        String methodName = name;
        int prefix = 0;
        while (methods.contains(methodName)) {
            methodName = name + prefix++;
        }
        methods.add(methodName);
        return methodName;
    }

    protected abstract String getModuleType();

    private Map, TSClass>> getClasses() {
        return CLASSES.get();
    }

    protected String getModuleName(String moduleName) {
        return moduleName.charAt(0) == '@' ? moduleName : ("@" + moduleName);
    }

    private String getContextPath(String key) {
        StringBuilder builder = new StringBuilder();
        for (char ch : key.toCharArray()) {
            if (ch == '/') builder.append("../");
        }
        return builder.toString();
    }

    protected void packages(String contextPath) throws IOException {
        Map> services = new HashMap<>();
        Set providers = new HashSet<>();
        Set packages = new HashSet<>();

        for (String key : getClasses().keySet()) {
            packages.add(key);
            Map, TSClass> map = getClasses().get(key);
            Map toWrite = new HashMap<>();
            toWrite.put("contextPath", getContextPath(key));
            Set> classSet = new HashSet<>();
            for (Class clz : map.keySet()) {
                //ConcreteService.class.isAssignableFrom(clz)
                if (isConcreteService(clz)) {
                    toWrite.put("includeServices", Boolean.TRUE);
                    providers.add(map.get(clz).getClassName());
                    Set set = services.containsKey(key) ? services.get(key) : new HashSet<>();
                    set.add(map.get(clz).getClassName());
                    services.put(key, set);
                }
                classSet.addAll(map.get(clz).getImports());
            }

            Map imports = new HashMap<>();
            for (Class clz : classSet) {
                String packageName = getPackageKey(clz);
                if (key.equals(packageName)) continue;
                TSImport importSet = imports.get(packageName);
                if (importSet == null) {
                    importSet = new TSImport();
                    importSet.setPackageName(packageName);
                    imports.put(packageName, importSet);
                }
                importSet.getClasses().add(clz.getSimpleName());
            }
            toWrite.put("imports", imports.values());

            toWrite.put("classes", sort(map));
            writeTo(contextPath + key + ".ts",
                    "tspackage.ftl",
                    toWrite);
        }
        Map toWrite = new HashMap<>();
        toWrite.put("services", services);
        toWrite.put("providers", providers);
        toWrite.put("packages", packages);
        toWrite.put("moduleType", getModuleType());
        writeTo(contextPath + "Concrete" + getModuleType() + "Module.ts", "concrete.ftl", toWrite);
    }

    private String getPackageKey(Class clz) {
        return clz.getPackage().getName().replace('.', '/');
    }

    private Collection sort(Map, TSClass> classes) {
        List ordered = new ArrayList<>();
        Map, TSClass> cache = new HashMap<>(classes);
        while (cache.keySet().size() > 0) {
            Class[] keys = cache.keySet().toArray(new Class[0]);
            for (Class key : keys) {
                TSClass tsClass = cache.get(key);
                if (noDep(tsClass, cache)) {
                    cache.remove(key);
                    ordered.add(tsClass);
                }
            }
        }
        return ordered;
    }

    private boolean noDep(TSClass tsClass, Map, TSClass> cache) {
        if (tsClass instanceof TSPojo) {
            return cache.get(((TSPojo) tsClass).getSuperType()) == null;
        }
        for (Class clz : tsClass.getImports()) {
            if (cache.get(clz) != null) return false;
        }
        return true;
    }

    protected void process(String moduleName, AbstractModule module) {
        Class clz = module.getInterfaceClass();
        Map, TSClass> moduleMap = getTSClassMap(clz);
//        if (moduleMap == null) return;
        TSModule tsModule = new TSModule(clz);
        tsModule.setBelong(moduleName);

        Set methods = new HashSet<>();
        for (U unit : module.getUnits()) {

            TSMethod method = new TSMethod();

            method.setName(getMethodName(unit.getMethod().getName(), methods));
            method.setHttpMethod(unit.getInvokeType());
            method.setReturnType(getClassType(unit.getGenericReturnType(), tsModule, clz));
            method.setMethodPath(getMethodPath(module, unit));

            method.setBody(getBody(unit));

            method.setParams(getParams(unit, tsModule));

            tsModule.getMethods().add(method);
        }
        moduleMap.put(clz, tsModule);
    }

    protected abstract String getMethodPath(AbstractModule module, U unit);

    private List getParams(U unit, TSClass clz) {
        List fieldList = new ArrayList<>();
        for (int i = 0; i < unit.getParameters().length; i++) {
            AbstractParam param = unit.getParameters()[i];
            TSParam field = new TSParam();
            field.setName(param.getName());
            field.setType(getClassType(param.getGenericType(), clz, unit.getDeclaringModule().getInterfaceClass()));
            fieldList.add(field);
        }
        return fieldList;
    }

    private Map, TSClass> getTSClassMap(Class clz) {
        Map, TSClass>> classes = getClasses();
        String packageName = clz.getPackage().getName().replace('.', '/');
//        Map moduleMap = classes.get(packageName);
//        if (moduleMap == null) {
//            moduleMap = new HashMap<>();
//            classes.put(packageName, moduleMap);
//        }
//        return moduleMap;
        return classes.computeIfAbsent(packageName, k -> new HashMap<>());
    }

    private String getClassType(Type type, TSClass clz, Class contextClass) {
        if (type instanceof Class) {
            Class c = (Class) type;
            if (c.isArray()) {
                return getClassType(c.getComponentType(), clz, contextClass) + "[]";
            } else
                return getClassType(c, clz);
        } else if (type instanceof ParameterizedType) {
            return getParameterizedType(clz, (ParameterizedType) type, contextClass);
        } else if (type instanceof GenericArrayType) {
            return getClassType(((GenericArrayType) type).getGenericComponentType(), clz, contextClass) + "[]";
        } else if (type instanceof TypeVariable) {
            if (contextClass != null) {
                return getClassType(solveFromType((TypeVariable) type, contextClass), clz, null);
            } else
                return ((TypeVariable) type).getName();
        } else {
            throw new RuntimeException("unknown type: " + type);
        }
    }

    private String getParameterizedType(TSClass clz, ParameterizedType pt, Class contextClass) {
        Class rawType = (Class) pt.getRawType();
        if (Collection.class.isAssignableFrom(rawType)) {
            return getClassType(pt.getActualTypeArguments()[0], clz, contextClass) + "[]";
        } else if (Map.class.isAssignableFrom(rawType)) {
            return String.format("Map<%s, %s>",
                    getClassType(pt.getActualTypeArguments()[0], clz, contextClass),
                    getClassType(pt.getActualTypeArguments()[1], clz, contextClass));
        } else {
            StringBuilder builder = new StringBuilder();
            builder.append(getClassType(rawType, clz))
                    .append("<");
            boolean isFirst = true;
            for (Type t : pt.getActualTypeArguments()) {
                if (!isFirst) builder.append(", ");
                builder.append(getClassType(t, clz, contextClass));
                isFirst = false;
            }
            builder.append(">");
            return builder.toString();
        }
    }

    private String getClassType(Class c, TSClass clz) {
        if (void.class.equals(c) || Void.class.equals(c)) {
            return "void";
        } else if (boolean.class.equals(c) || Boolean.class.equals(c)) {
            return "boolean";
        } else if (Common.inArray(c, NUMBERS) || Number.class.isAssignableFrom(c)) {
            return "number";
        } else if (char.class.equals(c) || Character.class.equals(c) || CharSequence.class.isAssignableFrom(c)) {
            return "string";
        } else if (Collection.class.isAssignableFrom(c)) {
            return "any[]";
        } else if (Map.class.isAssignableFrom(c)) {
            return "Map";
        } else if (Object.class.equals(c)) {
            return "any";
        } else {
            clz.getImports().add(c);
            return getTSPojo(c).getClassName();
        }
    }

    private TSPojo getTSPojo(Class c) {
        // 处理过的不管
        Map, TSClass> map = getTSClassMap(c);
        if (map.containsKey(c))
            return (TSPojo) map.get(c);

        TSPojo pojo = new TSPojo(c);
        map.put(c, pojo);

        if (Object.class.equals(c)) {
            return pojo;
        }

        if (!Object.class.equals(c.getGenericSuperclass()))
            pojo.setSuperClass(getClassType(c.getGenericSuperclass(), pojo, null));

        for (Field field : c.getDeclaredFields()) {
            int mod = field.getModifiers();
            if (!Modifier.isStatic(mod) && !Modifier.isTransient(mod)) {
                TSField tsField = new TSField();
                tsField.setName(field.getName());
                tsField.setType(getClassType(field.getGenericType(), pojo, null));
                pojo.getFields().add(tsField);
            }
        }

        while (!Object.class.equals(c.getSuperclass())) {
            c = c.getSuperclass();
            getTSPojo(c);
        }
        return pojo;
    }


    protected abstract String getBody(U unit);
}