package net.jangaroo.jooc.backend; import net.jangaroo.jooc.model.ActionScriptModel; import net.jangaroo.jooc.model.AnnotatedModel; import net.jangaroo.jooc.model.AnnotationModel; import net.jangaroo.jooc.model.AnnotationPropertyModel; import net.jangaroo.jooc.model.ClassModel; import net.jangaroo.jooc.model.CompilationUnitModel; import net.jangaroo.jooc.model.FieldModel; import net.jangaroo.jooc.model.MemberModel; import net.jangaroo.jooc.model.MethodModel; import net.jangaroo.jooc.model.MethodType; import net.jangaroo.jooc.model.ModelVisitor; import net.jangaroo.jooc.model.NamespaceModel; import net.jangaroo.jooc.model.NamespacedModel; import net.jangaroo.jooc.model.ParamModel; import net.jangaroo.jooc.model.PropertyModel; import net.jangaroo.jooc.model.ReturnModel; import net.jangaroo.jooc.model.TypedModel; import net.jangaroo.jooc.model.ValuedModel; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * A ClassModel visitor that generates ActionScript (API) code. */ public class ActionScriptCodeGeneratingModelVisitor implements ModelVisitor { private final PrintWriter output; private CompilationUnitModel compilationUnitModel; private boolean skipAsDoc; private String indent = ""; public ActionScriptCodeGeneratingModelVisitor(Writer writer) { output = writer instanceof PrintWriter ? (PrintWriter)writer : new PrintWriter(writer); } public ActionScriptCodeGeneratingModelVisitor(Writer writer, boolean skipAsDoc) { this(writer); this.skipAsDoc = skipAsDoc; } public void setCompilationUnitModel(CompilationUnitModel compilationUnitModel) { this.compilationUnitModel = compilationUnitModel; } @Override public void visitCompilationUnit(CompilationUnitModel compilationUnitModel) { setCompilationUnitModel(compilationUnitModel); output.printf("package %s {%n", compilationUnitModel.getPackage()); indent = ""; for (String anImport : compilationUnitModel.getImports()) { output.printf("import %s;%n", anImport); } output.println(); compilationUnitModel.getPrimaryDeclaration().visit(this); indent = ""; output.print("}"); output.close(); } @Override public void visitClass(ClassModel classModel) { visitAnnotations(classModel); printAsdoc(classModel.getAsdoc()); printToken(classModel.getNamespace()); printTokenIf(classModel.isFinal(), "final"); printTokenIf(classModel.isDynamic(), "dynamic"); printToken(classModel.isInterface(), "interface", "class"); printToken(classModel.getName()); if (!classModel.isInterface() && !isEmpty(classModel.getSuperclass())) { output.printf("extends %s ", classModel.getSuperclass()); } if (!classModel.getInterfaces().isEmpty()) { printToken(classModel.isInterface(), "extends", "implements"); List<String> tokens = classModel.getInterfaces(); output.print(tokens.get(0)); for (String token : tokens.subList(1, tokens.size())) { output.print(", "); output.print(token); } output.print(" "); } output.print("{"); indent = " "; for (MemberModel member : classModel.getMembers()) { output.println(); member.visit(this); } indent = ""; output.println("}"); } @Override public void visitNamespace(NamespaceModel namespaceModel) { visitAnnotations(namespaceModel); printAsdoc(namespaceModel.getAsdoc()); printToken(namespaceModel.getNamespace()); printToken("namespace"); output.print(namespaceModel.getName()); generateValue(namespaceModel); output.println(";"); } private void visitAnnotations(AnnotatedModel annotatedModel) { for (AnnotationModel annotation : annotatedModel.getAnnotations()) { annotation.visit(this); } } private void printParameterList(List<? extends ActionScriptModel> models) { output.print("("); boolean first = true; for (ActionScriptModel model : models) { if (first) { first = false; } else { output.print(", "); } model.visit(this); } output.print(")"); } private void printCommaSeparatedList(List<String> tokens) { output.print(tokens.get(0)); for (String token : tokens.subList(1, tokens.size())) { output.print(", "); output.print(token); } } private void printTokenIf(boolean flag, String token) { if (flag) { printToken(token); } } private void printToken(boolean flag, String trueToken, String falseToken) { printToken(flag ? trueToken : falseToken); } private void printToken(String token) { output.printf("%s ", token); } private void indent() { output.print(indent); } public void flush() { output.flush(); } @Override public void visitField(FieldModel fieldModel) { visitAnnotations(fieldModel); printAsdoc(fieldModel.getAsdoc()); indent(); printToken(fieldModel.getNamespace()); printTokenIf(fieldModel.isStatic(), "static"); printToken(fieldModel.isConst(), "const", "var"); output.print(fieldModel.getName()); generateType(fieldModel); generateValue(fieldModel); output.println(";"); } @Override public void visitProperty(PropertyModel propertyModel) { throw new IllegalStateException("PropertyModel should not be visited by code generator."); } private static List<String> PARAM_SUPPRESSING_ASDOC_TAGS = Arrays.asList("@inheritDoc", "@private"); @Override public void visitMethod(MethodModel methodModel) { visitAnnotations(methodModel); StringBuilder asdoc = new StringBuilder(); if (methodModel.getAsdoc() != null) { asdoc.append(methodModel.getAsdoc()); } if (!PARAM_SUPPRESSING_ASDOC_TAGS.contains(asdoc.toString())) { for (ParamModel paramModel : methodModel.getParams()) { if (!isEmpty(paramModel.getAsdoc())) { asdoc.append("\n @param ").append(paramModel.getName()).append(" ").append(paramModel.getAsdoc()); } } String returnAsDoc = methodModel.getReturnModel().getAsdoc(); if (!isEmpty(returnAsDoc)) { asdoc.append("\n @return ").append(returnAsDoc); } } printAsdoc(asdoc.toString()); indent(); printTokenIf(methodModel.isOverride(), "override"); String methodBody = methodModel.getBody(); if (!isPrimaryDeclarationAnInterface()) { printToken(methodModel.getNamespace()); printTokenIf(methodModel.isStatic(), "static"); printTokenIf(methodModel.isFinal(), "final"); printTokenIf(methodBody == null, "native"); } printToken("function"); if (methodModel.getMethodType() != null) { printToken(methodModel.getMethodType().toString()); } output.print(methodModel.getName()); printParameterList(methodModel.getParams()); methodModel.getReturnModel().visit(this); if (methodBody != null) { output.printf(" {%n %s%n }%n", methodModel.getBody()); } else { output.println(";"); } } @Override public void visitParam(ParamModel paramModel) { if (paramModel.isRest()) { output.print("..."); } output.print(paramModel.getName()); generateType(paramModel); if (!paramModel.isRest()) { generateValue(paramModel); } } @Override public void visitReturn(ReturnModel returnModel) { generateType(returnModel); } @Override public void visitAnnotation(AnnotationModel annotationModel) { printAsdoc(annotationModel.getAsdoc()); indent(); output.print("[" + annotationModel.getName()); if (!annotationModel.getProperties().isEmpty()) { printParameterList(annotationModel.getProperties()); } output.println("]"); } @Override public void visitAnnotationProperty(AnnotationPropertyModel annotationPropertyModel) { if (isEmpty(annotationPropertyModel.getName())) { output.print(annotationPropertyModel.getValue()); } else if (isEmpty(annotationPropertyModel.getValue())) { output.print(annotationPropertyModel.getName()); } else { output.printf("%s = %s", annotationPropertyModel.getName(), annotationPropertyModel.getValue()); } } private void printAsdoc(String asdoc) { if (!skipAsDoc && asdoc != null && asdoc.trim().length() > 0) { indent(); output.println("/**"); indent(); output.println(" * " + asdoc); indent(); output.println(" */"); } } private boolean isPrimaryDeclarationAnInterface() { return compilationUnitModel.getPrimaryDeclaration() instanceof ClassModel && ((ClassModel)compilationUnitModel.getPrimaryDeclaration()).isInterface(); } private void generateType(TypedModel typedModel) { if (!isEmpty(typedModel.getType())) { output.print(":" + typedModel.getType()); } } private void generateValue(ValuedModel valuedModel) { if (!isEmpty(valuedModel.getValue())) { output.print(" = " + valuedModel.getValue()); } } private static boolean isEmpty(String string) { return string == null || string.trim().length() == 0; } public static void main(String[] args) { // TODO: move to unit test! ClassModel classModel = new ClassModel("com.acme.Foo"); classModel.setAsdoc("This is the Foo class."); AnnotationModel annotation = new AnnotationModel("ExtConfig", new AnnotationPropertyModel("target", "'foo.Bar'")); classModel.addAnnotation(annotation); FieldModel field = new FieldModel("FOOBAR"); field.setType("String"); field.setConst(true); field.setStatic(true); field.setNamespace(NamespacedModel.PRIVATE); field.setAsdoc("A constant for foo bar."); field.setValue("'foo bar baz'"); classModel.addMember(field); MethodModel method = new MethodModel(); method.setName("doFoo"); method.setAsdoc("Some method."); method.setBody("trace('foo');"); ParamModel param = new ParamModel(); param.setName("foo"); param.setType("com.acme.sub.Bar"); param.setValue("null"); method.setParams(Collections.singletonList(param)); method.setType("int"); classModel.addMember(method); PropertyModel propertyModel = new PropertyModel(); propertyModel.setName("baz"); propertyModel.setType("String"); propertyModel.setAsdoc("The baz is a string."); classModel.addMember(propertyModel); StringWriter stringWriter = new StringWriter(); classModel.visit(new ActionScriptCodeGeneratingModelVisitor(stringWriter)); System.out.println("Result:\n" + stringWriter); } }