diff --git a/src/main/java/de/itemis/xtend/auto/gwt/ActiveAnnotationProcessorHelper.xtend b/src/main/java/de/itemis/xtend/auto/gwt/ActiveAnnotationProcessorHelper.xtend new file mode 100644 index 0000000..b6e5c7b --- /dev/null +++ b/src/main/java/de/itemis/xtend/auto/gwt/ActiveAnnotationProcessorHelper.xtend @@ -0,0 +1,18 @@ +package de.itemis.xtend.auto.gwt + +import org.eclipse.xtend.lib.macro.CodeGenerationContext +import org.eclipse.xtend.lib.macro.declaration.TypeDeclaration +import org.eclipse.xtend.lib.macro.file.Path + +class ActiveAnnotationProcessorHelper { + private static def TypeDeclaration topLevelType(TypeDeclaration type) { + return if (type.declaringType == null) type else type.declaringType.topLevelType + } + + static def Path getTargetPath(TypeDeclaration type, extension CodeGenerationContext ctx) { + val topLevelType = type.topLevelType + val unit = type.compilationUnit + val targetFolder = unit.filePath.targetFolder + return targetFolder.append(unit.sourceTypeDeclarations.findFirst[sourceType|sourceType==topLevelType].qualifiedName.replace('.','/')+".java") + } +} \ No newline at end of file diff --git a/src/main/java/de/itemis/xtend/auto/gwt/ClientBundle.xtend b/src/main/java/de/itemis/xtend/auto/gwt/ClientBundle.xtend index a3ac05d..c1dbe15 100644 --- a/src/main/java/de/itemis/xtend/auto/gwt/ClientBundle.xtend +++ b/src/main/java/de/itemis/xtend/auto/gwt/ClientBundle.xtend @@ -20,6 +20,8 @@ import java.lang.annotation.ElementType import java.lang.annotation.Target import java.util.List import org.eclipse.xtend.lib.macro.Active +import org.eclipse.xtend.lib.macro.CodeGenerationContext +import org.eclipse.xtend.lib.macro.CodeGenerationParticipant import org.eclipse.xtend.lib.macro.RegisterGlobalsContext import org.eclipse.xtend.lib.macro.RegisterGlobalsParticipant import org.eclipse.xtend.lib.macro.TransformationContext @@ -47,7 +49,8 @@ annotation ImageResources { annotation ClientBundle { } -class CliendBundleProcessor implements RegisterGlobalsParticipant, TransformationParticipant { +class CliendBundleProcessor implements RegisterGlobalsParticipant, TransformationParticipant, + CodeGenerationParticipant { private static final String INSTANCE = 'INSTANCE' @@ -246,5 +249,12 @@ class CliendBundleProcessor implements RegisterGlobalsParticipant annotatedSourceElements, extension CodeGenerationContext context) { + for(annotatedSourceElement: annotatedSourceElements) { + val path = ActiveAnnotationProcessorHelper::getTargetPath(annotatedSourceElement, context) + val newContents = path.contents.toString.replaceAll("@ClientBundle.Source", "@Source") + path.contents = newContents + } + } } diff --git a/src/main/java/de/itemis/xtend/auto/gwt/JsNative.xtend b/src/main/java/de/itemis/xtend/auto/gwt/JsNative.xtend index 5219573..ef341a9 100644 --- a/src/main/java/de/itemis/xtend/auto/gwt/JsNative.xtend +++ b/src/main/java/de/itemis/xtend/auto/gwt/JsNative.xtend @@ -7,6 +7,8 @@ *******************************************************************************/ package de.itemis.xtend.auto.gwt +import java.util.HashMap +import java.util.List import org.eclipse.xtend.lib.macro.AbstractMethodProcessor import org.eclipse.xtend.lib.macro.Active import org.eclipse.xtend.lib.macro.CodeGenerationContext @@ -51,23 +53,36 @@ class JsNativeProcessor extends AbstractMethodProcessor { return method.declaringType.simpleName+"#"+method.simpleName+"("+method.parameters.map[type].join(',')+")" } - override doGenerateCode(MethodDeclaration annotatedMethod, extension CodeGenerationContext context) { - val path = annotatedMethod.declaringType.getTargetPath(context) - val contents = path.contents.toString - val markerStart = contents.indexOf(getUniqueMarkerCode(annotatedMethod)) - val startIndex = contents.substring(0, markerStart).lastIndexOf('{') - val endIndex = contents.substring(markerStart).indexOf('}') + markerStart - val jsCode = annotatedMethod.body.toString.trimTripleQuotes - path.contents = contents.substring(0, startIndex)+"/*-{"+jsCode+"}-*/;"+contents.substring(endIndex+1) + override doGenerateCode(List annotatedMethods, extension CodeGenerationContext context) { + // When the annotation is used multiple times within a single source xtend compilation + // unit, the methods can end up in several different java compilation units due to + // multiple toplevel types in one xtend file. Furthermore, there can be multiple + // annotated methods per java compilation unit. + // We load change all contents first and later save all compilation units only once. + val javaUnits = new HashMap; + for(annotatedMethod: annotatedMethods) { + val path = ActiveAnnotationProcessorHelper::getTargetPath(annotatedMethod.declaringType, context) + if (javaUnits.get(path) == null) { + javaUnits.put(path, path.contents.toString) + } + var contents = javaUnits.get(path) + + val markerStart = contents.indexOf(getUniqueMarkerCode(annotatedMethod)) + if (markerStart > 0) { + val startIndex = contents.substring(0, markerStart).lastIndexOf('{') + val endIndex = contents.substring(markerStart).indexOf('}') + markerStart + val jsCode = annotatedMethod.body.toString.trimTripleQuotes + val newContents = contents.substring(0, startIndex)+"/*-{"+jsCode+"}-*/;"+contents.substring(endIndex+1) + + javaUnits.put(path, newContents) + } + } + for (path: javaUnits.keySet) { + path.contents = javaUnits.get(path) + } } private def String trimTripleQuotes(String s) { s.substring(3, s.length-3) } - - def Path getTargetPath(TypeDeclaration type, extension CodeGenerationContext ctx) { - val unit = type.compilationUnit - val targetFolder = unit.filePath.targetFolder - return targetFolder.append(type.qualifiedName.replace('.','/')+".java") - } -} \ No newline at end of file +} diff --git a/src/main/java/de/itemis/xtend/auto/gwt/OverlayTypeByExample.xtend b/src/main/java/de/itemis/xtend/auto/gwt/OverlayTypeByExample.xtend index 96f59d3..675918e 100644 --- a/src/main/java/de/itemis/xtend/auto/gwt/OverlayTypeByExample.xtend +++ b/src/main/java/de/itemis/xtend/auto/gwt/OverlayTypeByExample.xtend @@ -14,6 +14,9 @@ import com.google.gson.JsonParser import com.google.gson.JsonPrimitive import com.google.gwt.core.client.JavaScriptObject import com.google.gwt.core.client.JsArray +import com.google.gwt.core.client.JsArrayBoolean +import com.google.gwt.core.client.JsArrayNumber +import com.google.gwt.core.client.JsArrayString import java.util.Map.Entry import java.util.regex.Pattern import org.eclipse.xtend.lib.macro.AbstractClassProcessor @@ -23,8 +26,10 @@ import org.eclipse.xtend.lib.macro.RegisterGlobalsContext import org.eclipse.xtend.lib.macro.TransformationContext import org.eclipse.xtend.lib.macro.declaration.ClassDeclaration import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration +import org.eclipse.xtend.lib.macro.declaration.TypeDeclaration import org.eclipse.xtend.lib.macro.declaration.TypeReference import org.eclipse.xtend.lib.macro.declaration.Visibility +import org.eclipse.xtend.lib.macro.file.Path import static extension de.itemis.xtend.auto.gwt.StaticUtils.* @@ -85,20 +90,19 @@ class OverlayTypeByExampleProcessor extends AbstractClassProcessor { } - val PATTERN = Pattern.compile("public final native (.+) get(\\w+)\\(\\);") + val PATTERN = Pattern.compile("public final native (.+) get(\\w+)\\(\\) \\{\\s*(\\w+)\\s*\\}") /** * we add the Java comment containing the javascript code during code generation, since there is no way to add it using the Java model. */ override doGenerateCode(ClassDeclaration annotatedClass, extension CodeGenerationContext context) { - val targetFolder = annotatedClass.compilationUnit.filePath.targetFolder - val targetFile = targetFolder.append(annotatedClass.qualifiedName.replace('.','/')+".java") - val contents = targetFile.contents + val path = ActiveAnnotationProcessorHelper::getTargetPath(annotatedClass, context) + val contents = path.contents val matcher = PATTERN.matcher(contents) - targetFile.contents = matcher.replaceAll("public final native $1 get$2() /*-{ return this.$2; }-*/;") + val newContents = matcher.replaceAll("public final native $1 get$2() /*-{ return this.$3; }-*/;") + path.contents = newContents } - static class Util { extension TransformationContext ctx @@ -122,6 +126,8 @@ class OverlayTypeByExampleProcessor extends AbstractClassProcessor { native = true final = true returnType = getJavaType(property, classDeclaration) + // save the property key as body for later reference in code generation phase + body = '''«property.key»''' ] } @@ -147,19 +153,26 @@ class OverlayTypeByExampleProcessor extends AbstractClassProcessor { val type = currentContainer.declaredClasses.findFirst[simpleName == simpleTypeName] type.newTypeReference } - JsonPrimitive case element.isString: { - String.newTypeReference - } - JsonPrimitive case element.isBoolean: { - boolean.newTypeReference - } - JsonPrimitive case element.isNumber: { - if (element.asString.indexOf('.') == -1) { - int.newTypeReference + JsonPrimitive: + if (isArray) { + switch (element) { + case element.isString: return JsArrayString.newTypeReference + case element.isBoolean: return JsArrayBoolean.newTypeReference + case element.isNumber: return JsArrayNumber.newTypeReference + } } else { - double.newTypeReference - } - } + switch (element) { + case element.isString: String.newTypeReference + case element.isBoolean: boolean.newTypeReference + case element.isNumber: { + if (element.asString.indexOf('.') == -1) { + int.newTypeReference + } else { + double.newTypeReference + } + } + } + } default : { currentContainer.addError("unknown element "+element) throw new IllegalStateException