diff --git a/Issues.md b/Issues.md index 32eb773..ae67f75 100644 --- a/Issues.md +++ b/Issues.md @@ -62,6 +62,7 @@ Apache Velocity =============== - template engine that could help us generate code in a nicer fashion (no more out.write("blah..."); - it *can* be used to generate source code! + - DONE! Javassist ========= diff --git a/annotation-processor/src/de/hsrm/cs/jscala/ADT.java b/annotation-processor/src/de/hsrm/cs/jscala/ADT.java index b8b388a..a7f1792 100644 --- a/annotation-processor/src/de/hsrm/cs/jscala/ADT.java +++ b/annotation-processor/src/de/hsrm/cs/jscala/ADT.java @@ -7,12 +7,9 @@ import javax.annotation.processing.Filer; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.*; -import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import java.io.IOException; -import java.io.Writer; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -20,6 +17,9 @@ */ public class ADT { + /** + * The full name of this class, complete with packages, enclosing classes and name. + */ String fullName; /** @@ -40,76 +40,22 @@ public class ADT { final Filer filer; ProcessingEnvironment env; - // TODO: move helpers outta here - /** - * n == 1 means lastIndexOf - */ - public static int lastNthIndexOf(String s, char c, int n) { - int occ = 0; - for(int i = s.length() - 1; i >= 0; --i) { - if(s.charAt(i) == c) { - occ++; - } - - if(n == occ) { - return i; - } - } - - return -1; - } - - public static int nthIndexOf(String s, char c, int n) { - int occ = 0; - for(int i = 0; i < s.length(); ++i) { - if(s.charAt(i) == c) { - occ++; - } + public ADT(TypeElement typeElement, ProcessingEnvironment env) { + fullName = typeElement.getQualifiedName().toString(); + env.getMessager().printMessage(Diagnostic.Kind.NOTE, "Processing type: " + fullName); - if(n == occ) { - return i; - } - } + this.typeElement = typeElement; + this.env = env; + this.filer = env.getFiler(); - return -1; + resolveEnclosingTypes(); } - - /** - * Helper that returns the full type parameter specification of a given type parameter, as a string (includes - * type boundaries). + * Figures out if our type is nested and takes the necessary precautions in order to + * generate correct code. */ - public static String getFullTypeParamName(TypeParameterElement e) { - StringBuilder sb = new StringBuilder(); - String mainType = e.asType().toString(); - sb.append(mainType); - - List bounds = e.getBounds(); - if(bounds.size() == 0) { - return sb.toString(); - } - else { - sb.append(" extends "); - boolean first = true; - for(TypeMirror tm : bounds) { - if(first) { - first = false; - } else { - sb.append(" & "); - } - - sb.append(tm.toString()); - } - - return sb.toString(); - } - } - - public ADT(TypeElement typeElement, ProcessingEnvironment env) { - fullName = typeElement.getQualifiedName().toString(); - env.getMessager().printMessage(Diagnostic.Kind.NOTE, "Processing type: " + fullName); - + private void resolveEnclosingTypes() { Element enclosing = typeElement.getEnclosingElement(); int enclosingClasses = 0; @@ -134,7 +80,7 @@ public ADT(TypeElement typeElement, ProcessingEnvironment env) { if(fullName.contains(".")) { // The index of the dot that separates the package name and the top wrapper class, in case this class is nested - int psDotIndex = lastNthIndexOf(fullName, '.', enclosingClasses + 1); + int psDotIndex = StringUtil.lastNthIndexOf(fullName, '.', enclosingClasses + 1); thePackage = fullName.substring(0, psDotIndex); if(typeElement.getNestingKind().isNested()) { enclosingTypes = fullName.substring(psDotIndex + 1, fullName.lastIndexOf(".")); @@ -148,21 +94,10 @@ public ADT(TypeElement typeElement, ProcessingEnvironment env) { simpleName = fullName; thePackage = ""; } - - this.typeElement = typeElement; - this.env = env; - this.filer = env.getFiler(); - } - - String getFullName() { - return fullName; - } - - String getSimpleName() { - return simpleName; } /** + * Returns a comma-separated list of this type's type parameters. * @param full Whether to return the FULL type parameter specification, e.g. > or the short one (e.g. ). */ String commaSeparatedTypeParams(boolean full){ @@ -181,7 +116,7 @@ String commaSeparatedTypeParams(boolean full){ } if(full) { - sb.append(getFullTypeParamName(e)); + sb.append(StringUtil.getFullTypeParamName(e)); } else { sb.append(e.getSimpleName()); @@ -192,16 +127,6 @@ String commaSeparatedTypeParams(boolean full){ } } - // TODO: remove this when switching to full-fledged templating - String getParamList(boolean full) { - String pars = commaSeparatedTypeParams(full); - return pars.length() == 0 ? "" : "<" + pars + ">"; - } - - String getPackageDef() { - return thePackage.length() == 0 ? "" : "package " + thePackage + ";\n\n"; - } - /** * Returns the exact name of this class' enclosing element (it's equal to thePackage if the class isn't nested). */ @@ -237,97 +162,23 @@ public void generateClasses(){ } private void generateVisitorClass() throws Exception { - /* - final String csName = simpleName + "Visitor"; - final String fTypeParams = commaSeparatedTypeParams(true); - final String fullName = csName + "<" + fTypeParams + (fTypeParams.length() == 0 ? "" : ",") +"result>"; - - Writer out = filer.createSourceFile(thePackage+"."+csName).openWriter(); - out.write(getPackageDef()); - out.write("\n\n"); - out.write("public abstract class "); - out.write(fullName+"{\n"); - for (Constructor c:constructors) - out.write(" "+c.mkVisitMethod(this)+"\n"); - - out.write(" public result visit("+ getFullName() + getParamList(false) + " xs){"); - out.write("\n throw new RuntimeException("); - out.write("\"unmatched pattern: \"+xs.getClass());\n"); - out.write(" }"); - - out.write("}"); - out.close(); - */ - VelocityContext context = getTemplateContext(); context.put("constructors", constructors.stream().map(c -> c.name).iterator()); - CodeGen.generate(this, filer, context, "Visitor.vm", thePackage + "." + getSimpleName() + "Visitor"); + CodeGen.generate(this, filer, context, "Visitor.vm", thePackage + "." + simpleName + "Visitor"); } private void generateClass() throws IOException { - //final String fullName = getFullName(); - // String sourceFileName = ((thePackage.length() == 0) ? "" : thePackage + ".") + simpleName + "Adt"; - - /* - Writer out = filer.createSourceFile(sourceFileName).openWriter(); - - out.write(getPackageDef()); - out.write("public abstract class "); - out.write(getSimpleName() + "Adt" + getParamList(true)); - out.write(" extends "+ fullName + getParamList(false)); - out.write(" implements Iterable{\n"); - out.write(" abstract public b_ welcome(" - + simpleName + "Visitor<" + commaSeparatedTypeParams(false) - + (commaSeparatedTypeParams(false).length() == 0 ? "" : "," ) - +"b_> visitor);\n"); - out.write("}"); - out.close(); - - */ VelocityContext context = getTemplateContext(); - context.put("fullName", getFullName()); - context.put("name", getSimpleName()); - CodeGen.generate(this, filer, context, "Adt.vm", thePackage + "." + getSimpleName() + "Adt"); + context.put("fullName", fullName); + context.put("name", simpleName); + CodeGen.generate(this, filer, context, "Adt.vm", thePackage + "." + simpleName + "Adt"); } private void generateCaseBranches() throws IOException { - /* - String sourceFileName = ((thePackage.length() == 0) ? "" : thePackage + ".") + simpleName + "Cases"; - Writer out = filer.createSourceFile(sourceFileName).openWriter(); - - out.write(getPackageDef()); - out.write("\nimport " + getEnclosingName() + ".*;\n"); - out.write("\nimport de.hsrm.cs.jscala.helpers.*;\n"); - out.write("\nimport java.util.Optional;\n\n"); - out.write("public class " + getSimpleName() + "Cases { \n"); - - for(Constructor c : constructors) { - out.write(c.genStaticCaseMethod(this) + "\n"); - out.write(c.genStaticCaseVoidMethod(this) + "\n"); - } - - // Generate the "otherwise" branch code (which matches anything) - String typeParamsLong = commaSeparatedTypeParams(true); - String typeParamsShort = commaSeparatedTypeParams(false); - // String wrappedTypeParamsShort = typeParamsShort.length() == 0 ? "" : "<" + typeParamsShort + ">"; - out.write(Constructor.genCaseHeader(typeParamsShort, typeParamsLong, getSimpleName(), "otherwise", Arrays.asList(typeElement))); - out.write("\treturn self -> Optional.of(theCase.apply(self));\n"); - out.write("}\n"); - - // Generate the "otherwiseV" (void) branch code (which matches anything but doesn't return anything either) - out.write(Constructor.genCaseHeader(typeParamsShort, typeParamsLong, getSimpleName(), "otherwiseV", Arrays.asList(typeElement), false)); - out.write("\treturn self -> { theCase.apply(self); return Optional.of(Nothing.VAL); };\n"); - out.write("}\n"); - - out.write("}"); - out.close(); - */ - VelocityContext context = getTemplateContext(); context.put("constructors", constructors); context.put("enclosingName", getEnclosingName()); - CodeGen.generate(this, filer, context, "Cases.vm", thePackage + "." + getSimpleName() + "Cases"); - + CodeGen.generate(this, filer, context, "Cases.vm", thePackage + "." + simpleName + "Cases"); } /** @@ -336,8 +187,8 @@ private void generateCaseBranches() throws IOException { public VelocityContext getTemplateContext() { VelocityContext context = new VelocityContext(); context.put("package", thePackage); - context.put("parentName", getSimpleName()); - context.put("parentFullName", getFullName()); + context.put("parentName", simpleName); + context.put("parentFullName", fullName); context.put("typeParamDeclaration", commaSeparatedTypeParams(true)); context.put("typeParamUsage", commaSeparatedTypeParams(false)); context.put("StringUtil", StringUtil.class); diff --git a/annotation-processor/src/de/hsrm/cs/jscala/Constructor.java b/annotation-processor/src/de/hsrm/cs/jscala/Constructor.java index 965ef10..7deee59 100644 --- a/annotation-processor/src/de/hsrm/cs/jscala/Constructor.java +++ b/annotation-processor/src/de/hsrm/cs/jscala/Constructor.java @@ -22,7 +22,7 @@ */ public class Constructor { - public String name; + String name; List params; public Constructor(String name, List params) { @@ -39,243 +39,10 @@ public List getParams() { } public void generateClass(ADT theType, Filer filer) throws IOException { - /* - Writer out = filer.createSourceFile(theType.thePackage + "." + name).openWriter(); - out.write(theType.getPackageDef()); - out.write("public class "); - out.write(name); - out.write(theType.getParamList(true)); - out.write(" extends "); - out.write(theType.getSimpleName() + "Adt" + theType.getParamList(false)); - out.write("{\n"); - - mkFields(out); - mkConstructor(out); - mkGetterMethods(out); - mkSetterMethods(out); - mkWelcomeMethod(out, theType); - mkToStringMethod(out); - mkEqualsMethod(out, theType); - mkIteratorMethod(out); - out.write("}\n"); - out.close(); - */ VelocityContext context = theType.getTemplateContext(); context.put("name", this.name); context.put("fields", this.params); - // Experimental Apache Velocity stuff CodeGen.generate(theType, filer, context, "CaseClass.vm", theType.thePackage + "." + this.name); } - - public static String genCaseHeader(String typeParamsShort, String typeParamsLong, String parentName, String caseName, List params) { - return genCaseHeader(typeParamsShort, typeParamsLong, parentName, caseName, params, true); - } - - // TODO: outta here - public static String genCaseHeader(String typeParamsShort, String typeParamsLong, String parentName, String caseName, List params, boolean includeB) { - String wrappedTypeParamsShort = typeParamsShort.length() == 0 ? "" : "<" + typeParamsShort + ">"; - String wrappedTypeParamsLong = typeParamsLong.length() == 0 ? "" : "<" + typeParamsLong + ">"; - StringBuilder sb = new StringBuilder(); - if(includeB) { - sb.append("public static" + "<" + typeParamsLong + (typeParamsLong.length() > 0 ? ", " : "") + "B> "); - sb.append("Function1<" + parentName + wrappedTypeParamsShort + ", Optional> " + caseName + "(Function" + params.size()); - } - else { - sb.append("public static" + wrappedTypeParamsLong + " "); - sb.append("Function1<" + parentName + wrappedTypeParamsShort + ", Optional> " + caseName + "(Consumer" + params.size()); - } - - if(params.size() > 0) { - sb.append("<"); - boolean first = true; - for(Element param : params) { - if(first) { - first = false; - } - else { - sb.append(", "); - } - sb.append(param.asType().toString()); - } - - if(includeB) { - sb.append(", B>"); - } - else { - sb.append(">"); - } - } - else { - if(includeB) { - sb.append(""); - } - } - sb.append(" theCase) {\n"); - return sb.toString(); - } - - public static String genGetters(List params, String varName) { - StringBuilder getters = new StringBuilder(); - boolean fg = true; - for(VariableElement ve : params) { - if(fg) { - fg = false; - } - else { - getters.append(", "); - } - - String sn = ve.getSimpleName().toString(); - getters.append(varName + ".get" + Character.toUpperCase(sn.charAt(0)) + sn.substring(1) + "()"); - } - - return getters.toString(); - } - - /** - * @param adt The ADT we're generating the case classes for - * @return - */ - public String genStaticCaseMethod(ADT adt) { - StringBuilder sb = new StringBuilder(); - String typeParamsShort = adt.commaSeparatedTypeParams(false); - String typeParamsLong = adt.commaSeparatedTypeParams(true); - String wrappedTypeParamsShort = typeParamsShort.length() == 0 ? "" : "<" + typeParamsShort + ">"; - sb.append(genCaseHeader(typeParamsShort, typeParamsLong, adt.getSimpleName(), "case" + this.name, this.params)); - sb.append("\treturn (self) -> {\n"); - sb.append("\t\tif(! (self instanceof " + name + ")) return Optional.empty();\n"); - sb.append("\t\t" + name + wrappedTypeParamsShort + " matchedBranch = (" + name + wrappedTypeParamsShort + ") self;\n"); - sb.append("\t\treturn Optional.of(theCase.apply(" + genGetters(this.params, "matchedBranch") + "));\n"); - sb.append("\t};\n"); - sb.append("}\n"); - - return sb.toString(); - } - - /** - * Generates overloaded methods to allow void cases to be matched (think caseSomething(el -> println(el))) - * @param adt The ADT we're generating the case classes for - * @return - */ - public String genStaticCaseVoidMethod(ADT adt) { - StringBuilder sb = new StringBuilder(); - String typeParamsShort = adt.commaSeparatedTypeParams(false); - String typeParamsLong = adt.commaSeparatedTypeParams(true); - String wrappedTypeParamsShort = typeParamsShort.length() == 0 ? "" : "<" + typeParamsShort + ">"; - sb.append(genCaseHeader(typeParamsShort, typeParamsLong, adt.getSimpleName(), "case" + this.name + "V", this.params, false)); - sb.append("\treturn (self) -> {\n"); - sb.append("\t\tif(! (self instanceof " + name + ")) return Optional.empty();\n"); - sb.append("\t\t" + name + wrappedTypeParamsShort + " matchedBranch = (" + name + wrappedTypeParamsShort + ") self;\n"); - sb.append("\t\ttheCase.apply(" + genGetters(this.params, "matchedBranch") + "); return Optional.of(Nothing.VAL);\n"); - sb.append("\t};\n"); - sb.append("}\n"); - - return sb.toString(); - } - - private void mkFields(Writer out) throws IOException { - for (VariableElement param : params) { - out.write(" private "); - out.write(param.asType().toString() + " "); - out.write(param.getSimpleName().toString()); - out.write(";\n"); - } - } - - private void mkConstructor(Writer out) throws IOException { - out.write("\n public " + name + "("); - boolean first = true; - for (VariableElement p : params) { - if (!first) { - out.write(","); - } - out.write(p.asType().toString() + " "); - out.write(p.getSimpleName().toString()); - first = false; - } - out.write("){\n"); - for (VariableElement p : params) { - out.write(" this." + p.getSimpleName() + " = "); - out.write(p.getSimpleName() + ";\n"); - } - out.write(" }\n\n"); - } - - private void mkGetterMethods(Writer out) throws IOException { - for (VariableElement p : params) { - out.write(" public "); - out.write(p.asType().toString()); - out.write(" get"); - out.write(Character.toUpperCase(p.getSimpleName().charAt(0))); - out.write(p.getSimpleName().toString().substring(1)); - out.write("(){return " + p.getSimpleName() + ";}\n"); - } - } - - private void mkSetterMethods(Writer out) throws IOException { - for (VariableElement p : params) { - out.write(" public void set"); - out.write(Character.toUpperCase(p.getSimpleName().charAt(0))); - out.write(p.getSimpleName().toString().substring(1)); - out.write("("); - out.write(p.asType().toString()); - out.write(" "); - out.write(p.getSimpleName().toString()); - out.write("){this." + p.getSimpleName()); - out.write("= " + p.getSimpleName() + ";}\n"); - } - } - - private void mkWelcomeMethod(Writer out, ADT theType) throws IOException { - out.write(" public <_b> _b welcome(" - + theType.simpleName + "Visitor<" + theType.commaSeparatedTypeParams(false) - + (theType.commaSeparatedTypeParams(false).length() == 0 ? "" : ",") - + "_b> visitor){" - + "\n return visitor.visit(this);\n }\n"); - } - - private void mkToStringMethod(Writer out) throws IOException { - out.write(" public String toString(){\n"); - out.write(" return \"" + name + "(\""); - boolean first = true; - for (VariableElement p : params) { - if (first) { - first = false; - } else out.write("+\",\""); - out.write("+" + p.getSimpleName().toString()); - } - out.write("+\")\";\n }\n"); - } - - private void mkEqualsMethod(Writer out, ADT adt) throws IOException { - out.write(" @SuppressWarnings({ \"unchecked\", \"unused\" })\n"); - out.write(" public boolean equals(Object other){\n"); - out.write(" if (!(other instanceof " + name + ")) return false;\n"); - out.write(" if (null == other) return false;\n"); - out.write(" if (this == other) return true;\n"); - out.write(" final " + name + adt.getParamList(false) + " o = (" + name + adt.getParamList(false) + ") other;\n"); - out.write(" return true "); - for (VariableElement p : params) { - out.write("&& " + p.getSimpleName() - + ".equals(o." + p.getSimpleName() + ")"); - } - out.write(";\n }\n"); - } - - private void mkIteratorMethod(Writer out) throws IOException { - out.write(" public java.util.Iterator iterator(){"); - out.write("\n java.util.List res\n"); - out.write(" =new java.util.ArrayList();\n"); - for (VariableElement p : params) { - out.write(" res.add(" + p.getSimpleName() + ");\n"); - } - out.write(" return res.iterator();\n"); - out.write(" }\n"); - } - - public String mkVisitMethod(ADT theType) { - return "public abstract result visit(" - + name + theType.getParamList(false) + " _ignore);"; - } } diff --git a/annotations/src/de/hsrm/cs/jscala/helpers/StringUtil.java b/annotations/src/de/hsrm/cs/jscala/helpers/StringUtil.java index f94ead0..e4f39d1 100644 --- a/annotations/src/de/hsrm/cs/jscala/helpers/StringUtil.java +++ b/annotations/src/de/hsrm/cs/jscala/helpers/StringUtil.java @@ -1,9 +1,16 @@ package de.hsrm.cs.jscala.helpers; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.TypeMirror; +import java.util.List; + /** * Created by Andrei Barsan on 20.02.2014, based on code by Prof. Dr. Sven Eric Panitz. */ public class StringUtil { + /** + * Capitalizes the first letter of the given string. + */ public static String cfirst(String val) { if(val.length() < 1) { return val; @@ -11,4 +18,72 @@ public static String cfirst(String val) { return Character.toUpperCase(val.charAt(0)) + val.substring(1); } } + + /** + * Finds the nth last index of the given character, in the given string. If the + * total number of occurrences is smaller than n, it returns -1. + * n == 1 means lastIndexOf + */ + public static int lastNthIndexOf(String s, char c, int n) { + int occ = 0; + for(int i = s.length() - 1; i >= 0; --i) { + if(s.charAt(i) == c) { + occ++; + } + + if(n == occ) { + return i; + } + } + + return -1; + } + + /** + * @see #lastNthIndexOf + */ + public static int nthIndexOf(String s, char c, int n) { + int occ = 0; + for(int i = 0; i < s.length(); ++i) { + if(s.charAt(i) == c) { + occ++; + } + + if(n == occ) { + return i; + } + } + + return -1; + } + + /** + * Helper that returns the full type parameter specification of a given type parameter, as a string (includes + * type boundaries). + */ + public static String getFullTypeParamName(TypeParameterElement e) { + StringBuilder sb = new StringBuilder(); + String mainType = e.asType().toString(); + sb.append(mainType); + + List bounds = e.getBounds(); + if(bounds.size() == 0) { + return sb.toString(); + } + else { + sb.append(" extends "); + boolean first = true; + for(TypeMirror tm : bounds) { + if(first) { + first = false; + } else { + sb.append(" & "); + } + + sb.append(tm.toString()); + } + + return sb.toString(); + } + } }