blob: 2d76f5a0e9135aff37ddeeea50d6892cb9e49654 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.qpid.server.protocol.v1_0;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import org.apache.qpid.server.License;
public class CompositeTypeConstructorGenerator extends AbstractProcessor
{
private static final List<String> RESTRICTED_TYPES = Arrays.asList(
"org.apache.qpid.server.protocol.v1_0.type.transport.AmqpError",
"org.apache.qpid.server.protocol.v1_0.type.transport.ConnectionError",
"org.apache.qpid.server.protocol.v1_0.type.transport.SessionError",
"org.apache.qpid.server.protocol.v1_0.type.transport.LinkError",
"org.apache.qpid.server.protocol.v1_0.type.transaction.TransactionErrors",
"org.apache.qpid.server.protocol.v1_0.type.transport.ReceiverSettleMode",
"org.apache.qpid.server.protocol.v1_0.type.transport.SenderSettleMode",
"org.apache.qpid.server.protocol.v1_0.type.transport.Role",
"org.apache.qpid.server.protocol.v1_0.type.security.SaslCode",
"org.apache.qpid.server.protocol.v1_0.type.extensions.soleconn.SoleConnectionDetectionPolicy",
"org.apache.qpid.server.protocol.v1_0.type.extensions.soleconn.SoleConnectionEnforcementPolicy",
"org.apache.qpid.server.protocol.v1_0.type.messaging.StdDistMode",
"org.apache.qpid.server.protocol.v1_0.type.messaging.TerminusDurability",
"org.apache.qpid.server.protocol.v1_0.type.messaging.TerminusExpiryPolicy",
"org.apache.qpid.server.protocol.v1_0.type.transaction.TxnCapability");
@Override
public SourceVersion getSupportedSourceVersion()
{
return SourceVersion.latest();
}
@Override
public Set<String> getSupportedAnnotationTypes()
{
return Collections.singleton(CompositeType.class.getName());
}
@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnvironment)
{
if(roundEnvironment.processingOver())
{
return true;
}
Filer filer = processingEnv.getFiler();
try
{
for (Element e : roundEnvironment.getElementsAnnotatedWith(CompositeType.class))
{
if(e.getKind() == ElementKind.CLASS)
{
generateCompositeTypeConstructor(filer, (TypeElement) e);
}
}
}
catch (Exception e)
{
try(StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw))
{
e.printStackTrace(pw);
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Unexpected Error: " + sw.toString());
}
catch (IOException ioe)
{
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Error: " + ioe.getLocalizedMessage());
}
}
return true;
}
private void generateCompositeTypeConstructor(final Filer filer, final TypeElement typeElement)
{
String objectQualifiedClassName = typeElement.getQualifiedName().toString();
String objectSimpleName = typeElement.getSimpleName().toString();
String compositeTypeConstructorNameSimpleName = objectSimpleName + "Constructor";
PackageElement packageElement = (PackageElement) typeElement.getEnclosingElement();
final String compositeTypeConstructorPackage = packageElement.getQualifiedName() + ".codec";
String compositeTypeConstructorName = compositeTypeConstructorPackage + "." + compositeTypeConstructorNameSimpleName;
final CompositeType annotation = typeElement.getAnnotation(CompositeType.class);
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Generating composite constructor file for " + objectQualifiedClassName);
try
{
JavaFileObject factoryFile = filer.createSourceFile(compositeTypeConstructorName);
PrintWriter pw = new PrintWriter(new OutputStreamWriter(factoryFile.openOutputStream(), "UTF-8"));
pw.println("/*");
for(String headerLine : License.LICENSE)
{
pw.println(" *" + headerLine);
}
pw.println(" */");
pw.println();
pw.print("package ");
pw.print(compositeTypeConstructorPackage);
pw.println(";");
pw.println();
pw.println("import java.util.List;");
pw.println();
pw.println("import org.apache.qpid.server.protocol.v1_0.codec.AbstractCompositeTypeConstructor;");
pw.println("import org.apache.qpid.server.protocol.v1_0.codec.DescribedTypeConstructorRegistry;");
pw.println("import org.apache.qpid.server.protocol.v1_0.type.AmqpErrorException;");
pw.println("import org.apache.qpid.server.protocol.v1_0.type.Symbol;");
pw.println("import org.apache.qpid.server.protocol.v1_0.type.UnsignedLong;");
pw.println("import org.apache.qpid.server.protocol.v1_0.type.transport.AmqpError;");
pw.println("import org.apache.qpid.server.protocol.v1_0.type.transport.Error;");
pw.println("import " + objectQualifiedClassName + ";");
pw.println();
pw.println("public final class " + compositeTypeConstructorNameSimpleName + " extends AbstractCompositeTypeConstructor<"+ objectSimpleName +">");
pw.println("{");
pw.println(" private static final " + compositeTypeConstructorNameSimpleName + " INSTANCE = new " + compositeTypeConstructorNameSimpleName + "();");
pw.println();
pw.println(" public static void register(DescribedTypeConstructorRegistry registry)");
pw.println(" {");
pw.println(" registry.register(Symbol.valueOf(\"" + annotation.symbolicDescriptor() + "\"), INSTANCE);");
pw.println(String.format(" registry.register(UnsignedLong.valueOf(%#016x), INSTANCE);", annotation.numericDescriptor()));
pw.println(" }");
pw.println();
pw.println(" @Override");
pw.println(" protected String getTypeName()");
pw.println(" {");
pw.println(" return " + objectSimpleName + ".class.getSimpleName();");
pw.println(" }");
pw.println();
pw.println(" @Override");
pw.println(" protected " + objectSimpleName + " construct(final FieldValueReader fieldValueReader) throws AmqpErrorException");
pw.println(" {");
pw.println(" " + objectSimpleName + " obj = new " + objectSimpleName + "();");
pw.println();
generateAssigners(pw, typeElement);
pw.println(" return obj;");
pw.println(" }");
pw.println("}");
pw.close();
}
catch (IOException e)
{
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Failed to write composite constructor file: "
+ compositeTypeConstructorName
+ " - "
+ e.getLocalizedMessage());
}
}
private void generateAssigners(final PrintWriter pw, final TypeElement typeElement)
{
Types typeUtils = processingEnv.getTypeUtils();
final List<AnnotatedField> annotatedFields = new ArrayList<>();
for (Element element : typeElement.getEnclosedElements())
{
if (element instanceof VariableElement && element.getKind() == ElementKind.FIELD)
{
boolean annotationFound = false;
for(AnnotationMirror annotationMirror : element.getAnnotationMirrors())
{
if(annotationMirror.getAnnotationType().toString().equals("org.apache.qpid.server.protocol.v1_0.CompositeTypeField"))
{
if (annotationFound)
{
processingEnv.getMessager()
.printMessage(Diagnostic.Kind.ERROR,
String.format(
"More than one CompositeTypeField annotations on field '%s.%s'",
typeElement.getSimpleName(),
element.getSimpleName()));
}
annotationFound = true;
annotatedFields.add(new AnnotatedField((VariableElement) element, annotationMirror));
}
}
}
}
annotatedFields.sort(Comparator.comparingInt(AnnotatedField::getIndex));
for (int index = 0; index < annotatedFields.size(); ++index)
{
AnnotatedField annotatedField = annotatedFields.get(index);
final VariableElement variableElement = annotatedField.getVariableElement();
final String fieldName = stripUnderscore(variableElement.getSimpleName().toString());
if (annotatedField.getIndex() != index)
{
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,String.format(
"Unexpected CompositeTypeField index '%d' is specified on field '%s' of '%s'. Expected %d.",
annotatedField.getIndex(),
fieldName,
typeElement.getSimpleName(),
index));
}
final String baseIndent = " ";
if (variableElement.asType().getKind() == TypeKind.ARRAY)
{
final TypeMirror componentType = ((ArrayType) variableElement.asType()).getComponentType();
final String functionString;
if (annotatedField.getFactory() != null)
{
functionString = "x -> " + annotatedField.getFactory() + "(x)";
}
else if (RESTRICTED_TYPES.contains(componentType))
{
functionString = variableElement.asType().toString() + "::valueOf";
}
else
{
functionString = "x -> (" + componentType + ") x";
}
pw.println(String.format(" %s %s = fieldValueReader.readArrayValue(%d, \"%s\", %s, %s.class, %s);",
annotatedField.getVariableElement().asType(),
fieldName,
index,
fieldName,
annotatedField.isMandatory(),
componentType,
functionString));
optionallyWrapInNullCheck(!annotatedField.isMandatory(), pw, baseIndent, fieldName, indent -> {
pw.println(indent + "obj." + getSetterName(variableElement) + "(" + fieldName + ");");
});
}
else if (annotatedField.getFactory() != null || RESTRICTED_TYPES.contains(variableElement.asType().toString()))
{
String functionName = annotatedField.getFactory() != null ? annotatedField.getFactory() : variableElement.asType().toString() + ".valueOf";
pw.println(String.format(" Object %s = fieldValueReader.readValue(%d, \"%s\", %s, Object.class);",
fieldName,
index,
fieldName,
annotatedField.isMandatory()));
optionallyWrapInNullCheck(!annotatedField.isMandatory(), pw, baseIndent, fieldName, indent -> {
pw.println(indent + "try");
pw.println(indent + "{");
pw.println(indent + " obj." + getSetterName(variableElement) + "(" + functionName + "(" + fieldName + "));");
pw.println(indent + "}");
pw.println(indent + "catch (RuntimeException e)");
pw.println(indent + "{");
pw.println(indent + " Error error = new Error(AmqpError.DECODE_ERROR, \"Could not decode value field '" + fieldName + "' of '" + typeElement.getSimpleName() + "'\");");
pw.println(indent + " throw new AmqpErrorException(error, e);");
pw.println(indent + "}");
});
}
else if (typeUtils.isSameType(typeUtils.erasure(variableElement.asType()),
getErasure(processingEnv, "java.util.Map")))
{
List<? extends TypeMirror> args = ((DeclaredType) variableElement.asType()).getTypeArguments();
if (args.size() != 2)
{
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Map types must have exactly two type arguments");
}
pw.println(String.format(" %s %s = fieldValueReader.readMapValue(%d, \"%s\", %s, %s.class, %s.class);",
annotatedField.getVariableElement().asType(),
fieldName,
index,
fieldName,
annotatedField.isMandatory(),
args.get(0),
args.get(1)));
optionallyWrapInNullCheck(!annotatedField.isMandatory(), pw, baseIndent, fieldName, indent -> {
pw.println(indent + "obj." + getSetterName(variableElement) + "(" + fieldName + ");");
});
}
else
{
pw.println(String.format(" %s %s = fieldValueReader.readValue(%d, \"%s\", %s, %s.class);",
annotatedField.getVariableElement().asType(),
fieldName,
index,
fieldName,
annotatedField.isMandatory(),
annotatedField.getVariableElement().asType()));
optionallyWrapInNullCheck(!annotatedField.isMandatory(), pw, baseIndent, fieldName, indent -> {
pw.println(indent + "obj." + getSetterName(variableElement) + "(" + fieldName + ");");
});
}
pw.println();
}
}
private void optionallyWrapInNullCheck(boolean wrap, PrintWriter pw, String indent, String fieldName, Consumer<String> f)
{
if (wrap)
{
pw.println(indent + "if (" + fieldName + " != null)");
pw.println(indent + "{");
indent = " " + indent;
}
f.accept(indent);
if (wrap)
{
indent = indent.substring(4);
pw.println(indent + "}");
}
}
private String getSetterName(final VariableElement variableElement)
{
final String fieldName = stripUnderscore(variableElement.getSimpleName().toString());
return "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
}
private String stripUnderscore(final String fieldName)
{
if (fieldName.startsWith("_"))
{
return fieldName.substring(1);
}
return fieldName;
}
private static class AnnotatedField
{
private final VariableElement _variableElement;
private final AnnotationMirror _annotationMirror;
private final int _index;
private final String _factory;
private final boolean _mandatory;
public AnnotatedField(final VariableElement variableElement, final AnnotationMirror annotationMirror)
{
_variableElement = variableElement;
_annotationMirror = annotationMirror;
String factory = null;
boolean mandatory = false;
int index = -1;
for (Map.Entry<? extends ExecutableElement,? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet())
{
if ("index".contentEquals(entry.getKey().getSimpleName()))
{
index = (int) entry.getValue().getValue();
}
else if ("deserializationConverter".contentEquals(entry.getKey().getSimpleName()))
{
factory = (String) entry.getValue().getValue();
}
else if ("mandatory".contentEquals(entry.getKey().getSimpleName()))
{
mandatory = (boolean) entry.getValue().getValue();
}
}
_index = index;
_mandatory = mandatory;
_factory = factory;
}
public VariableElement getVariableElement()
{
return _variableElement;
}
public AnnotationMirror getAnnotationMirror()
{
return _annotationMirror;
}
public int getIndex()
{
return _index;
}
public String getFactory()
{
return _factory;
}
public boolean isMandatory()
{
return _mandatory;
}
}
private static TypeMirror getErasure(ProcessingEnvironment processingEnv, final String className)
{
final Types typeUtils = processingEnv.getTypeUtils();
final Elements elementUtils = processingEnv.getElementUtils();
return typeUtils.erasure(elementUtils.getTypeElement(className).asType());
}
}