org.jooby.internal.spec.RouteCollector.java Source code

Java tutorial

Introduction

Here is the source code for org.jooby.internal.spec.RouteCollector.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
/**
 * This copy of Woodstox XML processor is licensed under the
 * Apache (Software) License, version 2.0 ("the License").
 * See the License for details about distribution rights, and the
 * specific rights regarding derivate works.
 *
 * You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/
 *
 * A copy is also included in the downloadable source code package
 * containing Woodstox, in file "ASL2.0", under the same directory
 * as this file.
 */
package org.jooby.internal.spec;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;

import org.jooby.Route;

import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.ClassExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.LambdaExpr;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.visitor.GenericVisitorAdapter;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.google.common.collect.Maps;

public class RouteCollector extends VoidVisitorAdapter<Context> {

    private List<Map.Entry<Object, Node>> nodes = new ArrayList<>();

    private boolean script;

    private Consumer<String> owners;

    private Map<String, Node> vars = new HashMap<>();

    private RouteCollector(final boolean script, final Consumer<String> owners) {
        this.script = script;
        this.owners = owners;
    }

    public RouteCollector(final Consumer<String> owners) {
        this(true, owners);
    }

    public RouteCollector() {
        this(true, owner -> {
        });
    }

    public List<Entry<Object, Node>> accept(final Node node, final Context ctx) {
        node.accept(this, ctx);
        return nodes;
    }

    @Override
    public void visit(final VariableDeclarator n, final Context ctx) {
        vars.put(n.getId().getName(), n.getInit());
    }

    @Override
    public void visit(final MethodDeclaration m, final Context ctx) {
        if (!script) {
            boolean mvc = m.getAnnotations().stream().map(it -> it.getName().getName())
                    .filter(Route.METHODS::contains).findFirst().isPresent();
            if (mvc) {
                nodes.add(Maps.immutableEntry(m, m.getBody()));
            }
        }
    }

    @Override
    public void visit(final MethodCallExpr n, final Context ctx) {
        if (script) {
            Type mvcClass = useMvc(n, ctx);
            if (mvcClass != null) {
                mvcRoutes(n, mvcClass, ctx);
            } else {
                Type appType = importApp(n, ctx);
                if (appType != null) {
                    importRoutes(appType, ctx);
                } else {
                    List<MethodCallExpr> routes = routes(n, ctx);
                    for (MethodCallExpr route : routes) {
                        Optional<LambdaExpr> lambda = route.getArgs().stream().map(it -> handler(it, ctx))
                                .filter(it -> it != null).findFirst();
                        this.nodes.add(Maps.immutableEntry(route, lambda.get()));
                    }
                }
            }
        }
    }

    private void importRoutes(final Type type, final Context ctx) {
        // compiled or parse?
        List<Map.Entry<Object, Node>> result = ctx.parseSpec(type).map(specs -> {
            List<Map.Entry<Object, Node>> nodes = new ArrayList<>();
            specs.forEach(spec -> nodes.add(Maps.immutableEntry(spec, null)));
            return nodes;
        }).orElseGet(() -> ctx.parse(type).map(unit -> new RouteCollector(true, owners).accept(unit, ctx))
                .orElse(Collections.emptyList()));
        owners.accept(type.getTypeName());
        this.nodes.addAll(result);
    }

    @SuppressWarnings("rawtypes")
    private void mvcRoutes(final Node n, final Type type, final Context ctx) {
        if (type instanceof Class) {
            Class sclass = ((Class) type).getSuperclass();
            if (sclass != Object.class) {
                mvcRoutes(n, sclass, ctx);
            }
        }
        List<Map.Entry<Object, Node>> result = ctx.parse(type)
                .map(unit -> new RouteCollector(false, owners).accept(unit, ctx)).orElse(Collections.emptyList());
        owners.accept(type.getTypeName());
        nodes.addAll(result);
    }

    private Type useMvc(final MethodCallExpr n, final Context ctx) {
        if ("use".equals(n.getName())) {
            List<Expression> args = n.getArgs();
            if (args.size() == 1) {
                Expression arg = args.get(0);
                if (arg instanceof ClassExpr) {
                    return arg.accept(new TypeCollector(), ctx);
                }
            }
        }
        return null;
    }

    private Type importApp(final MethodCallExpr n, final Context ctx) {
        Function<Expression, Type> type = expr -> {
            if (expr instanceof ObjectCreationExpr) {
                ClassOrInterfaceType t = ((ObjectCreationExpr) expr).getType();
                Optional<java.lang.reflect.Type> resolved = ctx.resolveType(n, t.toStringWithoutComments());
                if (resolved.isPresent()) {
                    Type c = resolved.get();
                    if (isJooby(c)) {
                        return c;
                    }
                }
            }
            return null;
        };

        if ("use".equals(n.getName())) {
            List<Expression> args = n.getArgs();
            if (args.size() == 2) {
                return type.apply(args.get(1));
            }
            if (args.size() == 1) {
                return type.apply(args.get(0));

            }
        }
        return null;
    }

    private boolean isJooby(final Type type) {
        Type t = type;
        while (t instanceof Class) {
            @SuppressWarnings("rawtypes")
            Class c = (Class) t;
            if (c.getTypeName().equals("org.jooby.Jooby")) {
                return true;
            }
            t = c.getSuperclass();
        }
        return false;
    }

    private List<MethodCallExpr> routes(final MethodCallExpr expr, final Context ctx) {
        LinkedList<MethodCallExpr> expressions = new LinkedList<>();

        Expression it = expr;
        while (it instanceof MethodCallExpr) {
            MethodCallExpr local = (MethodCallExpr) it;
            String name = local.getName();
            int n = 0;
            if (Route.METHODS.contains(name.toUpperCase())) {
                n = route(local, ctx);
            } else if (name.equals("use")) {
                n = route(local, ctx);
            } else if (name.equals("all") && AST.scopeOf(local).getName().equals("use")) {
                n = route(local, ctx);
            }
            while (n > 0) {
                expressions.addFirst(local);
                n -= 1;
            }
            it = local.getScope();
        }
        return expressions;
    }

    private int route(final MethodCallExpr expr, final Context ctx) {
        List<Expression> args = expr.getArgs();
        if (args.size() == 1) {
            if (handler(args.get(0), ctx) != null) {
                return 1;
            }
        }
        // method(path, [path1, path2], handler());
        if (args.size() < 5) {
            // method(path, lambda)
            Set<Type> types = new LinkedHashSet<>();
            for (int i = 0; i < args.size() - 1; i++) {
                types.add(args.get(i).accept(new TypeCollector(), ctx));
            }
            boolean str = types.size() == 1 && types.contains(String.class);
            if (str && handler(args.get(args.size() - 1), ctx) != null) {
                return args.size() - 1;
            }
        }
        return 0;
    }

    private LambdaExpr handler(final Expression expr, final Context ctx) {
        Node node = vars.getOrDefault(expr.toStringWithoutComments(), expr);
        return node.accept(new GenericVisitorAdapter<LambdaExpr, Context>() {
            @Override
            public LambdaExpr visit(final LambdaExpr n, final Context ctx) {
                return n;
            }
        }, ctx);
    }

}