package scenic3.apt.processor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.declaration.AnnotationMirror;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;
import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.declaration.ExecutableDeclaration;
import com.sun.mirror.declaration.MethodDeclaration;
import com.sun.mirror.declaration.ParameterDeclaration;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.declaration.AnnotationValue;
import com.sun.mirror.util.DeclarationFilter;
import com.sun.mirror.util.SimpleDeclarationVisitor;
import java.util.List;
import scenic3.ActionPath;
import scenic3.apt.AnnotationConstants;
import scenic3.apt.Argument;
import scenic3.apt.ScenicControllerDesc;
import scenic3.apt.ScenicControllerGenerator;
import scenic3.apt.UnsupportedArgumentException;
import scenic3.apt.UrlMatcherDesc;
import scenic3.apt.UrlMatcherGenerator;
import scenic3.util.PathUtil;
/**
* {@link AnnotationProcessor} for Scenic3
* @author shuji.w6e
* @since 0.1.0
*/
public class Scenic3Processor implements AnnotationProcessor {
private final AnnotationProcessorEnvironment env;
private final Set<AnnotationTypeDeclaration> annotationTypeDeclarations;
/** Slim3 root package. */
protected final String rootPackage;
protected final EnvSupport support;
private final AnnotationTypeDeclaration pageDecl;
Scenic3Processor(Set<AnnotationTypeDeclaration> annotationTypeDeclarations, AnnotationProcessorEnvironment env) {
this.annotationTypeDeclarations = annotationTypeDeclarations;
this.env = env;
this.support = new EnvSupport(env);
String slim3Root = this.env.getOptions().get("slim3.rootPackage");
// BUG? apt-maven-plugin
// http://jira.codehaus.org/browse/MOJO-1319
if (slim3Root == null) {
for (Entry<String, String> entry : this.env.getOptions().entrySet()) {
String key = entry.getKey(); // key:
// -Aslim3.rootPackage=xxx.xxxx,
// value: null
if (key.startsWith("-Aslim3.rootPackage")) {
slim3Root = key.substring(key.indexOf('=') + 1);
break;
}
}
}
// --- END BUG?
rootPackage = slim3Root;
if (rootPackage == null) {
support.messager.printError("slim3rootPackage is not set. -Aslim3.rootPackage=xxx.xxx");
}
// declaration of annotation
pageDecl = (AnnotationTypeDeclaration) env.getTypeDeclaration(AnnotationConstants.PAGE);
}
// process each @Page
@Override
public void process() {
for (ClassDeclaration classDecl : DeclarationFilter.getFilter(ClassDeclaration.class).filter(
env.getDeclarationsAnnotatedWith(pageDecl), ClassDeclaration.class)) {
Visitor visitor = new Visitor();
classDecl.accept(visitor);
for (MethodDeclaration methodDecl : classDecl.getMethods()) {
methodDecl.accept(visitor);
}
if (visitor.patterns.isEmpty() && visitor.defaultPattern == null) continue;
String packageName = rootPackage + ".controller.matcher";
String className = classDecl.getSimpleName() + "Matcher";
UrlMatcherDesc desc = new UrlMatcherDesc(packageName, className, visitor.pageClassName, visitor.pagePath, visitor.patterns, visitor.defaultPattern);
try {
PrintWriter writer = env.getFiler().createSourceFile(packageName + "." + className);
new UrlMatcherGenerator(desc, writer).generate();
} catch (Throwable e) {
support.messager.printError(e.getMessage());
}
}
}
class Visitor extends SimpleDeclarationVisitor {
String pageClassName;
String pagePath;
SortedMap<ActionPath, String> patterns = new TreeMap<ActionPath, String>();
String defaultPattern = null;
// visit class declaration
// and read @Page'value to set pagePath
@Override
public void visitTypeDeclaration(TypeDeclaration d) {
this.pageClassName = d.getQualifiedName();
for (AnnotationMirror mirror : d.getAnnotationMirrors()) {
if (support.isTypeOf(mirror, AnnotationConstants.PAGE)) {
this.pagePath = support.getValue(mirror, "value");
if (!PathUtil.startsWithSlash(pagePath)) {
support.messager.printError("@Page do *not* have value start with a slash.");
throw new IllegalStateException("@Page do *not* have value start with a slash.");
}
return;
}
}
}
@Override
public void visitExecutableDeclaration(ExecutableDeclaration d) {
if (d.getAnnotationMirrors().isEmpty()) return;
try {
boolean hasDefault = false;
String actionPath = null;
String method = null;
for (AnnotationMirror mirror : d.getAnnotationMirrors()) {
if (support.isTypeOf(mirror, AnnotationConstants.DEFAULT)) {
hasDefault = true;
} else if (support.isTypeOf(mirror, AnnotationConstants.ACTION_PATH)) {
actionPath = support.getValue(mirror, "value");
if (PathUtil.startsWithSlash(actionPath)) {
support.messager.printError("@ActionPath start with slash.");
throw new IllegalStateException("@ActionPath start with slash.");
}
} else if (support.isTypeOf(mirror, AnnotationConstants.GET)) {
if (method != null) {
support.messager.printError("@GET can't set with @" + method);
throw new IllegalStateException("@GET can't set with @" + method);
}
method = "GET";
} else if (support.isTypeOf(mirror, AnnotationConstants.POST)) {
if (method != null) {
support.messager.printError("@POST can't set with @" + method);
throw new IllegalStateException("@POST can't set with @" + method);
}
method = "POST";
} else if (support.isTypeOf(mirror, AnnotationConstants.PUT)) {
if (method != null) {
support.messager.printError("@PUT can't set with @" + method);
throw new IllegalStateException("@PUT can't set with @" + method);
}
method = "PUT";
} else if (support.isTypeOf(mirror, AnnotationConstants.DELETE)) {
if (method != null) {
support.messager.printError("@DELETE can't set with @" + method);
throw new IllegalStateException("@DELETE can't set with @" + method);
}
method = "DELETE";
}
}
if (hasDefault && actionPath != null) {
support.messager.printWarning("Has both @Default and @ActionPath. :");
return;
} else if (!hasDefault && actionPath == null) {
// not target method
return;
}
if (hasDefault) {
defaultPattern = generate(d, null, method).qName;
} else if (actionPath != null) {
patterns.put(new ActionPath(actionPath, method), generate(d, actionPath, method).qName);
}
} catch (IOException e) {
support.printError(e.getMessage());
} catch (UnsupportedArgumentException e) {
support.printError(e.getMessage());
}
}
protected ScenicControllerDesc generate(ExecutableDeclaration d, String actionPath, String method)
throws UnsupportedArgumentException, IOException {
ArrayList<Argument> args = new ArrayList<Argument>();
OUTER_LOOP: for (ParameterDeclaration param : d.getParameters()) {
String name = param.getSimpleName(); // name
String type = param.getType().toString(); // type
// annotation param
for (AnnotationMirror mirror : param.getAnnotationMirrors()) {
if (support.isTypeOf(mirror, AnnotationConstants.REQUEST_PARAM)) {
String p = support.getValue(mirror, "value");
if (support.containsValue(mirror, "defaultValue")) {
// Object obj = support.getValue(mirror, "defaultValue");
// throw new IllegalStateException("obj: " + obj.getClass());
Collection<AnnotationValue> dvs = support.getValue(mirror, "defaultValue");
List<String> dvList = new ArrayList<String>();
for (AnnotationValue v : dvs) {
dvList.add((String)v.getValue());
}
String[] dvArray = dvs.isEmpty() ? null : dvList.toArray(new String[0]);
args.add(Argument.paramsInstance(type, name, p, dvArray));
} else {
args.add(Argument.paramInstance(type, name, p, null));
}
continue OUTER_LOOP;
} else if (support.isTypeOf(mirror, AnnotationConstants.VAR)) {
String v = support.getValue(mirror, "value");
args.add(Argument.varInstance(type, name, v));
continue OUTER_LOOP;
}
}
// simple param
args.add(Argument.simpleInstance(type, name));
}
ScenicControllerDesc desc = new ScenicControllerDesc(pageClassName, rootPackage, pagePath, actionPath,
d.getSimpleName(), method, args.toArray(new Argument[args.size()]));
PrintWriter writer = env.getFiler().createSourceFile(desc.qName);
ScenicControllerGenerator generator = new ScenicControllerGenerator(desc, writer);
generator.generate();
return desc;
}
}
}