-
Notifications
You must be signed in to change notification settings - Fork 431
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[#500] Extract validation, CompileTimeTypeInfo and TypedMember from t…
…he annotation processor class
- Loading branch information
Showing
3 changed files
with
437 additions
and
0 deletions.
There are no files selected for viewing
90 changes: 90 additions & 0 deletions
90
picocli-codegen/src/main/java/picocli/codegen/annotation/processing/AnnotationValidator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package picocli.codegen.annotation.processing; | ||
|
||
import picocli.CommandLine; | ||
import picocli.codegen.util.Assert; | ||
|
||
import javax.annotation.processing.ProcessingEnvironment; | ||
import javax.annotation.processing.RoundEnvironment; | ||
import javax.lang.model.element.Element; | ||
import javax.lang.model.element.ElementKind; | ||
import javax.tools.Diagnostic; | ||
import java.lang.annotation.Annotation; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.LinkedHashSet; | ||
import java.util.Set; | ||
|
||
import static java.lang.String.format; | ||
|
||
public class AnnotationValidator { | ||
|
||
private static final Set<Class<? extends Annotation>> ALL = Collections.unmodifiableSet( | ||
new LinkedHashSet<Class<? extends Annotation>>(Arrays.asList( | ||
CommandLine.Command.class, | ||
CommandLine.Option.class, | ||
CommandLine.Parameters.class, | ||
CommandLine.Mixin.class, | ||
CommandLine.ParentCommand.class, | ||
CommandLine.Spec.class, | ||
CommandLine.Unmatched.class, | ||
CommandLine.ArgGroup.class | ||
)) | ||
); | ||
private ProcessingEnvironment processingEnv; | ||
|
||
public AnnotationValidator(ProcessingEnvironment processingEnv) { | ||
this.processingEnv = Assert.notNull(processingEnv, "processingEnv"); | ||
} | ||
|
||
public void validateAnnotations(RoundEnvironment roundEnv) { | ||
validateNoAnnotationsOnInterfaceField(roundEnv); | ||
validateInvalidCombination(roundEnv, CommandLine.Mixin.class, CommandLine.Option.class); | ||
validateInvalidCombination(roundEnv, CommandLine.Mixin.class, CommandLine.Parameters.class); | ||
validateInvalidCombination(roundEnv, CommandLine.Mixin.class, CommandLine.Unmatched.class); | ||
validateInvalidCombination(roundEnv, CommandLine.Mixin.class, CommandLine.Spec.class); | ||
validateInvalidCombination(roundEnv, CommandLine.Unmatched.class, CommandLine.Option.class); | ||
validateInvalidCombination(roundEnv, CommandLine.Unmatched.class, CommandLine.Parameters.class); | ||
validateInvalidCombination(roundEnv, CommandLine.Spec.class, CommandLine.Option.class); | ||
validateInvalidCombination(roundEnv, CommandLine.Spec.class, CommandLine.Parameters.class); | ||
validateInvalidCombination(roundEnv, CommandLine.Spec.class, CommandLine.Unmatched.class); | ||
validateInvalidCombination(roundEnv, CommandLine.Option.class, CommandLine.Parameters.class); | ||
|
||
// TODO | ||
//validateSpecFieldTypeIsCommandSpec(roundEnv); | ||
//validateOptionOrParametersIsNotFinalPrimitiveOrFinalString(roundEnv); | ||
//validateUnmatchedFieldTypeIsStringArrayOrListOfString(roundEnv); | ||
} | ||
|
||
private void validateNoAnnotationsOnInterfaceField(RoundEnvironment roundEnv) { | ||
for (Class<? extends Annotation> cls : ALL) { | ||
validateNoAnnotationsOnInterfaceField(roundEnv.getElementsAnnotatedWith(cls)); | ||
} | ||
} | ||
|
||
private void validateNoAnnotationsOnInterfaceField(Set<? extends Element> all) { | ||
for (Element element : all) { | ||
if (element.getKind() == ElementKind.FIELD && | ||
element.getEnclosingElement().getKind() == ElementKind.INTERFACE) { | ||
error(element, "Invalid picocli annotation on interface field %s.%s", | ||
element.getEnclosingElement().toString(), element.getSimpleName()); | ||
} | ||
} | ||
} | ||
|
||
private <T1 extends Annotation, T2 extends Annotation> void validateInvalidCombination( | ||
RoundEnvironment roundEnv, Class<T1> c1, Class<T2> c2) { | ||
for (Element element : roundEnv.getElementsAnnotatedWith(c1)) { | ||
if (element.getAnnotation(c2) != null) { | ||
error(element, "%s cannot have both @%s and @%s annotations", | ||
element, c1.getCanonicalName(), c2.getCanonicalName()); | ||
} | ||
} | ||
} | ||
|
||
void error(Element e, String msg, Object... args) { | ||
processingEnv.getMessager().printMessage( | ||
Diagnostic.Kind.ERROR, | ||
format(msg, args), | ||
e); | ||
} | ||
} |
217 changes: 217 additions & 0 deletions
217
picocli-codegen/src/main/java/picocli/codegen/annotation/processing/CompileTimeTypeInfo.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
package picocli.codegen.annotation.processing; | ||
|
||
import picocli.CommandLine; | ||
|
||
import javax.lang.model.element.Element; | ||
import javax.lang.model.element.ElementKind; | ||
import javax.lang.model.element.ExecutableElement; | ||
import javax.lang.model.element.TypeElement; | ||
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 java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.EnumSet; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.logging.Logger; | ||
|
||
class CompileTimeTypeInfo implements CommandLine.Model.ITypeInfo { | ||
private static Logger logger = Logger.getLogger(CompileTimeTypeInfo.class.getName()); | ||
private static final EnumSet<TypeKind> PRIMITIVES = EnumSet.of( | ||
TypeKind.BYTE, TypeKind.BOOLEAN, TypeKind.CHAR, TypeKind.DOUBLE, | ||
TypeKind.FLOAT, TypeKind.INT, TypeKind.LONG, TypeKind.SHORT); | ||
|
||
final TypeMirror typeMirror; | ||
final List<? extends TypeMirror> auxTypeMirrors; | ||
final List<String> actualGenericTypeArguments; | ||
final TypeElement typeElement; | ||
final boolean isCollection; | ||
final boolean isMap; | ||
|
||
public CompileTimeTypeInfo(TypeMirror asType) { | ||
typeMirror = asType; | ||
|
||
// for non-multi-value types, the auxiliary type is a single-value list with the type | ||
List<? extends TypeMirror> aux = Arrays.asList(typeMirror); | ||
TypeElement tempTypeElement = null; | ||
boolean collection = false; | ||
boolean map = false; | ||
|
||
if (typeMirror.getKind() == TypeKind.DECLARED) { | ||
logger.finest("CompileTimeTypeInfo DECLARED typeMirror " + typeMirror); | ||
Element element = ((DeclaredType) typeMirror).asElement(); | ||
if (element.getKind().isClass() || element.getKind().isInterface()) { | ||
tempTypeElement = (TypeElement) element; | ||
logger.finest("element is class or interface " + tempTypeElement); | ||
map = find("java.util.Map", tempTypeElement); | ||
collection = !map && find("java.util.Collection", tempTypeElement); | ||
} | ||
aux = ((DeclaredType) typeMirror).getTypeArguments(); | ||
actualGenericTypeArguments = new ArrayList<String>(); | ||
for (TypeMirror typeMirror : aux) { | ||
actualGenericTypeArguments.add(typeMirror.toString()); | ||
} | ||
logger.finest("aux (type args): " + aux); | ||
if (aux.isEmpty()) { | ||
if (map || collection) { | ||
aux = Arrays.asList(createStringTypeMirror(), createStringTypeMirror()); | ||
logger.finest("fixed aux (for multi type): " + aux); | ||
} else { | ||
aux = Arrays.asList(typeMirror); | ||
logger.finest("fixed aux (for single type): " + aux); | ||
} | ||
} | ||
} else if (typeMirror.getKind() == TypeKind.ARRAY) { | ||
aux = Arrays.asList(((ArrayType) typeMirror).getComponentType()); | ||
actualGenericTypeArguments = Arrays.asList(aux.get(0).toString()); | ||
} else { | ||
actualGenericTypeArguments = Collections.emptyList(); | ||
} | ||
auxTypeMirrors = aux; | ||
typeElement = tempTypeElement; | ||
isCollection = collection; | ||
isMap = map; | ||
} | ||
|
||
private TypeMirror createStringTypeMirror() { | ||
TypeElement element = typeElement; | ||
while (element.getSuperclass().getKind() != TypeKind.NONE) { | ||
logger.finest("finding toString in " + element); | ||
|
||
element = (TypeElement) ((DeclaredType) element.getSuperclass()).asElement(); | ||
} | ||
for (Element enclosed : typeElement.getEnclosedElements()) { | ||
if (enclosed.getKind() == ElementKind.METHOD) { | ||
ExecutableElement method = (ExecutableElement) enclosed; | ||
if (method.getSimpleName().contentEquals("toString")) { | ||
return method.getReturnType(); | ||
} | ||
} | ||
} | ||
throw new IllegalStateException("Cannot find toString method in Object"); | ||
} | ||
private static boolean find(String interfaceName, TypeElement typeElement) { | ||
return find(interfaceName, typeElement, new HashSet<Element>()); | ||
} | ||
private static boolean find(String interfaceName, TypeElement typeElement, Set<Element> visited) { | ||
if (visited.contains(typeElement)) { return false; } | ||
visited.add(typeElement); | ||
//logger.finest("trying to find " + interfaceName + " in " + typeElement); | ||
|
||
if (typeElement.getQualifiedName().contentEquals(interfaceName)) { | ||
return true; | ||
} | ||
for (TypeMirror implemented : typeElement.getInterfaces()) { | ||
if (find(interfaceName, (TypeElement) ((DeclaredType) implemented).asElement())) { | ||
return true; | ||
} | ||
} | ||
while (typeElement.getSuperclass().getKind() != TypeKind.NONE) { | ||
typeElement = (TypeElement) ((DeclaredType) typeElement.getSuperclass()).asElement(); | ||
if (find(interfaceName, typeElement)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public List<CommandLine.Model.ITypeInfo> getAuxiliaryTypeInfos() { | ||
// for non-multi-value types, the auxiliary type is a single-value list with the type | ||
if (!isMultiValue()) { | ||
logger.fine("getAuxiliaryTypeInfos (non-multi) returning new list with this"); | ||
return Arrays.<CommandLine.Model.ITypeInfo>asList(this); | ||
} | ||
|
||
List<CommandLine.Model.ITypeInfo> result = new ArrayList<CommandLine.Model.ITypeInfo>(); | ||
for (TypeMirror typeMirror : auxTypeMirrors) { | ||
result.add(new CompileTimeTypeInfo(typeMirror)); | ||
} | ||
logger.fine("getAuxiliaryTypeInfos (multi) returning list " + result); | ||
return result; | ||
} | ||
|
||
@Override | ||
public List<String> getActualGenericTypeArguments() { | ||
return actualGenericTypeArguments; | ||
} | ||
|
||
@Override | ||
public boolean isBoolean() { | ||
TypeMirror type = auxTypeMirrors.get(0); | ||
return type.getKind() == TypeKind.BOOLEAN || "java.lang.Boolean".equals(type.toString()); | ||
} | ||
|
||
@Override | ||
public boolean isMultiValue() { | ||
return isArray() || isCollection() || isMap(); | ||
} | ||
|
||
@Override | ||
public boolean isArray() { | ||
return typeMirror.getKind() == TypeKind.ARRAY; | ||
} | ||
|
||
@Override | ||
public boolean isCollection() { | ||
return isCollection; | ||
} | ||
|
||
@Override | ||
public boolean isMap() { | ||
return isMap; | ||
} | ||
|
||
@Override | ||
public boolean isEnum() { | ||
TypeMirror type = auxTypeMirrors.get(0); | ||
return type.getKind() == TypeKind.DECLARED && | ||
((DeclaredType) type).asElement().getKind() == ElementKind.ENUM; | ||
} | ||
|
||
@Override | ||
public List<String> getEnumConstantNames() { | ||
if (!isEnum()) { | ||
return Collections.emptyList(); | ||
} | ||
List<String> result = new ArrayList<String>(); | ||
TypeMirror type = auxTypeMirrors.get(0); | ||
List<? extends Element> enclosed = ((DeclaredType) type).asElement().getEnclosedElements(); | ||
for (Element element : enclosed) { | ||
if (element.getKind() == ElementKind.ENUM_CONSTANT) { | ||
result.add(element.toString()); | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
@Override | ||
public String getClassName() { | ||
return typeElement == null ? typeMirror.toString() : typeElement.getQualifiedName().toString(); | ||
} | ||
|
||
@Override | ||
public String getClassSimpleName() { | ||
return typeElement == null ? typeMirror.toString() : typeElement.getSimpleName().toString(); | ||
} | ||
|
||
@Override | ||
public Class<?> getType() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public Class<?>[] getAuxiliaryTypes() { | ||
return new Class[0]; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return String.format("CompileTimeTypeInfo(%s, aux=%s, collection=%s, map=%s)", | ||
typeMirror, Arrays.toString(auxTypeMirrors.toArray()), isCollection, isMap); | ||
} | ||
} |
Oops, something went wrong.