org.enginehub.piston.gen.CommandParameterInterpreter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of FastAsyncWorldEdit-Libs-Ap Show documentation
Show all versions of FastAsyncWorldEdit-Libs-Ap Show documentation
Blazingly fast Minecraft world manipulation for artists, builders and everyone else.
/*
* Piston, a flexible command management system.
* Copyright (C) EngineHub
* Copyright (C) Piston contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
package org.enginehub.piston.gen;
import com.google.auto.common.MoreElements;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.TypeName;
import org.enginehub.piston.CommandParameters;
import org.enginehub.piston.CommandValue;
import org.enginehub.piston.annotation.param.Arg;
import org.enginehub.piston.annotation.param.ArgFlag;
import org.enginehub.piston.annotation.param.Switch;
import org.enginehub.piston.gen.util.ProcessingException;
import org.enginehub.piston.gen.util.TypeNameUtil;
import org.enginehub.piston.gen.value.CommandParamInfo;
import org.enginehub.piston.gen.value.ExtractSpec;
import org.enginehub.piston.gen.value.ReservedNames;
import org.enginehub.piston.inject.InjectAnnotation;
import org.enginehub.piston.internal.RegistrationUtil;
import org.enginehub.piston.part.ArgAcceptingCommandFlag;
import org.enginehub.piston.part.CommandArgument;
import org.enginehub.piston.part.CommandParts;
import org.enginehub.piston.part.NoArgCommandFlag;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static com.google.auto.common.MoreElements.asType;
import static com.google.auto.common.MoreElements.getAnnotationMirror;
import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static org.enginehub.piston.gen.util.AnnoValueExtraction.getList;
import static org.enginehub.piston.gen.util.AnnoValueExtraction.getValue;
import static org.enginehub.piston.gen.util.CodeBlockUtil.listForGen;
import static org.enginehub.piston.gen.util.CodeBlockUtil.stringListForGen;
import static org.enginehub.piston.gen.util.CodeBlockUtil.textCompOf;
import static org.enginehub.piston.gen.util.CodeBlockUtil.transCompOf;
import static org.enginehub.piston.gen.util.ProcessingEnvValues.prefixArgName;
class CommandParameterInterpreter {
private interface ParamTransform {
CommandParamInfo createPartInfo(VariableElement parameter);
}
private static boolean isUnconverted(ProcessingEnvironment env, VariableElement parameter) {
TypeMirror commandValue = env.getElementUtils().getTypeElement(CommandValue.class.getCanonicalName()).asType();
return env.getTypeUtils().isAssignable(parameter.asType(), commandValue);
}
private final Map, ParamTransform> ANNOTATION_TRANSFORMS = ImmutableMap.of(
Arg.class, this::argTransform,
ArgFlag.class, this::argFlagTransform,
Switch.class, this::switchTransform
);
private final ExecutableElement method;
private final GenerationSupport generationSupport;
private final ProcessingEnvironment env;
CommandParameterInterpreter(ExecutableElement method,
GenerationSupport generationSupport,
ProcessingEnvironment env) {
this.method = method;
this.generationSupport = generationSupport;
this.env = env;
}
private CommandParamInfo argTransform(VariableElement parameter) {
AnnotationMirror arg = MoreElements.getAnnotationMirror(parameter, Arg.class)
.toJavaUtil().orElseThrow(() ->
new ProcessingException("Missing Arg annotation").withElement(parameter));
String name = getValue(parameter, arg, "name", String.class);
if (name.equals(Arg.NAME_IS_PARAMETER_NAME)) {
name = parameter.getSimpleName().toString();
}
String desc = getValue(parameter, arg, "desc", String.class);
List defaults = getList(parameter, arg, "def", String.class);
CodeBlock.Builder construction = CodeBlock.builder()
.add("$T.arg($L, $L)\n" +
".defaultsTo($L)\n",
CommandParts.class, transCompOf(prefixArgName(env, name)), textCompOf(desc),
stringListForGen(defaults.stream()));
addArgTypes(parameter, construction);
if (getValue(parameter, arg, "variable", boolean.class)) {
construction.add(".variable(true)\n");
}
construction.add(".build()");
return CommandParamInfo.builder()
.name(parameter.getSimpleName() + "Part")
.type(TypeName.get(CommandArgument.class))
.construction(construction.build())
.extractSpec(getArgExtractSpec(parameter))
.build();
}
private CommandParamInfo argFlagTransform(VariableElement parameter) {
AnnotationMirror arg = MoreElements.getAnnotationMirror(parameter, ArgFlag.class)
.toJavaUtil().orElseThrow(() ->
new ProcessingException("Missing Arg annotation").withElement(parameter));
char name = getValue(parameter, arg, "name", char.class);
String argName = getValue(parameter, arg, "argName", String.class);
if (argName.equals(ArgFlag.ARG_NAME_IS_PARAMETER_NAME)) {
argName = parameter.getSimpleName().toString();
}
String desc = getValue(parameter, arg, "desc", String.class);
List defaults = getList(parameter, arg, "def", String.class);
CodeBlock.Builder construction = CodeBlock.builder()
.add("$T.flag('$L', $L)\n" +
".withRequiredArg()\n" +
".argNamed($L)\n" +
".defaultsTo($L)\n",
CommandParts.class, name, textCompOf(desc),
transCompOf(prefixArgName(env, argName)),
stringListForGen(defaults.stream()));
addArgTypes(parameter, construction);
construction.add(".build()");
return CommandParamInfo.builder()
.name(parameter.getSimpleName() + "Part")
.type(TypeName.get(ArgAcceptingCommandFlag.class))
.construction(construction.build())
.extractSpec(getArgExtractSpec(parameter))
.build();
}
private void addArgTypes(VariableElement parameter, CodeBlock.Builder construction) {
if (!isUnconverted(env, parameter)) {
CodeBlock keyRef;
TypeMirror parameterType = parameter.asType();
if (isMultiCompatibleType(parameterType)) {
keyRef = asKeyType(parameter,
TypeNameUtil.firstTypeArg(TypeName.get(parameterType)));
} else {
keyRef = asKeyType(parameter);
}
construction.add(".ofTypes($L)\n", listForGen(Stream.of(keyRef)));
}
}
private ExtractSpec getArgExtractSpec(VariableElement parameter) {
TypeMirror parameterType = parameter.asType();
return ExtractSpec.builder()
.name("extract$" + parameter.getSimpleName().toString())
.type(TypeName.get(parameterType))
.extractMethodBody(var -> {
CodeBlock.Builder builder = CodeBlock.builder();
if (isUnconverted(env, parameter)) {
builder.addStatement("return $L.value($L)",
var, ReservedNames.PARAMETERS);
} else if (isMultiCompatibleType(parameterType)) {
// Parameter is a collection type
builder.addStatement("return $L.value($L).asMultiple($L)",
var, ReservedNames.PARAMETERS, asKeyType(
parameter,
TypeNameUtil.firstTypeArg(TypeName.get(parameterType))
));
} else {
builder.addStatement("return $L.value($L).asSingle($L)",
var, ReservedNames.PARAMETERS, asKeyType(parameter));
}
return builder.build();
})
.build();
}
private boolean isMultiCompatibleType(TypeMirror mirror) {
TypeMirror immutableList = env.getElementUtils().getTypeElement(ImmutableList.class.getCanonicalName())
.asType();
Element paramAsElement = env.getTypeUtils().asElement(mirror);
boolean isObject = paramAsElement != null && Object.class.getName().contentEquals(
asType(paramAsElement).getQualifiedName()
);
TypeMirror erasedType = env.getTypeUtils().erasure(mirror);
return !isObject && env.getTypeUtils().isAssignable(immutableList, erasedType);
}
private CommandParamInfo switchTransform(VariableElement parameter) {
checkState(TypeName.BOOLEAN.equals(TypeName.get(parameter.asType())),
"Non-boolean parameter annotated with @Switch");
AnnotationMirror switchAnno = MoreElements.getAnnotationMirror(parameter, Switch.class)
.toJavaUtil().orElseThrow(() ->
new ProcessingException("Missing Switch annotation").withElement(parameter));
char name = getValue(parameter, switchAnno, "name", char.class);
String desc = getValue(parameter, switchAnno, "desc", String.class);
return CommandParamInfo.builder()
.name(parameter.getSimpleName() + "Part")
.type(TypeName.get(NoArgCommandFlag.class))
.construction(CodeBlock.of(
"$T.flag('$L', $L).build()",
CommandParts.class, name, textCompOf(desc)))
.extractSpec(ExtractSpec.builder()
.name("extract$" + parameter.getSimpleName().toString())
.type(TypeName.get(parameter.asType()))
.extractMethodBody(var -> CodeBlock.builder()
.addStatement("return $L.in($L)",
var, ReservedNames.PARAMETERS)
.build())
.build())
.build();
}
private CommandParamInfo untransformedParameter(VariableElement parameter) {
if (TypeName.get(parameter.asType()).equals(ClassName.get(CommandParameters.class))) {
return commandParameterValue(parameter);
}
return injectableValue(parameter);
}
private CommandParamInfo commandParameterValue(VariableElement parameter) {
return CommandParamInfo.builder()
.extractSpec(ExtractSpec.builder()
.name("extract$" + parameter.getSimpleName().toString())
.type(TypeName.get(parameter.asType()))
.extractMethodBody(var ->
CodeBlock.of("$[return $L;\n$]", ReservedNames.PARAMETERS))
.build())
.build();
}
private CommandParamInfo injectableValue(VariableElement parameter) {
return CommandParamInfo.builder()
.extractSpec(ExtractSpec.builder()
.name("extract$" + parameter.getSimpleName().toString())
.type(TypeName.get(parameter.asType()))
.extractMethodBody(var -> {
CodeBlock paramKey = asKeyType(parameter);
return CodeBlock.builder()
.addStatement("return $T.requireOptional($L, $S, $L.injectedValue($L))",
RegistrationUtil.class,
paramKey,
parameter.getSimpleName(),
ReservedNames.PARAMETERS, paramKey)
.build();
})
.build())
.build();
}
private CodeBlock asKeyType(VariableElement mirror) {
return asKeyType(mirror, TypeName.get(mirror.asType()));
}
private CodeBlock asKeyType(VariableElement mirror, TypeName typeName) {
ImmutableList firstAnnotation = mirror.getAnnotationMirrors().stream()
.filter(am -> {
TypeElement annoType = asType(am.getAnnotationType().asElement());
return getAnnotationMirror(annoType, InjectAnnotation.class).isPresent();
})
.collect(toImmutableList());
if (firstAnnotation.size() > 1) {
throw new ProcessingException("Too many binding annotations. Only one is allowed.")
.withElement(mirror)
.withAnnotation(firstAnnotation.get(1));
}
return generationSupport.requestKey(
typeName,
firstAnnotation.stream().map(AnnotationSpec::get).findFirst().orElse(null)
);
}
List getParams() {
return method.getParameters().stream()
.map(this::getParam)
.collect(toImmutableList());
}
private CommandParamInfo getParam(VariableElement parameter) {
ImmutableList transforms =
ANNOTATION_TRANSFORMS.entrySet().stream()
.filter(e -> isAnnotationPresent(parameter, e.getKey()))
.map(Map.Entry::getValue)
.collect(toImmutableList());
if (transforms.size() > 1) {
throw new ProcessingException(
"Too many transforms applicable. Did you add conflicting annotations?")
.withElement(parameter);
}
ParamTransform transform = transforms.isEmpty() ? this::untransformedParameter : transforms.get(0);
return transform.createPartInfo(parameter);
}
}