package org.corfudb.annotations;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.*;
import org.corfudb.runtime.object.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
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.tools.Diagnostic;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/** The annotation processor, which takes annotated Corfu objects and
* generates a class which can be used by the runtime instead of requiring
* runtime instrumentation.
*
* See README.md for details on how the annotation processor works and how to
* use it.
*
* Created by mwei on 11/9/16.
*/
@SupportedAnnotationTypes("org.corfudb.annotations.*")
public class ObjectAnnotationProcessor extends AbstractProcessor {
private Elements elementUtils;
private Filer filer;
private Messager messager;
// $ needs to be escaped, so we use _ for fields.
private static final String CORFUSMR_FIELD = "_CORFUSMR";
/** Always support the latest source version.
*
* @return The source version supported.
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
/**
* {@inheritDoc}
*
* @param processingEnv
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
/**
* {@inheritDoc}
*
* @param annotations
* @param roundEnv
*/
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
roundEnv.getRootElements().stream()
.filter(x -> x.getKind() == ElementKind.CLASS)
// Don't re-instrument instrumented classes
.filter(x -> !x.getSimpleName().toString().endsWith(ICorfuSMR.CORFUSMR_SUFFIX))
.map(x -> (TypeElement) x)
.forEach(this::processClass);
} catch (Exception e) {
messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage());
}
return true;
}
/** Process each class, checking if the class is annotated and
* generating the appropiate proxy class.
* @param classElement The element to process.
*/
public void processClass(TypeElement classElement) {
// Does the class contain annotated elements? If so,
// generate a new proxy file and process
if ( classElement.getAnnotation(CorfuObject.class) != null) {
try {
generateProxy(classElement);
} catch (IOException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "Failed to process class "
+ classElement.getSimpleName() + " with IOException");
}
}
// deal with any inner classes
classElement.getEnclosedElements().stream()
.filter(x -> x instanceof TypeElement)
.map(x -> (TypeElement) x)
.forEach(this::processClass);
}
/** Return a SMR function name, extracting it from the annotations if available.
*
* @param smrMethod The ExecutableElement to extract the name from.
* @return An SMR Function name string.
*/
public String getSMRFunctionName(ExecutableElement smrMethod) {
MutatorAccessor mutatorAccessor = smrMethod.getAnnotation(MutatorAccessor.class);
Mutator mutator = smrMethod.getAnnotation(Mutator.class);
return (mutator != null && !mutator.name().equals("")) ? mutator.name() :
(mutatorAccessor != null && !mutatorAccessor.name().equals("")) ?
mutatorAccessor.name() : smrMethod.getSimpleName().toString();
}
/** Class to hold data about SMR Methods. */
class SMRMethodInfo {
/** The method itself. */
final ExecutableElement method;
/** The interface, if present, which provided the element. */
final TypeElement interfaceOverride;
/** If the element should be excluded from instrumentation. */
final boolean doNotAdd;
/** If the element has conflictParameter annotations. */
final boolean hasConflictAnnotations;
/** If the element has a function to calculate conflict params. */
final String conflictFunction;
public SMRMethodInfo(ExecutableElement method,
TypeElement interfaceOverride) {
this(method, interfaceOverride, false);
}
public SMRMethodInfo(ExecutableElement method,
TypeElement interfaceOverride,
boolean doNotAdd) {
this.method = method;
this.interfaceOverride = interfaceOverride;
this.doNotAdd = doNotAdd;
this.hasConflictAnnotations = checkForConflictAnnotations(method);
Accessor accessor = method.getAnnotation(Accessor.class);
MutatorAccessor mutatorAccessor = method.getAnnotation(MutatorAccessor.class);
Mutator mutator = method.getAnnotation(Mutator.class);
// Check if there is a conflictFunction
if (accessor != null && !accessor.conflictParameterFunction().equals("")) {
conflictFunction = accessor.conflictParameterFunction();
}else if (mutator != null && !mutator.conflictParameterFunction().equals("")) {
conflictFunction = mutator.conflictParameterFunction();
}
else if (mutatorAccessor != null && !mutatorAccessor.conflictParameterFunction().equals("")) {
conflictFunction = mutatorAccessor.conflictParameterFunction();
} else {
conflictFunction = null;
}
if (conflictFunction != null && hasConflictAnnotations) {
messager.printMessage(Diagnostic.Kind.ERROR, "Method " + method.getSimpleName() +
" cannot have both conflict annotations and conflict function '" + conflictFunction + "'");
}
}
/** Return whether the given method has conflict annotations.
* @param method The method to check for.
* @return True, if conflict annotations are present.
*/
private boolean checkForConflictAnnotations(ExecutableElement method) {
return method.getParameters().stream()
.anyMatch(x -> x.getAnnotation(ConflictParameter.class)
!= null);
}
}
/** Generate a proxy file.
*
* @param classElement The element to generate a proxy file for
* @throws IOException If we could not generate the proxy file
*/
public void generateProxy(TypeElement classElement)
throws IOException
{
// Extract the package name for the class. We'll need this to generate the proxy file.
String packageName = elementUtils.getPackageOf(classElement).toString();
// Calculate the name of the proxy, which appends $CORFUSMR to the class name.
ClassName proxyName = classElement.getNestingKind() == NestingKind.TOP_LEVEL ?
// Top level classes can just use the name
ClassName.bestGuess(classElement.getSimpleName() + ICorfuSMR.CORFUSMR_SUFFIX) :
// Otherwise we need to include the enclosing element as well.
ClassName.bestGuess(classElement.getEnclosingElement().getSimpleName()
+ "$" + classElement.getSimpleName() + ICorfuSMR.CORFUSMR_SUFFIX);
// Also get the class name of the original class.
TypeName originalName = ParameterizedTypeName.get(classElement.asType());
// Generate the proxy class. The proxy class extends the original class and implements
// ICorfuSMR.
TypeSpec.Builder typeSpecBuilder = TypeSpec
.classBuilder(proxyName)
.addTypeVariables(classElement.getTypeParameters()
.stream()
.map(TypeVariableName::get)
.collect(Collectors.toList())
)
.addSuperinterface(ParameterizedTypeName
.get(ClassName.get(ICorfuSMR.class), originalName))
.superclass(originalName)
.addModifiers(Modifier.PUBLIC);
// But we also will want a wrapper for every public constructor as well.
classElement.getEnclosedElements().stream()
.filter(x -> x.getKind() == ElementKind.CONSTRUCTOR)
.map(x -> (ExecutableElement) x)
.forEach(x -> {
typeSpecBuilder.addMethod(MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameters(x.getParameters().stream()
.map(param -> ParameterSpec.builder(
TypeName.get(param.asType()),
param.getSimpleName().toString()
).build())
.collect(Collectors.toSet())
)
.addStatement("super($L)",
x.getParameters().stream()
.map(param -> param.getSimpleName().toString())
.collect(Collectors.joining(", "))
)
.build()
);
});
// Add the proxy field and an accessor/setter, which manages the state of the object.
typeSpecBuilder.addField(ParameterizedTypeName.get(ClassName.get(ICorfuSMRProxy.class),
originalName), "proxy" + CORFUSMR_FIELD, Modifier.PUBLIC);
typeSpecBuilder.addMethod(MethodSpec.methodBuilder("getCorfuSMRProxy")
.addModifiers(Modifier.PUBLIC)
.returns(ParameterizedTypeName.get(ClassName.get(ICorfuSMRProxy.class),
originalName))
.addStatement("return $L", "proxy" + CORFUSMR_FIELD)
.build());
typeSpecBuilder.addMethod(MethodSpec.methodBuilder("setCorfuSMRProxy")
.addParameter(ParameterizedTypeName.get(ClassName.get(ICorfuSMRProxy.class),
originalName), "proxy")
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addStatement("this.$L = proxy", "proxy" + CORFUSMR_FIELD)
.build());
// Gather the set of methods for from this class.
final Set<SMRMethodInfo> methodSet = classElement.getEnclosedElements().stream()
.filter(x -> x.getKind() == ElementKind.METHOD)
.map(x -> (ExecutableElement) x)
.filter(x -> !x.getModifiers().contains(Modifier.STATIC)) // don't want static methods
.map(x -> new SMRMethodInfo(x, null))
.collect(Collectors.toCollection(HashSet::new));
// Deal with the inheritance tree for this class.
TypeElement superClassElement = classElement;
do {
// Get the next superClass.
superClassElement = ((TypeElement)((DeclaredType)superClassElement.getSuperclass()).asElement());
boolean samePackage = ClassName.get(superClassElement).packageName().equals(
ClassName.get(classElement).packageName());
superClassElement.getEnclosedElements().stream()
.filter(x -> x.getKind() == ElementKind.METHOD)
.map(x -> (ExecutableElement) x)
.filter(x -> !x.getModifiers().contains(Modifier.FINAL)) // Can't override final
.filter(x -> !x.getModifiers().contains(Modifier.STATIC)) // or static...
//or protected, when we're not in the same package.
.filter(x -> samePackage || !x.getModifiers().contains(Modifier.PROTECTED))
//only public, or protected from same package filtered above
.filter(x -> x.getModifiers().contains(Modifier.PUBLIC) ||
x.getModifiers().contains(Modifier.PROTECTED))
.forEach(x -> {
if (methodSet.stream().noneMatch(y ->
// If this method is present in the parent, the string
// will match.
y.method.toString().equals(x.toString()))) {
methodSet.add(new SMRMethodInfo(x, null));
}
});
// Terminate if the current superClass is java.lang.Object.
} while (!ClassName.get(superClassElement).equals(ClassName.OBJECT));
// Deal with interfaces.
List<TypeMirror> allInterfaces = new ArrayList<>();
List<TypeMirror> visitedInterfaces = new ArrayList<>();
allInterfaces.addAll(classElement.getInterfaces());
while (allInterfaces.stream()
.filter(x -> !visitedInterfaces.contains(x))
.map(x -> (TypeElement) ((DeclaredType)x).asElement())
.filter(x -> x.getInterfaces().size() > 0)
.count() > 0
) {
List<TypeMirror> tInterfaces = new ArrayList<>();
allInterfaces.stream()
.filter(x -> !visitedInterfaces.contains(x))
.filter(x -> ((TypeElement) ((DeclaredType)x).asElement()).getInterfaces().size() > 0)
.forEach(x -> {
TypeElement t = (TypeElement) ((DeclaredType)x).asElement();
tInterfaces.addAll(t.getInterfaces());
visitedInterfaces.add(x);
});
allInterfaces.addAll(tInterfaces);
}
Set<TypeName> interfacesToAdd = new HashSet<>();
allInterfaces.forEach(iface -> {
TypeElement ifaceElement = (TypeElement) ((DeclaredType)iface).asElement();
// need to traverse the interface inheritance tree.
ifaceElement.getEnclosedElements().stream()
.filter(x -> x.getKind() == ElementKind.METHOD)
.map(x -> (ExecutableElement) x)
.forEach(method -> {
Optional<SMRMethodInfo> overwrite =
methodSet.stream()
.filter(x -> method.toString().equals(x.method.toString()))
.findFirst();
if (method.getAnnotation(Accessor.class) != null ||
method.getAnnotation(Mutator.class) != null ||
method.getAnnotation(MutatorAccessor.class) != null ||
method.getAnnotation(PassThrough.class) != null) {
if (overwrite.isPresent()) {
methodSet.remove(overwrite.get());
methodSet.add(new SMRMethodInfo(method, null));
} else {
methodSet.add(new SMRMethodInfo(method, ifaceElement));
}
}
else if (method.getAnnotation(InterfaceOverride.class) != null) {
interfacesToAdd.add(ParameterizedTypeName.get(ifaceElement.asType()));
if (overwrite.isPresent()) {
methodSet.remove(overwrite.get());
}
methodSet.add(new SMRMethodInfo(method,
ifaceElement));
}
// if we have a implementation, and we aren't already
// implemented by the object.
else if (
method.getModifiers().contains(Modifier.DEFAULT) &&
methodSet.stream().noneMatch(x -> x.method.toString()
.equals(method.toString()))){
methodSet.add(new SMRMethodInfo(method,
ifaceElement, true));
}
});
});
// remove any methods which end with $CORFUSMR
methodSet.removeIf(x -> x.method.getSimpleName()
.toString().endsWith(ICorfuSMR.CORFUSMR_SUFFIX));
// Verify that all methods that require an up call have specified as annotated name
checkAnnotatedNames(methodSet);
// Gather methods that require upcalls
Set<SMRMethodInfo> upCalls = methodSet.stream()
.filter(x -> ((x.method.getAnnotation(Mutator.class) != null)
&& !x.method.getAnnotation(Mutator.class).noUpcall())
|| ((x.method.getAnnotation(MutatorAccessor.class) != null)
&& !x.method.getAnnotation(MutatorAccessor.class).noUpcall()))
.collect(Collectors.toCollection(HashSet::new));
checkOverloadConflicts(upCalls);
// Gather methods that reference upcall methods (i.e. mutators that require no upcalls)
Set<SMRMethodInfo> noUpcalls = methodSet.stream()
.filter(x -> ((x.method.getAnnotation(Mutator.class) != null)
&& x.method.getAnnotation(Mutator.class).noUpcall())
|| ((x.method.getAnnotation(MutatorAccessor.class) != null)
&& x.method.getAnnotation(MutatorAccessor.class).noUpcall()))
.collect(Collectors.toCollection(HashSet::new));
verifyNoUpCallReference(noUpcalls, upCalls);
// Generate wrapper classes.
methodSet.stream()
.filter(x -> !x.doNotAdd)
.forEach(m -> {
ExecutableElement smrMethod = m.method;
// Extract each annotation
Accessor accessor = smrMethod.getAnnotation(Accessor.class);
MutatorAccessor mutatorAccessor = smrMethod.getAnnotation(MutatorAccessor.class);
Mutator mutator = smrMethod.getAnnotation(Mutator.class);
TransactionalMethod transactional = smrMethod.getAnnotation(TransactionalMethod.class);
// Override the method we will proxy.
MethodSpec.Builder ms = MethodSpec.overriding(smrMethod);
// This is a hack, but necessary since the modifier list is immutable
// and we need to remove the "native" and "default" modifiers.
try {
Field f = ms.getClass().getDeclaredField("modifiers");
f.setAccessible(true);
((List<Modifier>)f.get(ms)).remove(Modifier.NATIVE);
((List<Modifier>)f.get(ms)).remove(Modifier.DEFAULT);
} catch (Exception e) {
messager.printMessage(Diagnostic.Kind.ERROR, "error trying to change methodspec"
+ e.getMessage());
}
// If there is conflict information, calculate the conflict set
boolean hasConflictData = false;
final String conflictField = "conflictField" + CORFUSMR_FIELD;
if (m.conflictFunction != null) {
hasConflictData = true;
addConflictFieldFromFunctionToMethod(ms, conflictField, m.conflictFunction, smrMethod);
}
else if (m.hasConflictAnnotations) {
hasConflictData = true;
addConflictFieldToMethod(ms, conflictField, smrMethod);
}
// If a mutator, then log the update.
if (mutator != null || mutatorAccessor != null) {
ms.addStatement(
(mutatorAccessor != null ? "long address" + CORFUSMR_FIELD + " = " : "") +
"proxy" + CORFUSMR_FIELD + ".logUpdate($S,$L,$L$L$L)",
getSMRFunctionName(smrMethod),
// Don't need upcall result for mutators
mutator != null ? "false" :
// or mutatorAccessors which return void.
smrMethod.getReturnType().getKind().equals(TypeKind.VOID) ? "false" : "true",
hasConflictData ? conflictField : "null",
smrMethod.getParameters().size() > 0 ? "," : "",
smrMethod.getParameters().stream()
.map(VariableElement::getSimpleName)
.collect(Collectors.joining(", ")));
}
// If an accessor (or not annotated), return the object by doing the underlying call.
if (mutatorAccessor != null) {
// If the return to the mutatorAccessor is void, we don't need
// to do anything...
if (!smrMethod.getReturnType().getKind().equals(TypeKind.VOID)) {
ms.addStatement("return (" +
ParameterizedTypeName.get(smrMethod.getReturnType())
+ ") proxy" + CORFUSMR_FIELD
+ ".getUpcallResult(address"
+ CORFUSMR_FIELD
+ ", "
+ (m.hasConflictAnnotations ?
conflictField: "null")
+ ")");
}
}
// If transactional, begin the transaction
else if (transactional != null) {
ms.addCode(smrMethod.getReturnType().getKind().equals(TypeKind.VOID) ?
"proxy" + CORFUSMR_FIELD + ".TXExecute(() -> {":
"return proxy" + CORFUSMR_FIELD + ".TXExecute(() -> {"
);
ms.addStatement("$Lsuper.$L($L)",
smrMethod.getReturnType().getKind().equals(TypeKind.VOID) ?
"":
"return ",
smrMethod.getSimpleName(),
smrMethod.getParameters().stream()
.map(VariableElement::getSimpleName)
.collect(Collectors.joining(", ")));
ms.addCode(smrMethod.getReturnType().getKind().equals(TypeKind.VOID) ?
"return null; });":
"});"
);
}
else if (smrMethod.getAnnotation(PassThrough.class) != null) {
ms.addStatement("$L super.$L($L)",
smrMethod.getReturnType().getKind().equals(TypeKind.VOID) ?
"" : "return ",
smrMethod.getSimpleName(),
smrMethod.getParameters().stream()
.map(VariableElement::getSimpleName)
.collect(Collectors.joining(", "))
);
}
else if (smrMethod.getAnnotation(InterfaceOverride.class) != null) {
ms.addStatement("$L$T.super.$L($L)",
smrMethod.getReturnType().getKind().equals(TypeKind.VOID) ?
"" : "return ",
ClassName.get(m.interfaceOverride),
smrMethod.getSimpleName(),
smrMethod.getParameters().stream()
.map(VariableElement::getSimpleName)
.collect(Collectors.joining(", "))
);
}
else if (mutator == null) {
// Otherwise, just force the access to access the underlying call.
ms.addStatement((smrMethod.getReturnType().getKind().equals(TypeKind.VOID) ? "" :
"return ") +"proxy" + CORFUSMR_FIELD + ".access(" +
"o" + CORFUSMR_FIELD + " -> {$Lo" + CORFUSMR_FIELD + ".$L($L);$L}," +
"$L)",
smrMethod.getReturnType().getKind().equals(TypeKind.VOID) ?
"" : "return ",
smrMethod.getSimpleName(),
smrMethod.getParameters().stream()
.map(VariableElement::getSimpleName)
.collect(Collectors.joining(", ")),
smrMethod.getReturnType().getKind().equals(TypeKind.VOID) ?
"return null;" : "",
(m.hasConflictAnnotations ?
conflictField : "null")
);
}
typeSpecBuilder.addMethod(ms.build());
});
addUpcallMap(typeSpecBuilder, originalName, interfacesToAdd, methodSet);
addUndoRecordMap(typeSpecBuilder, originalName, interfacesToAdd, methodSet);
addUndoMap(typeSpecBuilder, originalName, interfacesToAdd, methodSet);
addResetSet(typeSpecBuilder, originalName, interfacesToAdd, methodSet);
typeSpecBuilder
.addSuperinterfaces(interfacesToAdd);
// Mark the object as instrumented, so we don't instrument it again.
typeSpecBuilder
.addAnnotation(AnnotationSpec.builder(InstrumentedCorfuObject.class).build());
JavaFile javaFile = JavaFile
.builder(packageName,
typeSpecBuilder.build())
.build();
javaFile.writeTo(filer);
}
/*
* Verify that methods with a mutator annotation specify the name field
*/
void checkAnnotatedNames(Set<SMRMethodInfo> methodSet) {
for(SMRMethodInfo smrMethodInfo : methodSet) {
if((smrMethodInfo.method.getAnnotation(Mutator.class) != null
&& smrMethodInfo.method.getAnnotation(Mutator.class).name().isEmpty())
|| (smrMethodInfo.method.getAnnotation(MutatorAccessor.class) != null
&& smrMethodInfo.method.getAnnotation(MutatorAccessor.class).name().isEmpty())){
messager.printMessage(Diagnostic.Kind.ERROR, "Method " + smrMethodInfo.method.getSimpleName()
+ " must specify a name in its annotation name field");
}
}
}
String getAnnotationNameField(ExecutableElement method) {
String name = "";
if(method.getAnnotation(Mutator.class) != null) {
name = method.getAnnotation(Mutator.class).name();
} else if (method.getAnnotation(MutatorAccessor.class) != null) {
name = method.getAnnotation(MutatorAccessor.class).name();
}
return name;
}
/*
* Verify that the no upcall methods reference valid upcall methods
*/
void verifyNoUpCallReference(Set<SMRMethodInfo> noUpcalls, Set<SMRMethodInfo> upCalls) {
Set<String> upCallMethodNames = upCalls.stream()
.map(x -> getAnnotationNameField(x.method))
.collect(Collectors.toCollection(HashSet::new));
for(SMRMethodInfo smrMethodInfo : noUpcalls) {
String methodName = getAnnotationNameField(smrMethodInfo.method);
if(!upCallMethodNames.contains(methodName)){
messager.printMessage(Diagnostic.Kind.ERROR,
"Error " + smrMethodInfo.method.toString() +" referencing unknown smr method ");
}
}
}
/**
* Verify that no two methods have the same annotation name
* @param possibleConflictMethods Methods that are mutators and require upcalls
*/
void checkOverloadConflicts(Set<SMRMethodInfo> possibleConflictMethods) {
Set<SMRMethodInfo> allMethods = new HashSet<>(possibleConflictMethods);
for(SMRMethodInfo smrMethodInfo : possibleConflictMethods) {
Set<SMRMethodInfo> setDiff = new HashSet<>(allMethods);
setDiff.remove(smrMethodInfo);
String baseMethodName = getAnnotationNameField(smrMethodInfo.method);
for(SMRMethodInfo smrMethodInfo2 : setDiff) {
String methodName = getAnnotationNameField(smrMethodInfo2.method);
if(baseMethodName.equals(methodName)){
messager.printMessage(Diagnostic.Kind.ERROR,
"Error overloading with annotation name "
+ smrMethodInfo.method.toString() + " and " + smrMethodInfo2.method.toString());
}
}
}
}
/** Add the reset set and the getter for the set.
*
* @param typeSpecBuilder The typespec builder to add the reset set to
* @param originalName The name of the original base type (without $CORFUSMR)
* @param interfacesToAdd A list of interfaces to add for instrumentation.
* @param methodSet The set of methods to add for instrumentation.
*/
private void addResetSet(TypeSpec.Builder typeSpecBuilder, TypeName originalName,
Set<TypeName> interfacesToAdd, Set<SMRMethodInfo> methodSet) {
// Generate the initializer for the reset set.
String resetString = methodSet.stream()
.filter(x -> x.method.getAnnotation(Mutator.class) != null
&& x.method.getAnnotation(Mutator.class).reset()||
x.method.getAnnotation(MutatorAccessor.class) != null
&& x.method.getAnnotation(MutatorAccessor.class).reset())
.map(x -> "\n.add(\"" + getSMRFunctionName(x.method) + "\")")
.collect(Collectors.joining());
FieldSpec resetSet = FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(Set.class),
ClassName.get(String.class)), "resetSet" + CORFUSMR_FIELD, Modifier.FINAL, Modifier.PUBLIC)
.initializer("new $T()$L.build()",
ParameterizedTypeName.get(ClassName.get(ImmutableSet.Builder.class),
ClassName.get(String.class)), resetString)
.build();
typeSpecBuilder.addField(resetSet);
typeSpecBuilder.addMethod(MethodSpec.methodBuilder("getCorfuResetSet")
.addModifiers(Modifier.PUBLIC)
.returns(ParameterizedTypeName.get(ClassName.get(Set.class),
ClassName.get(String.class)))
.addStatement("return $L", "resetSet" + CORFUSMR_FIELD)
.build());
}
private void addUpcallMap(TypeSpec.Builder typeSpecBuilder, TypeName originalName,
Set<TypeName> interfacesToAdd, Set<SMRMethodInfo> methodSet) {
// Generate the upcall string and associated map.
String upcallString = methodSet.stream()
.filter(x -> x.method.getAnnotation(MutatorAccessor.class) != null ||
(x.method.getAnnotation(Mutator.class) != null &&
!x.method.getAnnotation(Mutator.class).noUpcall()))
.map(x -> "\n.put(\"" + getSMRFunctionName(x.method) + "\", " +
"(obj, args) -> { " + (x.method.getReturnType().getKind().equals(TypeKind.VOID)
? "" : "return ") + "obj." + x.method.getSimpleName() + "(" +
IntStream.range(0, x.method.getParameters().size())
.mapToObj(i ->
"(" + x.method.getParameters().get(i).asType().toString() + ")" +
" args[" + i + "]")
.collect(Collectors.joining(", "))
+ ");" + (x.method.getReturnType().getKind().equals(TypeKind.VOID)
? "return null;" : "") + "})")
.collect(Collectors.joining());
FieldSpec upcallMap = FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(ICorfuSMRUpcallTarget.class),
originalName)), "upcallMap" + CORFUSMR_FIELD,
Modifier.PUBLIC, Modifier.FINAL)
.initializer("new $T()$L.build()",
ParameterizedTypeName.get(ClassName.get(ImmutableMap.Builder.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(ICorfuSMRUpcallTarget.class),
originalName)), upcallString)
.build();
typeSpecBuilder
.addField(upcallMap);
typeSpecBuilder.addMethod(MethodSpec.methodBuilder("getCorfuSMRUpcallMap")
.addModifiers(Modifier.PUBLIC)
.returns(ParameterizedTypeName.get(ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(ICorfuSMRUpcallTarget.class),
originalName)))
.addStatement("return $L", "upcallMap" + CORFUSMR_FIELD)
.build());
}
/** Generate the undo record map for this type. */
private void addUndoRecordMap(TypeSpec.Builder typeSpecBuilder, TypeName originalName,
Set<TypeName> interfacesToAdd, Set<SMRMethodInfo> methodSet)
{
// And generate a map for the undoRecord and undo functions, if available.
// We may have to add additional interfaces during this process.
// Generate the undo string and associated map.
String undoRecordString = methodSet.stream()
.filter(x -> x.method.getAnnotation(MutatorAccessor.class) != null ||
x.method.getAnnotation(Mutator.class) != null)
.filter(x -> x.method.getAnnotation(MutatorAccessor.class) == null ||
!x.method.getAnnotation(MutatorAccessor.class).undoRecordFunction().equals(""))
.filter(x -> x.method.getAnnotation(Mutator.class) == null ||
!x.method.getAnnotation(Mutator.class).undoRecordFunction().equals(""))
.map(x -> {
MutatorAccessor mutatorAccessor = x.method.getAnnotation(MutatorAccessor.class);
Mutator mutator = x.method.getAnnotation(Mutator.class);
String undoRecordFunction = mutator == null ? mutatorAccessor.undoRecordFunction() :
mutator.undoRecordFunction();
Optional<SMRMethodInfo> mi = methodSet.stream()
.filter(y ->
y.method.getSimpleName().toString().equals(undoRecordFunction))
.findFirst();
// Don't generate a record since we don't have a matching method
if (!mi.isPresent())
{
messager.printMessage(Diagnostic.Kind.MANDATORY_WARNING, "No undoRecord" +
" method found for "
+ x.method.getSimpleName() + " named " +undoRecordFunction);
return "";
}
// Check that the signature matches what we expect. (1+original)
if (mi.get().method.getParameters().size() !=
x.method.getParameters().size() + 1) {
messager.printMessage(Diagnostic.Kind.ERROR, "undoRecord method "
+ undoRecordFunction + " contained the wrong number of parameters");
}
// Add the interface, if present.
if (mi.get().interfaceOverride != null) {
interfacesToAdd.add(ParameterizedTypeName
.get(mi.get().interfaceOverride.asType()));
}
String callingConvention = mi.get().interfaceOverride == null ? "this." :
mi.get().interfaceOverride.getSimpleName() + ".super.";
return "\n.put(\"" + getSMRFunctionName(x.method) + "\", " +
"(obj, args) -> { return "
+ callingConvention + undoRecordFunction + "(obj," +
IntStream.range(0, x.method.getParameters().size())
.mapToObj(i ->
"(" + mi.get().method.getParameters().get(i+1).asType().toString() + ")" +
" args[" + i + "]")
.collect(Collectors.joining(", "))
+ ");" + "})";})
.collect(Collectors.joining());
FieldSpec undoRecordMap = FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(IUndoRecordFunction.class),
originalName)), "undoRecordMap" + CORFUSMR_FIELD,
Modifier.PUBLIC, Modifier.FINAL)
.initializer("new $T()$L.build()",
ParameterizedTypeName.get(ClassName.get(ImmutableMap.Builder.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(IUndoRecordFunction.class),
originalName)), undoRecordString)
.build();
typeSpecBuilder.addField(undoRecordMap);
typeSpecBuilder.addMethod(MethodSpec.methodBuilder("getCorfuUndoRecordMap")
.addModifiers(Modifier.PUBLIC)
.returns(ParameterizedTypeName.get(ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(IUndoRecordFunction.class),
originalName)))
.addStatement("return $L", "undoRecordMap" + CORFUSMR_FIELD)
.build());
}
/** Generate the undo string and associated map for the type. */
private void addUndoMap(TypeSpec.Builder typeSpecBuilder, TypeName originalName,
Set<TypeName> interfacesToAdd, Set<SMRMethodInfo> methodSet) {
String undoString = methodSet.stream()
.filter(x -> x.method.getAnnotation(MutatorAccessor.class) != null ||
x.method.getAnnotation(Mutator.class) != null)
.filter(x -> x.method.getAnnotation(MutatorAccessor.class) == null ||
!x.method.getAnnotation(MutatorAccessor.class).undoRecordFunction().equals(""))
.filter(x -> x.method.getAnnotation(Mutator.class) == null ||
!x.method.getAnnotation(Mutator.class).undoRecordFunction().equals(""))
.map(x -> {
MutatorAccessor mutatorAccessor = x.method.getAnnotation(MutatorAccessor.class);
Mutator mutator = x.method.getAnnotation(Mutator.class);
String undoFunction = mutator == null ? mutatorAccessor.undoFunction() :
mutator.undoFunction();
Optional<SMRMethodInfo> mi = methodSet.stream()
.filter(y ->
y.method.getSimpleName().toString().equals(undoFunction))
.findFirst();
// Don't generate a record since we don't have a matching method
if (!mi.isPresent())
{
messager.printMessage(Diagnostic.Kind.MANDATORY_WARNING, "No undo method found for "
+ x.method.getSimpleName() + " named " +undoFunction);
return "";
}
// Check that the signature matches what we expect. (2+original)
if (mi.get().method.getParameters().size() !=
x.method.getParameters().size() + 2) {
messager.printMessage(Diagnostic.Kind.ERROR, "undoRecord method "
+ undoFunction + " contained the wrong number of parameters");
}
// Add the interface, if present.
if (mi.get().interfaceOverride != null) {
interfacesToAdd.add(ParameterizedTypeName
.get(mi.get().interfaceOverride.asType()));
}
String callingConvention = mi.get().interfaceOverride == null ? "this." :
mi.get().interfaceOverride.getSimpleName() + ".super.";
return "\n.put(\"" + getSMRFunctionName(x.method) + "\", " +
"(obj, undoRecord, args) -> {" + callingConvention
+ undoFunction + "(obj, (" +
ParameterizedTypeName.get(mi.get().method.getParameters().get(1).asType())
+ ") undoRecord, " +
IntStream.range(0, x.method.getParameters().size())
.mapToObj(i ->
"(" + mi.get().method.getParameters().get(i+2).asType().toString() + ")" +
" args[" + i + "]")
.collect(Collectors.joining(", "))
+ ");})";
})
.collect(Collectors.joining());
FieldSpec undoMap = FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(IUndoFunction.class),
originalName)), "undoMap" + CORFUSMR_FIELD,
Modifier.PUBLIC, Modifier.FINAL)
.initializer("new $T()$L.build()",
ParameterizedTypeName.get(ClassName.get(ImmutableMap.Builder.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(IUndoFunction.class),
originalName)), undoString)
.build();
typeSpecBuilder.addField(undoMap);
typeSpecBuilder.addMethod(MethodSpec.methodBuilder("getCorfuUndoMap")
.addModifiers(Modifier.PUBLIC)
.returns(ParameterizedTypeName.get(ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(IUndoFunction.class),
originalName)))
.addStatement("return $L", "undoMap" + CORFUSMR_FIELD)
.build());
}
/** Add a conflict field to the method.
*
* @param ms The builder to add the field to.
* @param conflictField The name of the field to add
* @param smrMethod The method element.
*/
public void addConflictFieldToMethod(MethodSpec.Builder ms, String conflictField,
ExecutableElement smrMethod) {
ms.addStatement("$T $L = new Object[]{$L}", Object[].class, conflictField,
smrMethod.getParameters()
.stream()
.filter(y -> y.getAnnotation(ConflictParameter.class)
!= null)
.map(y -> y.getSimpleName())
.collect(Collectors.joining(", ")));
}
/** Add a conflict field to the method.
*
* @param ms The builder to add the field to.
* @param conflictField The name of the field to add
* @param functionName The function to call to obtain the parameters.
* @param smrMethod The method element.
*/
public void addConflictFieldFromFunctionToMethod(MethodSpec.Builder ms, String conflictField, String functionName,
ExecutableElement smrMethod) {
ms.addStatement("$T $L = $L($L)", Object[].class, conflictField, functionName,
smrMethod.getParameters()
.stream()
.map(y -> y.getSimpleName())
.collect(Collectors.joining(", ")));
}
}