blob: 95db307f9e71f6ff56d462e247a24c15a6d80081 [file] [log] [blame]
package org.eclipse.xtend.lib.annotations
import com.google.common.annotations.Beta
import com.google.common.annotations.GwtCompatible
import java.lang.annotation.ElementType
import java.lang.annotation.Target
import java.util.List
import java.util.regex.Pattern
import org.eclipse.xtend.lib.macro.Active
import org.eclipse.xtend.lib.macro.TransformationContext
import org.eclipse.xtend.lib.macro.TransformationParticipant
import org.eclipse.xtend.lib.macro.declaration.ClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableConstructorDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableTypeDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableTypeParameterDeclarator
import org.eclipse.xtend.lib.macro.declaration.TypeDeclaration
import org.eclipse.xtend.lib.macro.declaration.TypeReference
import java.lang.annotation.Documented
/**
* <p>Creates a constructor that takes a parameter for each final field of a class.</p>
*
* Annotated on a class
* <p>
* Creates a constructor that takes all
* non-static, final fields of the class as an argument and assigns them to
* their corresponding fields. The order of arguments is the same as the order
* of the fields.
* </p>
* Annotated on a constructor
* <p>
* Turns that constructor into a final
* fields constructor as described above. This is useful for adding annotations
* to the constructor, e.g. for dependency injection frameworks.
* </p>
*
* @since 2.7
*/
@Target(ElementType.TYPE, ElementType.CONSTRUCTOR)
@GwtCompatible
@Beta
@Active(FinalFieldsConstructorProcessor)
@Documented
annotation FinalFieldsConstructor {
}
/**
* @since 2.7
* @noextend
* @noreference
*/
@Beta
class FinalFieldsConstructorProcessor implements TransformationParticipant<MutableTypeParameterDeclarator> {
override doTransform(List<? extends MutableTypeParameterDeclarator> elements,
extension TransformationContext context) {
elements.forEach[transform(context)]
}
def dispatch void transform(MutableClassDeclaration it, extension TransformationContext context) {
if (findAnnotation(Data.findTypeGlobally) !== null) {
return
}
if (findAnnotation(Accessors.findTypeGlobally) !== null) {
return
}
val extension util = new FinalFieldsConstructorProcessor.Util(context)
addFinalFieldsConstructor
}
def dispatch void transform(MutableConstructorDeclaration it, extension TransformationContext context) {
val extension util = new FinalFieldsConstructorProcessor.Util(context)
makeFinalFieldsConstructor
}
/**
* @since 2.7
* @noextend
* @noreference
*/
@Beta
static class Util {
extension TransformationContext context
new(TransformationContext context) {
this.context = context
}
def getFinalFields(MutableTypeDeclaration it) {
declaredFields.filter[!static && final == true && initializer === null && thePrimaryGeneratedJavaElement]
}
def needsFinalFieldConstructor(MutableClassDeclaration it) {
!hasFinalFieldsConstructor
&& (primarySourceElement as ClassDeclaration).declaredConstructors.isEmpty
}
def hasFinalFieldsConstructor(MutableTypeDeclaration cls) {
val expectedTypes = cls.finalFieldsConstructorArgumentTypes
cls.declaredConstructors.exists [
parameters.map[type].toList == expectedTypes
]
}
def getFinalFieldsConstructorArgumentTypes(MutableTypeDeclaration cls) {
val types = newArrayList
if (cls.superConstructor !== null) {
types += cls.superConstructor.resolvedParameters.map[resolvedType]
}
types += cls.finalFields.map[type]
types
}
def String getConstructorAlreadyExistsMessage(MutableTypeDeclaration it) {
'''Cannot create FinalFieldsConstructor as a constructor with the signature "new(«finalFieldsConstructorArgumentTypes.join(",")»)" already exists.'''
}
def addFinalFieldsConstructor(MutableClassDeclaration it) {
if (finalFieldsConstructorArgumentTypes.empty) {
val anno = findAnnotation(FinalFieldsConstructor.findTypeGlobally)
anno.addWarning('''There are no final fields, this annotation has no effect''')
return
}
if (hasFinalFieldsConstructor) {
addError(constructorAlreadyExistsMessage)
return
}
addConstructor [
primarySourceElement = declaringType.primarySourceElement
makeFinalFieldsConstructor
]
}
static val EMPTY_BODY = Pattern.compile("(\\{(\\s*\\})?)?")
def makeFinalFieldsConstructor(MutableConstructorDeclaration it) {
if (declaringType.finalFieldsConstructorArgumentTypes.empty) {
val anno = findAnnotation(FinalFieldsConstructor.findTypeGlobally)
anno.addWarning('''There are no final fields, this annotation has no effect''')
return
}
if (declaringType.hasFinalFieldsConstructor) {
addError(declaringType.constructorAlreadyExistsMessage)
return
}
if (!parameters.empty) {
addError("Parameter list must be empty")
}
if (body !== null && !EMPTY_BODY.matcher(body.toString).matches) {
addError("Body must be empty")
}
val superParameters = declaringType.superConstructor?.resolvedParameters ?: #[]
superParameters.forEach [ p |
addParameter(p.declaration.simpleName, p.resolvedType)
]
val fieldToParameter = newHashMap
declaringType.finalFields.forEach [ p |
p.markAsInitializedBy(it)
val param = addParameter(p.simpleName, p.type.orObject)
fieldToParameter.put(p, param)
]
body = '''
super(«superParameters.join(", ")[declaration.simpleName]»);
«FOR arg : declaringType.finalFields»
this.«arg.simpleName» = «fieldToParameter.get(arg).simpleName»;
«ENDFOR»
'''
}
def getSuperConstructor(TypeDeclaration it) {
if (it instanceof ClassDeclaration) {
if (extendedClass == object || extendedClass === null)
return null;
return extendedClass.declaredResolvedConstructors.head
} else {
return null
}
}
private def orObject(TypeReference ref) {
if (ref === null) object else ref
}
}
}