/**
* Copyright 2015 Santhosh Kumar Tekuri
*
* The JLibs authors license 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.
*/
package jlibs.core.graph;
import jlibs.core.annotation.processing.AnnotationError;
import jlibs.core.annotation.processing.AnnotationProcessor;
import jlibs.core.annotation.processing.Environment;
import jlibs.core.annotation.processing.Printer;
import jlibs.core.graph.sequences.FilteredSequence;
import jlibs.core.graph.sequences.IterableSequence;
import jlibs.core.lang.model.ModelUtil;
import org.kohsuke.MetaInfServices;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import java.io.IOException;
import java.util.*;
import static jlibs.core.annotation.processing.Printer.*;
/**
* @author Santhosh Kumar T
*/
@SupportedAnnotationTypes("jlibs.core.graph.Visitor.Implement")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
@MetaInfServices(javax.annotation.processing.Processor.class)
public class VisitorAnnotationProcessor extends AnnotationProcessor{
private static final String METHOD_NAME = "accept";
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv){
for(TypeElement annotation: annotations){
for(Element elem: roundEnv.getElementsAnnotatedWith(annotation)){
Map<TypeMirror, ExecutableElement> classes = new LinkedHashMap<TypeMirror, ExecutableElement>();
try{
TypeElement c = (TypeElement)elem;
while(c!=null && !c.getQualifiedName().contentEquals(Object.class.getName())){
process(classes, c);
c = ModelUtil.getSuper(c);
}
c = (TypeElement)elem;
Printer pw = null;
try{
pw = Printer.get(c, Visitor.Implement.class, VisitorUtil.FORMAT);
boolean implementing = ModelUtil.isAssignable(elem.asType(), Visitor.class);
boolean isFinal = c.getModifiers().contains(Modifier.FINAL);
if(!isFinal && implementing)
generateExtendingClass(classes, pw);
else
generateDelegatingClass(classes, pw);
}catch(IOException ex){
throw new RuntimeException(ex);
}finally{
if(pw!=null)
pw.close();
}
}catch(AnnotationError error){
error.report();
}
}
}
return true;
}
private boolean accept(ExecutableElement method){
return method.getSimpleName().contentEquals(METHOD_NAME) && method.getParameters().size() == 1;
}
private void process(Map<TypeMirror, ExecutableElement> classes, TypeElement c){
for(ExecutableElement method: ElementFilter.methodsIn(c.getEnclosedElements())){
if(accept(method)){
TypeMirror mirror = method.getParameters().get(0).asType();
if(!classes.containsKey(mirror))
classes.put(mirror, method);
}
}
}
private void generateExtendingClass(Map<TypeMirror, ExecutableElement> classes, Printer printer){
printer.printPackage();
printer.printClassDoc();
printer.printlns(
"@SuppressWarnings(\"unchecked\")",
"public class "+printer.generatedClazz +" extends "+ printer.clazz.getSimpleName()+"{",
PLUS
);
printVisitMethod("", classes, printer);
printer.printlns(
MINUS,
"}"
);
}
private void generateDelegatingClass(Map<TypeMirror, ExecutableElement> classes, Printer printer){
printer.printPackage();
printer.printClassDoc();
printer.printlns(
"public class "+printer.generatedClazz +" implements "+ Visitor.class.getCanonicalName()+"{",
PLUS,
"private final "+printer.clazz.getSimpleName()+" delegate;",
"public "+printer.generatedClazz+"("+printer.clazz.getSimpleName()+" delegate){",
PLUS,
"this.delegate = delegate;",
MINUS,
"}"
);
printer.emptyLine(true);
printVisitMethod("delegate.", classes, printer);
printer.printlns(
MINUS,
"}"
);
}
private void printVisitMethod(String prefix, Map<TypeMirror, ExecutableElement> classes, Printer printer){
printer.println("@Override");
printer.println("public Object visit(Object obj){");
printer.indent++;
List<TypeMirror> list = new ArrayList<TypeMirror>(classes.keySet());
Collections.reverse(list);
boolean addElse = false;
for(TypeMirror mirror: sort(list)){
String type = ModelUtil.toString(mirror, true);
if(addElse)
printer.print("else ");
else
addElse = true;
printer.println("if(obj instanceof "+type+")");
printer.indent++;
if(classes.get(mirror).getReturnType().getKind()!=TypeKind.VOID)
printer.print("return ");
printer.println(prefix+METHOD_NAME+"(("+type+")obj);");
printer.indent--;
}
printer.println();
printer.println("return null;");
printer.indent--;
printer.println("}");
}
public static List<TypeMirror> sort(final Sequence<TypeMirror> classes){
return WalkerUtil.topologicalSort(classes, new Navigator<TypeMirror>(){
@Override
public Sequence<TypeMirror> children(final TypeMirror parent){
return new FilteredSequence<TypeMirror>(classes.copy(), new Filter<TypeMirror>(){
@Override
public boolean select(TypeMirror child){
return Environment.get().getTypeUtils().isSubtype(parent, child);
}
});
}
});
}
public static List<TypeMirror> sort(Collection<TypeMirror> classes){
return sort(new IterableSequence<TypeMirror>(classes));
}
}