blob: a929f67eb98e95da881ee75bae25e09293891c5c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
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 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.AnnotationReference
import org.eclipse.xtend.lib.macro.declaration.AnnotationTarget
import org.eclipse.xtend.lib.macro.declaration.FieldDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableFieldDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableMemberDeclaration
import org.eclipse.xtend.lib.macro.declaration.TypeReference
import org.eclipse.xtend.lib.macro.declaration.Visibility
import java.lang.annotation.Documented
/**
* Creates getters and setters for annotated fields or for all fields in an annotated class.
* <p>
* Annotated on a field
* <ul>
* <li>Creates a getter for that field if none exists. For primitive boolean properties, the "is"-prefix is used.</li>
* <li>Creates a setter for that field if it is not final and no setter exists</li>
* <li>By default the accessors are public</li>
* <li>If the {@link AccessorType}[] argument is given, only the listed
* accessors with the specified visibility will be generated</li>
* <li>By default the accessors will be deprecated if the field is annotated as such.
* This can be changed by explicitly providing {@link Accessors#deprecationPolicy deprecationPolicy}</li>
* </ul>
* </p>
* <p>
* Annotated on a class
* <ul>
* <li>Creates accessors for all non-static fields of that class as specified
* above</li>
* <li>Creates a constructor taking all final fields of the class if no
* constructor exists yet. If there already is a constructor and you want the
* default one on top of that, you can use the {@link FinalFieldsConstructor}
* annotation.</li>
* </ul>
* </p>
* Field-level annotations have precedence over a class-level annotation. Accessors can be suppressed completely by using {@link AccessorType#NONE}.
* This annotation can also be used to fine-tune the getters generated by {@link Data}.
* @since 2.7
*/
@GwtCompatible
@Target(ElementType.FIELD, ElementType.TYPE)
@Active(AccessorsProcessor)
@Documented
annotation Accessors {
/**
* Describes the access modifiers for generated accessors. Valid combinations
* include at most one type for getters and one for setters.
* Accessors may be suppressed by passing {@link AccessorType#NONE}.
*/
AccessorType[] value = #[AccessorType.PUBLIC_GETTER, AccessorType.PUBLIC_SETTER]
/**
* Describes when {@code @Deprecated} will be added to generated accessors.<br>
* If it is not wanted or needed, pass {@link AccessorsDeprecationPolicy#NEVER} to prevent the annotation from being added.
* @since 2.23
*/
@Beta
AccessorsDeprecationPolicy deprecationPolicy = AccessorsDeprecationPolicy.SAME_AS_FIELD
}
/**
* @since 2.7
* @noreference
*/
@Beta
@GwtCompatible
enum AccessorType {
PUBLIC_GETTER,
PROTECTED_GETTER,
PACKAGE_GETTER,
PRIVATE_GETTER,
PUBLIC_SETTER,
PROTECTED_SETTER,
PACKAGE_SETTER,
PRIVATE_SETTER,
NONE
}
/**
* @since 2.23
*/
@Beta
@GwtCompatible
enum AccessorsDeprecationPolicy {
SAME_AS_FIELD,
ONLY_GETTER,
ONLY_SETTER,
ALWAYS,
NEVER
}
/**
* @since 2.7
* @noextend
* @noreference
*/
@Beta
class AccessorsProcessor implements TransformationParticipant<MutableMemberDeclaration> {
override doTransform(List<? extends MutableMemberDeclaration> elements, extension TransformationContext context) {
elements.forEach[transform(context)]
}
def dispatch void transform(MutableFieldDeclaration it, extension TransformationContext context) {
extension val util = new AccessorsProcessor.Util(context)
val annot = accessorsAnnotation
if (shouldAddGetter) {
addGetter(getterType.toVisibility)
}
else if (annot !== null && annot.deprecationPolicyAsEnum === AccessorsDeprecationPolicy.ONLY_GETTER){
it.addWarning(
'''
Field «simpleName» needs no getter, but deprecationPolicy is ONLY_GETTER.
Explicitly setting it has no effect, as no getter will be generated.
Use deprecation policy NEVER to disable accessors deprecation and remove this warning.
'''
)
}
if (shouldAddSetter) {
addSetter(setterType.toVisibility)
}
else if(annot !== null && annot.deprecationPolicyAsEnum === AccessorsDeprecationPolicy.ONLY_SETTER){
it.addWarning(
'''
Field «simpleName» needs no setter, but deprecationPolicy is ONLY_SETTER.
Explicitly setting it has no effect, as no setter will be generated.
Use deprecation policy NEVER to disable accessors deprecation and remove this warning.
'''
)
}
}
def dispatch void transform(MutableClassDeclaration it, extension TransformationContext context) {
if (findAnnotation(Data.findTypeGlobally) !== null) {
return
}
val extension requiredArgsUtil = new FinalFieldsConstructorProcessor.Util(context)
if (needsFinalFieldConstructor || findAnnotation(FinalFieldsConstructor.findTypeGlobally) !== null) {
addFinalFieldsConstructor
}
declaredFields.filter[!static && thePrimaryGeneratedJavaElement].forEach[_transform(context)]
}
/**
* @since 2.7
* @noextend
* @noreference
*/
@Beta
static class Util {
extension TransformationContext context
new(TransformationContext context) {
this.context = context
}
def toVisibility(AccessorType type) {
switch type {
case PUBLIC_GETTER: Visibility.PUBLIC
case PROTECTED_GETTER: Visibility.PROTECTED
case PACKAGE_GETTER: Visibility.DEFAULT
case PRIVATE_GETTER: Visibility.PRIVATE
case PUBLIC_SETTER: Visibility.PUBLIC
case PROTECTED_SETTER: Visibility.PROTECTED
case PACKAGE_SETTER: Visibility.DEFAULT
case PRIVATE_SETTER: Visibility.PRIVATE
default: throw new IllegalArgumentException('''Cannot convert «type»''')
}
}
def hasGetter(FieldDeclaration it) {
possibleGetterNames.exists[name| declaringType.findDeclaredMethod(name) !== null]
}
def shouldAddGetter(FieldDeclaration it) {
!hasGetter && getterType !== AccessorType.NONE
}
@SuppressWarnings('unchecked')
def getGetterType(FieldDeclaration it) {
val annotation = accessorsAnnotation ?: declaringType.accessorsAnnotation
if (annotation !== null) {
val types = annotation.getEnumArrayValue("value").map[AccessorType.valueOf(simpleName)]
return types.findFirst[name.endsWith("GETTER")] ?: AccessorType.NONE
}
return null;
}
def getAccessorsAnnotation(AnnotationTarget it) {
findAnnotation(Accessors.findTypeGlobally)
}
def getDeprecatedAnnotation(AnnotationTarget it) {
findAnnotation(Deprecated.findTypeGlobally)
}
def getDeprecationPolicyAsEnum(AnnotationReference annot){
AccessorsDeprecationPolicy.valueOf(annot.getEnumValue('deprecationPolicy').simpleName)
}
def validateGetter(MutableFieldDeclaration field) {
}
def getGetterName(FieldDeclaration it) {
possibleGetterNames.head
}
def List<String> getPossibleGetterNames(FieldDeclaration it) {
val names = newArrayList
// common case: a boolean field already starts with 'is'. Allow field name as getter method name
if (type.orObject.isBooleanType && simpleName.startsWith('is') && simpleName.length>2 && Character.isUpperCase(simpleName.charAt(2))) {
names += simpleName
}
names.addAll((if(type.orObject.isBooleanType) #["is", "get"] else #["get"]).map[prefix|prefix + simpleName.toFirstUpper])
return names
}
def isBooleanType(TypeReference it) {
!inferred && it == primitiveBoolean
}
def void addGetter(MutableFieldDeclaration field, Visibility visibility) {
field.validateGetter
field.markAsRead
field.declaringType.addMethod(field.getterName) [
primarySourceElement = field.primarySourceElement
addAnnotation(newAnnotationReference(Pure))
// Generate Override if not overriding a final method
val superGetters = overriddenOrImplementedMethods
if(!superGetters.empty){
if(superGetters.exists[final]) {
field.addError('''Adding a getter to the field «field.simpleName» would override a final method.''')
}
else {
addAnnotation(newAnnotationReference(Override))
}
}
val annot = field.accessorsAnnotation
if(annot !== null) {
// Enforce deprecation policy for getter
switch annot.deprecationPolicyAsEnum {
case ALWAYS,
case ONLY_GETTER:
addAnnotation(newAnnotationReference(Deprecated))
case SAME_AS_FIELD:
if(field.deprecatedAnnotation !== null)
addAnnotation(newAnnotationReference(Deprecated))
case ONLY_SETTER,
case NEVER: {} // No-op
default: throw new IllegalArgumentException('''Cannot determine deprecation policy for field «field.simpleName»''')
}
} // if(annot !== null)
returnType = field.type.orObject
body = '''return «field.fieldOwner».«field.simpleName»;'''
static = field.static
it.visibility = visibility
]
}
@SuppressWarnings('unchecked')
def getSetterType(FieldDeclaration it) {
val annotation = accessorsAnnotation ?: declaringType.accessorsAnnotation
if (annotation !== null) {
val types = annotation.getEnumArrayValue("value").map[AccessorType.valueOf(simpleName)]
return types.findFirst[name.endsWith("SETTER")] ?: AccessorType.NONE
}
return null
}
private def fieldOwner(MutableFieldDeclaration it) {
if(static) declaringType.newTypeReference else "this"
}
def hasSetter(FieldDeclaration it) {
declaringType.findDeclaredMethod(setterName, type.orObject) !== null
}
def getSetterName(FieldDeclaration it) {
"set" + simpleName.toFirstUpper
}
def shouldAddSetter(FieldDeclaration it) {
!final && !hasSetter && setterType !== AccessorType.NONE
}
def validateSetter(MutableFieldDeclaration field) {
if (field.final) {
field.addError("Cannot set a final field")
}
if (field.type === null || field.type.inferred) {
field.addError("Type cannot be inferred.")
return
}
}
def void addSetter(MutableFieldDeclaration field, Visibility visibility) {
field.validateSetter
field.declaringType.addMethod(field.setterName) [
primarySourceElement = field.primarySourceElement
returnType = primitiveVoid
// Generate Override if not overriding a final method
val superSetters = overriddenOrImplementedMethods
if(!superSetters.empty){
if(superSetters.exists[final]) {
field.addError('''Adding a setter to the field «field.simpleName» would override a final method.''')
}
else {
addAnnotation(newAnnotationReference(Override))
}
}
val annot = field.accessorsAnnotation
if(annot !== null) {
// Enforce deprecation policy for setter
switch annot.deprecationPolicyAsEnum {
case ALWAYS,
case ONLY_SETTER:
addAnnotation(newAnnotationReference(Deprecated))
case SAME_AS_FIELD:
if(field.deprecatedAnnotation !== null)
addAnnotation(newAnnotationReference(Deprecated))
case ONLY_GETTER,
case NEVER: {} // No-op
default: throw new IllegalArgumentException('''Cannot determine deprecation policy for field «field.simpleName»''')
}
} // if(annot !== null)
val param = addParameter(field.simpleName, field.type.orObject)
body = '''«field.fieldOwner».«field.simpleName» = «param.simpleName»;'''
static = field.static
it.visibility = visibility
]
}
private def orObject(TypeReference ref) {
if (ref === null) object else ref
}
}
}