org.jooby.internal.spec.RouteCollector Maven / Gradle / Ivy
/**
* 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 {
private List> nodes = new ArrayList<>();
private boolean script;
private Consumer owners;
private Map vars = new HashMap<>();
private RouteCollector(final boolean script, final Consumer owners) {
this.script = script;
this.owners = owners;
}
public RouteCollector(final Consumer owners) {
this(true, owners);
}
public RouteCollector() {
this(true, owner -> {
});
}
public List> 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 routes = routes(n, ctx);
for (MethodCallExpr route : routes) {
Optional 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> result = ctx.parseSpec(type).map(specs -> {
List> 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> 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 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 type = expr -> {
if (expr instanceof ObjectCreationExpr) {
ClassOrInterfaceType t = ((ObjectCreationExpr) expr).getType();
Optional 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 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 routes(final MethodCallExpr expr, final Context ctx) {
LinkedList 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 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 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() {
@Override
public LambdaExpr visit(final LambdaExpr n, final Context ctx) {
return n;
}
}, ctx);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy