blob: 8a40140566e35360165da577e73594989f79c4cb [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 org.eclipse.xtend.lib.macro.AbstractClassProcessor
import org.eclipse.xtend.lib.macro.Active
import org.eclipse.xtend.lib.macro.TransformationContext
import org.eclipse.xtend.lib.macro.declaration.AnnotationReference
import org.eclipse.xtend.lib.macro.declaration.ClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.FieldDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration
import org.eclipse.xtext.xbase.lib.util.ToStringBuilder
/**
* Creates a default {@link Object#toString toString} implementation.
*
* <p>
* All non-static fields of this class and all of its superclasses are used.
* Reflection is used to access all fields if this class has a superclass. This may not work in restrictive environments.
* For such cases use {@link ToStringBuilder} to write a non-reflective implementation.
* </p>
* The default format is:
* <pre>
* ClassName [
* field1 = "Foo"
* field2 = 2
* field3 = null
* ]
* </pre>
*
* The class name is hardcoded. Subclasses which inherit this class without overriding <code>toString</code> will show the same name.
*
* </p>
* <p>
* For brevity there are options to hide field names, skip fields with null values and print everything on one line.
* </p>
* @since 2.7
*/
@Beta
@Target(ElementType.TYPE)
@Active(ToStringProcessor)
@GwtCompatible
annotation ToString {
/**
* Fields with null values are not shown in the output.
*/
val skipNulls = false
/**
* Seperate fields with a comma and a single space
*/
val singleLine = false
/**
* Only list the values of the fields, not their names
*/
val hideFieldNames = false
/**
* By default, Iterables, Arrays and multiline Strings are pretty-printed.
* Switching to their normal representation makes the toString method significantly faster.
* @since 2.9
*/
val verbatimValues = false
}
/**
* @since 2.7
* @noextend This class is not intended to be subclassed by clients.
* @noreference This class is not intended to be referenced by clients.
* @noinstantiate This class is not intended to be instantiated by clients.
*/
@Beta
class ToStringConfiguration {
val boolean skipNulls
val boolean singleLine
val boolean hideFieldNames
val boolean verbatimValues
new() {
this(false, false, false, false)
}
new(boolean skipNulls, boolean singleLine, boolean hideFieldNames, boolean verbatimValues) {
this.skipNulls = skipNulls
this.singleLine = singleLine
this.hideFieldNames = hideFieldNames
this.verbatimValues = verbatimValues
}
new(AnnotationReference annotation) {
this.skipNulls = annotation.getBooleanValue("skipNulls")
this.singleLine = annotation.getBooleanValue("singleLine")
this.hideFieldNames = annotation.getBooleanValue("hideFieldNames")
this.verbatimValues = annotation.getBooleanValue("verbatimValues")
}
def isSkipNulls() {
skipNulls
}
def isSingleLine() {
singleLine
}
def isHideFieldNames() {
hideFieldNames
}
def isVerbatimValues() {
verbatimValues
}
}
/**
* @since 2.7
* @noextend
* @noreference
*/
@Beta
class ToStringProcessor extends AbstractClassProcessor {
override doTransform(MutableClassDeclaration it, extension TransformationContext context) {
if (findAnnotation(Data.findTypeGlobally) !== null) {
return
}
val extension util = new ToStringProcessor.Util(context)
val annotation = findAnnotation(ToString.findTypeGlobally)
val configuration = new ToStringConfiguration(annotation)
if (hasToString) {
annotation.addWarning("toString is already defined, this annotation has no effect.")
} else if (extendedClass != object) {
addReflectiveToString(configuration)
} else {
addToString(declaredFields.filter[thePrimaryGeneratedJavaElement && ! static && !transient], configuration)
}
}
/**
* @since 2.7
* @noextend
* @noreference
*/
@Beta
static class Util {
extension TransformationContext context
new(TransformationContext context) {
this.context = context
}
def hasToString(ClassDeclaration it) {
findDeclaredMethod("toString") !== null
}
def getToStringConfig(ClassDeclaration it) {
val anno = findAnnotation(ToString.findTypeGlobally)
if(anno === null) null else new ToStringConfiguration(anno)
}
def void addReflectiveToString(MutableClassDeclaration cls, ToStringConfiguration config) {
cls.addMethod("toString") [
primarySourceElement = cls.primarySourceElement
returnType = string
addAnnotation(newAnnotationReference(Override))
addAnnotation(newAnnotationReference(Pure))
body = '''
return new «ToStringBuilder»(this)
.addAllFields()
«IF config.skipNulls».skipNulls()«ENDIF»
«IF config.singleLine».singleLine()«ENDIF»
«IF config.hideFieldNames».hideFieldNames()«ENDIF»
«IF config.verbatimValues».verbatimValues()«ENDIF»
.toString();
'''
]
}
def void addToString(MutableClassDeclaration cls, Iterable<? extends FieldDeclaration> fields,
ToStringConfiguration config) {
cls.addMethod("toString") [
primarySourceElement = cls.primarySourceElement
returnType = string
addAnnotation(newAnnotationReference(Override))
addAnnotation(newAnnotationReference(Pure))
body = '''
«ToStringBuilder» b = new «ToStringBuilder»(this);
«IF config.skipNulls»b.skipNulls();«ENDIF»
«IF config.singleLine»b.singleLine();«ENDIF»
«IF config.hideFieldNames»b.hideFieldNames();«ENDIF»
«IF config.verbatimValues»b.verbatimValues();«ENDIF»
«FOR field : fields»
b.add("«field.simpleName»", this.«field.simpleName»);
«ENDFOR»
return b.toString();
'''
]
}
}
}