package org.vaadin.mideaas.model; import japa.parser.JavaParser; import japa.parser.ParseException; import japa.parser.ast.Comment; import japa.parser.ast.CompilationUnit; import japa.parser.ast.ImportDeclaration; import japa.parser.ast.LineComment; import japa.parser.ast.Node; import japa.parser.ast.PackageDeclaration; import japa.parser.ast.body.BodyDeclaration; import japa.parser.ast.body.ClassOrInterfaceDeclaration; import japa.parser.ast.body.FieldDeclaration; import japa.parser.ast.body.MethodDeclaration; import japa.parser.ast.body.ModifierSet; import japa.parser.ast.body.Parameter; import japa.parser.ast.body.TypeDeclaration; import japa.parser.ast.body.VariableDeclarator; import japa.parser.ast.body.VariableDeclaratorId; import japa.parser.ast.expr.AnnotationExpr; import japa.parser.ast.expr.Expression; import japa.parser.ast.expr.MarkerAnnotationExpr; import japa.parser.ast.expr.MethodCallExpr; import japa.parser.ast.expr.NameExpr; import japa.parser.ast.expr.ObjectCreationExpr; import japa.parser.ast.expr.SingleMemberAnnotationExpr; import japa.parser.ast.expr.StringLiteralExpr; import japa.parser.ast.expr.SuperExpr; import japa.parser.ast.stmt.BlockStmt; import japa.parser.ast.stmt.ExpressionStmt; import japa.parser.ast.stmt.ReturnStmt; import japa.parser.ast.stmt.Statement; import japa.parser.ast.type.ClassOrInterfaceType; import japa.parser.ast.type.ReferenceType; import japa.parser.ast.type.Type; import japa.parser.ast.type.VoidType; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import org.vaadin.aceeditor.ServerSideDocDiff; import org.vaadin.mideaas.editor.DocDiffMediator; /** * * * */ public class ControllerCode { // Latest valid code private CompilationUnit cu; public static ControllerCode createInitial(String projPackage, String className) { CompilationUnit cu = createInitialCu(projPackage); ControllerCode cc = new ControllerCode(cu); cc.createClass(className); // XXX? cc.addAttachSkeleton(); //cc.addOnBecomingVisibleMethod(); return cc; } public ControllerCode(CompilationUnit cu) { this.cu = cu; } public ControllerCode(String code) throws ParseException { this(getCu(code)); } public String getCode() { return cu.toString(); } private static CompilationUnit getCu(InputStream is) throws ParseException { try { return JavaParser.parse(is); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } private static CompilationUnit createInitialCu(String projPackage) { CompilationUnit newCu = new CompilationUnit(); newCu.setPackage(new PackageDeclaration(new NameExpr(projPackage))); return newCu; } private void createClass(String name) { ensureImport("org.vaadin.mideaas.MideaasComponent"); ClassOrInterfaceDeclaration decl = new ClassOrInterfaceDeclaration(ModifierSet.PUBLIC, false, name); decl.setExtends(Collections.singletonList(new ClassOrInterfaceType("MideaasComponent"))); cu.setTypes(Collections.singletonList((TypeDeclaration)decl)); } @SuppressWarnings("unused") private void createClassTouchkit(String name) { ensureImport("org.vaadin.mideaas.MideaasNavigationView"); ClassOrInterfaceDeclaration decl = new ClassOrInterfaceDeclaration(ModifierSet.PUBLIC, false, name); decl.setExtends(Collections.singletonList(new ClassOrInterfaceType("MideaasNavigationView"))); cu.setTypes(Collections.singletonList((TypeDeclaration)decl)); } private void addAttachSkeleton() { List<Parameter> params = Collections.emptyList(); MethodDeclaration method = new MethodDeclaration( ModifierSet.PUBLIC, new VoidType(), "attach", params); AnnotationExpr override = new MarkerAnnotationExpr(new NameExpr("Override")); method.setAnnotations(Collections.singletonList(override)); BlockStmt block = new BlockStmt(); Expression e = new MethodCallExpr(new SuperExpr(), "attach"); List<Statement> sts = Collections.singletonList((Statement)new ExpressionStmt(e)); block.setStmts(sts); method.setBody(block); if (getType().getMembers()==null) { getType().setMembers(new LinkedList<BodyDeclaration>()); } getType().getMembers().add(method); } @SuppressWarnings("unused") private void addOnBecomingVisibleMethod() { List<Parameter> params = Collections.emptyList(); MethodDeclaration method = new MethodDeclaration( ModifierSet.PUBLIC, new VoidType(), "onBecomingVisible", params); AnnotationExpr override = new MarkerAnnotationExpr(new NameExpr("Override")); method.setAnnotations(Collections.singletonList(override)); BlockStmt block = new BlockStmt(); Expression e = new MethodCallExpr(new SuperExpr(), "onBecomingVisible"); List<Statement> sts = Collections.singletonList((Statement)new ExpressionStmt(e)); block.setStmts(sts); method.setBody(block); if (getType().getMembers()==null) { getType().setMembers(new LinkedList<BodyDeclaration>()); } getType().getMembers().add(method); } private void addNotification(MethodDeclaration method, String text) { MethodCallExpr e = new MethodCallExpr(new NameExpr("Notification"), "show"); e.setArgs(Collections.singletonList((Expression)new StringLiteralExpr(text))); Statement statement = new ExpressionStmt(e); addToMethod(method, statement); ensureImport("com.vaadin.ui.Notification"); } private void addToMethod(MethodDeclaration method, Statement statement) { BlockStmt body = method.getBody(); if (body==null) { body = new BlockStmt(); method.setBody(body); } if (body.getStmts()==null) { body.setStmts(new ArrayList<Statement>()); } body.getStmts().add(statement); } private static void setComment(Node node, String text) { LineComment c = new LineComment(text); node.setComment(c); } private static CompilationUnit getCu(String code) throws ParseException { return getCu(new ByteArrayInputStream(code.getBytes())); } // This is needed to update line and col positions?? // TODO: is there a better way? private void reparse() { try { cu = getCu(cu.toString()); } catch (ParseException e) { // shouldn't fail System.err.println("WARNING: error while parsing valid java code?!?"); } } private void addImport(String imp) { if (cu.getImports()==null) { cu.setImports(new LinkedList<ImportDeclaration>()); } ImportDeclaration impDeclr = new ImportDeclaration(new NameExpr(imp), false, false); cu.getImports().add(impDeclr); } private FieldDeclaration getField(String name) { List<BodyDeclaration> members = getType().getMembers(); if (members==null) { return null; } for (BodyDeclaration f : members) { if (f instanceof FieldDeclaration) { for (VariableDeclarator v : ((FieldDeclaration)f).getVariables()) { if (name.equals(v.getId().getName())) { return (FieldDeclaration) f; } } } } return null; } private MethodDeclaration getMethod(String name) { List<BodyDeclaration> members = getType().getMembers(); if (members==null) { return null; } for (BodyDeclaration m : members) { if (m instanceof MethodDeclaration) { if (name.equals(((MethodDeclaration)m).getName())) { return (MethodDeclaration) m; } } } return null; } private boolean fieldExists(String name) { return getField(name) != null; } private boolean methodExists(String name) { return getMethod(name) != null; } public int[] ensureClaraHandlerExists(String id, String className, String comment) { List<BodyDeclaration> members = getType().getMembers(); if (members != null) { for (BodyDeclaration member : members) { if (member instanceof MethodDeclaration) { MethodDeclaration method = (MethodDeclaration) member; if (isClaraHandler(method, id, className)) { if (comment!=null) { addCommentToTheBeginningOfMethod(method, comment); } return getMemberRowCol(method); } } } } MethodDeclaration method = addClaraHandler(id, className); if (comment!=null) { addCommentToTheBeginningOfMethod(method, comment); } reparse(); String name = method.getName(); int[] hmm = getMethodRowCol(name); return hmm; } /** TODO: different kinds of data sources?? */ public void ensureClaraDataSource(String id, String cls, String comment) { MethodDeclaration ds = getClaraDataSourceFor(id); if (ds!=null) { // TODO comment? // TODO: if cls different from ds's? return; } MethodDeclaration method = addClaraDataSourceMethod(id, cls); if (comment!=null) { addCommentToTheBeginningOfMethod(method, comment); } } private void addMember(BodyDeclaration body) { getType().getMembers().add(body); } private MethodDeclaration getClaraDataSourceFor(String id) { List<BodyDeclaration> members = getType().getMembers(); if (members != null) { for (BodyDeclaration member : members) { if (member instanceof MethodDeclaration) { MethodDeclaration method = (MethodDeclaration) member; if (isClaraDataSource(method, id)) { return method; } } } } return null; } private boolean isClaraDataSource(MethodDeclaration method, String id) { // TODO Auto-generated method stub return false; } private static boolean addCommentToTheBeginningOfMethod(MethodDeclaration method, String comment) { if (method.getBody()==null || method.getBody().getStmts()==null || method.getBody().getStmts().isEmpty()) { return false; } setComment(method.getBody().getStmts().get(0), comment); return true; } @SuppressWarnings("unused") private static void setLineComment(BodyDeclaration body, String comment) { Comment jdc = body.getComment(); if (jdc instanceof LineComment) { if (comment==null) { jdc.setComment(null); } else { jdc.setContent(comment); } } else { if (comment!=null) { body.setComment(new LineComment(comment)); } } } private MethodDeclaration addClaraDataSourceMethod(String id, String returnType) { String handlerName = generateDataSourceNameFor(id); Type type = createDataSourceReturnType(returnType); List<Parameter> params = Collections.emptyList(); MethodDeclaration source = new MethodDeclaration( ModifierSet.PUBLIC, type, handlerName, params); source.setBody(new BlockStmt()); source.setAnnotations(createClaraDataSourceAnnotations(id)); setTestMethodBodyFor(source, returnType); addMember(source); return source; } private void setTestMethodBodyFor(MethodDeclaration source, String returnType) { if ("java.lang.String".equals(returnType)) { ensureImport("com.vaadin.data.util.ObjectProperty"); ClassOrInterfaceType cls = new ClassOrInterfaceType("ObjectProperty<String>"); List<Expression> args = Collections.singletonList((Expression)new StringLiteralExpr("Moi")); Expression creation = new ObjectCreationExpr(null, cls, args); ReturnStmt ret = new ReturnStmt(creation); addToMethod(source, ret); } else if ("com.vaadin.data.Container".equals(returnType)) { // TODO ensureImport("com.vaadin.data.util.IndexedContainer"); ClassOrInterfaceType cls = new ClassOrInterfaceType("IndexedContainer"); List<Expression> args = Collections.emptyList(); Expression creation = new ObjectCreationExpr(null, cls, args); ReturnStmt ret = new ReturnStmt(creation); addToMethod(source, ret); } // TODO ... } private Type createDataSourceReturnType(String returnType) { // TODO ... if ("java.lang.String".equals(returnType)) { ensureImport("com.vaadin.data.Property"); return new ClassOrInterfaceType("Property<String>"); } else if ("com.vaadin.data.Container".equals(returnType)) { ensureImport("com.vaadin.data.Container"); return new ClassOrInterfaceType("Container"); } // TODO ... return null; } private MethodDeclaration addClaraHandler(String id, String className) { ensureImport(className); String shortName = className.substring(className.lastIndexOf(".")+1); String handlerName = generateHandlerNameFor(id, shortName); Parameter p = new Parameter(new ClassOrInterfaceType(shortName), new VariableDeclaratorId("event")); MethodDeclaration handler = new MethodDeclaration( ModifierSet.PUBLIC, new VoidType(), handlerName, Collections.singletonList(p)); handler.setBody(new BlockStmt()); handler.setAnnotations(createClaraHandlerAnnotations(id)); addNotification(handler, shortName+" on "+id); addMember(handler); return handler; } private static boolean isClaraHandler(MethodDeclaration method, String id, String className) { if (method.getAnnotations()==null) { return false; } for (AnnotationExpr ann : method.getAnnotations()) { if ("UiHandler".equals(ann.getName().getName())) { if (ann instanceof SingleMemberAnnotationExpr) { SingleMemberAnnotationExpr smae = (SingleMemberAnnotationExpr)ann; if (smae.getMemberValue() instanceof StringLiteralExpr) { String v = ((StringLiteralExpr)smae.getMemberValue()).getValue(); if (id.equals(v)) { return hasOneParamWithType(method, className); } } } } } return false; } private static boolean hasOneParamWithType(MethodDeclaration method, String className) { if (method.getParameters().size()!=1) { return false; } Parameter p = method.getParameters().get(0); // TODO: could check class matching more carefully... if (p.getType() instanceof ClassOrInterfaceType) { ClassOrInterfaceType coit = (ClassOrInterfaceType)p.getType(); return className.endsWith("."+coit.getName()); } else if (p.getType() instanceof ReferenceType) { ReferenceType rt = (ReferenceType)p.getType(); return className.endsWith("."+rt.toString()); // ?? } return false; } public int[] ensureClaraFieldExists(String id, String className) { List<TypeDeclaration> types = cu.getTypes(); for (TypeDeclaration type : types) { List<BodyDeclaration> members = type.getMembers(); if (members==null) { break; } for (BodyDeclaration member : members) { if (member instanceof FieldDeclaration) { FieldDeclaration field = (FieldDeclaration) member; if (isClaraField(field, id, className)) { return getMemberRowCol(field); } } } } FieldDeclaration fd = addClaraField(id, className); reparse(); String name = fd.getVariables().get(0).getId().getName(); int[] hmm = getFieldRowCol(name); return hmm; } private int[] getMethodRowCol(String name) { return getMemberRowCol(getMethod(name)); } private int[] getFieldRowCol(String name) { return getMemberRowCol(getField(name)); } private int[] getMemberRowCol(BodyDeclaration b) { return new int[]{b.getBeginLine(), b.getBeginColumn()-1, b.getEndLine(), b.getEndColumn()-1}; } private TypeDeclaration getType() { return cu.getTypes().get(0); } private FieldDeclaration addClaraField(String id, String className) { ensureImport(className); String fieldName = generateFieldNameLike(id); VariableDeclarator var = new VariableDeclarator( new VariableDeclaratorId(fieldName)); Type type = new ClassOrInterfaceType(className.substring(className.lastIndexOf('.') + 1)); FieldDeclaration field = new FieldDeclaration(ModifierSet.PRIVATE, type, var); field.setAnnotations(createClaraFieldAnnotations(id)); if (getType().getMembers()==null) { getType().setMembers(new LinkedList<BodyDeclaration>()); } getType().getMembers().add(getFirstMethodIndex(), field); return field; } private List<AnnotationExpr> createClaraFieldAnnotations(String id) { LinkedList<AnnotationExpr> anns = new LinkedList<AnnotationExpr>(); ensureImport("org.vaadin.teemu.clara.binder.annotation.UiField"); // ensureImport("com.vaadin.annotations.AutoGenerated"); // anns.add(createAutogeneratedAnnotation()); anns.add(createSimpleAnnotation("UiField", id)); return anns; } private List<AnnotationExpr> createClaraHandlerAnnotations(String id) { LinkedList<AnnotationExpr> anns = new LinkedList<AnnotationExpr>(); ensureImport("org.vaadin.teemu.clara.binder.annotation.UiHandler"); // ensureImport("com.vaadin.annotations.AutoGenerated"); // anns.add(createAutogeneratedAnnotation()); anns.add(createSimpleAnnotation("UiHandler", id)); return anns; } private List<AnnotationExpr> createClaraDataSourceAnnotations(String id) { LinkedList<AnnotationExpr> anns = new LinkedList<AnnotationExpr>(); ensureImport("org.vaadin.teemu.clara.binder.annotation.UiDataSource"); anns.add(createSimpleAnnotation("UiDataSource", id)); return anns; } @SuppressWarnings("unused") private AnnotationExpr createAutogeneratedAnnotation() { return new MarkerAnnotationExpr(new NameExpr("AutoGenerated")); } private AnnotationExpr createSimpleAnnotation(String cls, String id) { return new SingleMemberAnnotationExpr(new NameExpr(cls), new StringLiteralExpr(id)); } private String generateFieldNameLike(String id) { String name = id.replaceAll("[^\\w]", ""); int i = 1; while (fieldExists(name)) { name = name + (++i); } return name; } private String generateHandlerNameFor(String id, String shortName) { String methodName = id.replaceAll("[^\\w]", "") + "Handle" + shortName; int i = 1; String name = methodName; while (methodExists(name)) { name = methodName + (++i); } return name; } private String generateDataSourceNameFor(String id) { String methodName = id.replaceAll("[^\\w]", "") + "DataSource"; int i = 1; String name = methodName; while (methodExists(name)) { name = methodName + (++i); } return name; } public void ensureImport(String className) { if (!isImported(className)) { addImport(className); } } private boolean isImported(String className) { if (cu.getImports()==null) { return false; } for (ImportDeclaration imp : cu.getImports()) { if(importEquals(imp, className)) { return true; } } return false; } private static boolean importEquals(ImportDeclaration imp, String fullClassName) { if (fullClassName.equals(imp.getName().getName())) { return true; } // Don't know why the above isn't enough... // Sometimes imp.getName().getName() is just the short name, sometimes full (??) // Additional check. Not 100% certain. return imp.toString().contains(fullClassName+";"); } private static boolean isClaraField(FieldDeclaration field, String id, String className) { if (field.getAnnotations()==null) { return false; } for (AnnotationExpr ann : field.getAnnotations()) { if ("UiField".equals(ann.getName().getName())) { if (ann instanceof SingleMemberAnnotationExpr) { SingleMemberAnnotationExpr smae = (SingleMemberAnnotationExpr)ann; if (smae.getMemberValue() instanceof StringLiteralExpr) { String v = ((StringLiteralExpr)smae.getMemberValue()).getValue(); if (id.equals(v)) { return true; } } } } } return false; } private int getFirstMethodIndex() { List<BodyDeclaration> ms = getType().getMembers(); if (ms==null) { return 0; // XXX } for (int i=0; i<ms.size(); i++) { if (ms.get(i) instanceof MethodDeclaration) { return i; } } return 0; // XXX } public interface Modifier { public void modify(ControllerCode c); } public static ServerSideDocDiff getDiffAfterModify(String initial, Modifier modifier) throws ParseException { ControllerCode c = new ControllerCode(initial); String base = c.getCode(); modifier.modify(c); return ServerSideDocDiff.diff(base, c.getCode()); } }