blob: ce296a7950bdc9797b8f0aa81a6fb947f4ac1ace [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2016 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
******************************************************************************/
package org.eclipse.lsp4j.generator
import org.eclipse.lsp4j.jsonrpc.validation.NonNull
import org.eclipse.xtend.lib.annotations.AccessorsProcessor
import org.eclipse.xtend.lib.annotations.EqualsHashCodeProcessor
import org.eclipse.xtend.lib.macro.AbstractClassProcessor
import org.eclipse.xtend.lib.macro.TransformationContext
import org.eclipse.xtend.lib.macro.declaration.ClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.CompilationStrategy.CompilationContext
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableFieldDeclaration
import org.eclipse.xtend.lib.macro.declaration.Type
import org.eclipse.xtend.lib.macro.declaration.Visibility
import org.eclipse.xtext.xbase.lib.util.ToStringBuilder
import com.google.gson.annotations.JsonAdapter
class JsonRpcDataProcessor extends AbstractClassProcessor {
override doTransform(MutableClassDeclaration annotatedClass, TransformationContext context) {
generateImpl(annotatedClass, context)
}
protected def generateImpl(MutableClassDeclaration impl, extension TransformationContext context) {
impl.removeAnnotation(impl.annotations.findFirst [
annotationTypeDeclaration == JsonRpcData.findTypeGlobally
])
impl.generateImplMembers(new JsonRpcDataTransformationContext(context))
generateToString(impl, context)
val shouldIncludeSuper = impl.extendedClass.type != Object.newTypeReference.type
val equalsHashCodeUtil = new EqualsHashCodeProcessor.Util(context)
val fields = impl.declaredFields.filter[!static]
equalsHashCodeUtil.addEquals(impl, fields, shouldIncludeSuper)
equalsHashCodeUtil.addHashCode(impl, fields, shouldIncludeSuper)
return impl
}
protected def void generateImplMembers(MutableClassDeclaration impl,
extension JsonRpcDataTransformationContext context) {
impl.declaredFields.filter [
!static
].forEach [ field |
val accessorsUtil = new AccessorsProcessor.Util(context)
val deprecated = field.findAnnotation(Deprecated.findTypeGlobally)
accessorsUtil.addGetter(field, Visibility.PUBLIC)
val hasNonNull = field.findAnnotation(NonNull.newTypeReference.type) !== null
val hasJsonAdapter = field.findAnnotation(JsonAdapter.newTypeReference.type) !== null
impl.findDeclaredMethod(accessorsUtil.getGetterName(field)) => [
docComment = field.docComment
if (hasNonNull) {
addAnnotation(newAnnotationReference(NonNull))
}
if (deprecated !== null)
addAnnotation(newAnnotationReference(Deprecated))
]
if (!field.type.inferred) {
accessorsUtil.addSetter(field, Visibility.PUBLIC)
val setterName = accessorsUtil.getSetterName(field)
impl.findDeclaredMethod(setterName, field.type) => [
docComment = field.docComment
if (hasNonNull) {
val parameter = parameters.head
parameter.addAnnotation(newAnnotationReference(NonNull))
body = '''
this.«field.simpleName» = «getPreconditionsUtil(impl, context)».checkNotNull(«parameter.simpleName», "«field.simpleName»");
'''
}
if (deprecated !== null)
addAnnotation(newAnnotationReference(Deprecated))
]
val childTypes = field.type.childTypes
if (!childTypes.empty) {
val jsonTypes = childTypes.map[type.jsonType].toList
if (jsonTypes.size !== jsonTypes.toSet.size) {
// If there is a JsonAdapter on the field, the warning is expected to be unneeded because
// the adapter will handle the resolution of the either
if (!hasJsonAdapter) {
field.addWarning('''The json types of an Either must be distinct.''')
}
} else {
for (childType : childTypes) {
field.addEitherSetter(setterName, childType, context)
}
}
}
}
]
}
protected def void addEitherSetter(
MutableFieldDeclaration field,
String setterName,
EitherTypeArgument argument,
extension JsonRpcDataTransformationContext context
) {
field.declaringType.addMethod(setterName) [ method |
method.primarySourceElement = field.primarySourceElement
method.addParameter(field.simpleName, argument.type)
method.static = field.static
method.visibility = Visibility.PUBLIC
method.returnType = primitiveVoid
method.body = [ctx|compileEitherSetterBody(field, argument, field.simpleName, ctx, context)]
]
}
protected def CharSequence compileEitherSetterBody(
MutableFieldDeclaration field,
EitherTypeArgument argument,
String variableName,
extension CompilationContext compilationContext,
extension JsonRpcDataTransformationContext context
) {
val hasNonNull = field.findAnnotation(NonNull.newTypeReference.type) !== null
val newVariableName = '_' + variableName
val CharSequence compileNewEither = '''«eitherType.toJavaCode».for«IF argument.right»Right«ELSE»Left«ENDIF»(«variableName»)'''
'''
if («variableName» == null) {
«IF hasNonNull»
«getPreconditionsUtil(field.declaringType, context)».checkNotNull(«variableName», "«field.simpleName»");
«ENDIF»
this.«field.simpleName» = null;
return;
}
«IF argument.parent !== null»
final «argument.parent.type.toJavaCode» «newVariableName» = «compileNewEither»;
«compileEitherSetterBody(field, argument.parent, newVariableName, compilationContext, context)»
«ELSE»
this.«field.simpleName» = «compileNewEither»;
«ENDIF»
'''
}
protected def generateToString(MutableClassDeclaration impl, extension TransformationContext context) {
val toStringFields = newArrayList
var ClassDeclaration c = impl
do {
toStringFields += c.declaredFields
c = c.extendedClass?.type as ClassDeclaration
} while (c !== null && c != object)
impl.addMethod("toString") [
returnType = string
addAnnotation(newAnnotationReference(Override))
addAnnotation(newAnnotationReference(Pure))
val accessorsUtil = new AccessorsProcessor.Util(context)
body = '''
«ToStringBuilder» b = new «ToStringBuilder»(this);
«FOR field : toStringFields»
b.add("«field.simpleName»", «IF field.declaringType == impl»this.«field.simpleName»«ELSE»«
accessorsUtil.getGetterName(field)»()«ENDIF»);
«ENDFOR»
return b.toString();
'''
]
}
private def getPreconditionsUtil(Type type, extension TransformationContext context) {
if (type.qualifiedName.startsWith('org.eclipse.lsp4j.debug'))
newTypeReference('org.eclipse.lsp4j.debug.util.Preconditions')
else
newTypeReference('org.eclipse.lsp4j.util.Preconditions')
}
}