/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * Licensed 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.uberfire.annotations.processors; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.annotation.processing.ProcessingEnvironment; import javax.inject.Qualifier; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleAnnotationValueVisitor6; import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; import org.uberfire.annotations.processors.exceptions.GenerationException; import org.uberfire.annotations.processors.facades.APIModule; import org.uberfire.annotations.processors.facades.BackendModule; import org.uberfire.annotations.processors.facades.ClientAPIModule; import static java.util.Collections.singletonList; import static org.uberfire.annotations.processors.facades.ClientAPIModule.OWNING_PERSPECTIVE; import static org.uberfire.annotations.processors.facades.ClientAPIModule.workbenchEditor; import static org.uberfire.annotations.processors.facades.ClientAPIModule.workbenchScreen; /** * Utilities for code generation */ public class GeneratorUtils { /** * Handy constant for an emtpy array of argument types. */ private static final String[] NO_PARAMS = new String[0]; /** * Passing a reference to exactly this array causes * {@link #getAnnotatedMethods(TypeElement, ProcessingEnvironment, String, TypeMirror, String[])}, * {@link #getAnnotatedMethods(TypeElement, ProcessingEnvironment, String, TypeMirror[], String[])} and * friends not to care about parameter types. */ private static final String[] ANY_PARAMS = new String[0]; /** * Finds the {@code @OnStartup} method suitable for workbench classes that are not {@code @WorkbenchEditor}. * The method must be public, non-static, have a return-type of void and either take zero parameters or one * parameter of type {@code PlaceRequest}. * <p> * If no such method is found, returns null. If methods annotated with {@code @OnStartup} are found but they do not * satisfy all the requirements, they are marked with errors explaining the problem. */ public static ExecutableElement getOnStartupMethodForNonEditors(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) { final Types typeUtils = processingEnvironment.getTypeUtils(); final TypeMirror requiredReturnType = typeUtils.getNoType(TypeKind.VOID); List<ExecutableElement> onStartupMethods = getAnnotatedMethods( classElement, processingEnvironment, APIModule.getOnStartupClass(), requiredReturnType, ANY_PARAMS); Elements elementUtils = processingEnvironment.getElementUtils(); ExecutableElement zeroArgMethod = null; ExecutableElement oneArgMethod = null; for (ExecutableElement m : onStartupMethods) { if (doParametersMatch(typeUtils, elementUtils, m, NO_PARAMS)) { zeroArgMethod = m; } else if (doParametersMatch(typeUtils, elementUtils, m, new String[]{APIModule.getPlaceRequestClass()})) { oneArgMethod = m; } else { processingEnvironment.getMessager().printMessage( Kind.ERROR, formatProblemsList(APIModule.getOnStartupClass(), singletonList("take no arguments or one argument of type " + APIModule.getPlaceRequestClass()))); } } if (zeroArgMethod != null && oneArgMethod != null) { // TODO multiple methods should be allowed, but only if inherited. See UF-42. processingEnvironment.getMessager().printMessage( Kind.ERROR, "Found multiple @OnStartup methods. Each class can declare at most one.", zeroArgMethod); } if (oneArgMethod != null) { return oneArgMethod; } return zeroArgMethod; } /** * Finds the {@code @OnStartup} method suitable for {@code @WorkbenchEditor} classes. * The method must be public, non-static, have a return-type of void and either take one parameter * of type {@code Path} or two parameters of type {@code (Path, PlaceRequest)}. * <p> * If no such method is found, returns null. If methods annotated with {@code @OnStartup} are found but they do not * satisfy all the requirements, they are marked with errors explaining the problem. */ public static ExecutableElement getOnStartupMethodForEditors(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) { final Types typeUtils = processingEnvironment.getTypeUtils(); final TypeMirror requiredReturnType = typeUtils.getNoType(TypeKind.VOID); List<ExecutableElement> onStartupMethods = getAnnotatedMethods( classElement, processingEnvironment, APIModule.getOnStartupClass(), requiredReturnType, ANY_PARAMS); Elements elementUtils = processingEnvironment.getElementUtils(); ExecutableElement oneArgMethod = null; ExecutableElement twoArgMethod = null; for (ExecutableElement m : onStartupMethods) { if (doParametersMatch(typeUtils, elementUtils, m, new String[]{BackendModule.getPathClass()})) { oneArgMethod = m; } else if (doParametersMatch(typeUtils, elementUtils, m, new String[]{BackendModule.getPathClass(), APIModule.getPlaceRequestClass()})) { twoArgMethod = m; } else { processingEnvironment.getMessager().printMessage( Kind.ERROR, formatProblemsList(APIModule.getOnStartupClass(), singletonList("take one argument of type " + BackendModule.getPathClass() + " and an optional second argument of type " + APIModule.getPlaceRequestClass()))); } } if (oneArgMethod != null && twoArgMethod != null) { // TODO make this an error (need to take inherited methods into account). See UF-76. processingEnvironment.getMessager().printMessage( Kind.WARNING, "There is also an @OnStartup(Path, PlaceRequest) method in this class. That method takes precedence over this one.", oneArgMethod); } if (twoArgMethod != null) { return twoArgMethod; } return oneArgMethod; } public static String getOnContextAttachPanelDefinitionMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getVoidMethodName(classElement, processingEnvironment, new String[]{APIModule.getPanelDefinitionClass()}, APIModule.getOnContextAttachClass()); } /** * Get the method name annotated with {@code @OnMayClose}. The method must * be public, non-static, have a return-type of void and take zero * parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getOnMayCloseMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getBooleanMethodName(classElement, processingEnvironment, APIModule.getOnMayCloseClass()); } /** * Get the method name annotated with {@code @OnClose}. The method must be * public, non-static, have a return-type of void and take zero parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getOnCloseMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getVoidMethodName(classElement, processingEnvironment, APIModule.getOnCloseClass()); } /** * Get the method name annotated with {@code @OnShutdown}. The method must be * public, non-static, have a return-type of void and take zero parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getOnShutdownMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getVoidMethodName(classElement, processingEnvironment, APIModule.getOnShutdownlass()); } /** * Get the method name annotated with {@code @OnOpen}. The method must be * public, non-static, have a return-type of void and take zero parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getOnOpenMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getVoidMethodName(classElement, processingEnvironment, APIModule.getOnOpenClass()); } /** * Get the method name annotated with {@code @OnLostFocus}. The method must * be public, non-static, have a return-type of void and take zero * parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getOnLostFocusMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getVoidMethodName(classElement, processingEnvironment, APIModule.getOnLostFocusClass()); } /** * Get the method name annotated with {@code @OnFocus}. The method must be * public, non-static, have a return-type of void and take zero parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getOnFocusMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getVoidMethodName(classElement, processingEnvironment, APIModule.getOnFocusClass()); } /** * Get the method name annotated with {@code @DefaultPosition}. The method * must be public, non-static, have a return-type of void and take zero * parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getDefaultPositionMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getDefaultPositionMethodName(classElement, processingEnvironment, ClientAPIModule.getDefaultPositionClass()); } /** * Get the method name annotated with {@code @WorkbenchPartTitle}. The * method must be public, non-static, have a return-type of java.lang.String * and take zero parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getTitleMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getStringMethodName(classElement, processingEnvironment, ClientAPIModule.getWorkbenchPartTitleClass()); } public static String getContextIdMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getStringMethodName(classElement, processingEnvironment, ClientAPIModule.getWorkbenchContextIdClass()); } /** * Get the method name annotated with {@code @WorkbenchPartTitleDecoration}. The * method must be public, non-static, have a return-type of * com.google.gwt.user.client.ui.IsWidget and take zero parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static ExecutableElement getTitleWidgetMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getWidgetMethodName(classElement, processingEnvironment, ClientAPIModule.getWorkbenchPartTitleDecorationsClass()); } /** * Get the method name annotated with {@code @WorkbenchPartView}. The method * must be public, non-static, have a return-type of IsWidget and take zero * parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static ExecutableElement getWidgetMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getWidgetMethodName(classElement, processingEnvironment, ClientAPIModule.getWorkbenchPartViewClass()); } /** * Check whether the provided type extends IsWidget. * @param classElement * @param processingEnvironment * @return */ public static boolean getIsWidget(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) { final Types typeUtils = processingEnvironment.getTypeUtils(); final Elements elementUtils = processingEnvironment.getElementUtils(); final TypeMirror requiredReturnType = elementUtils.getTypeElement("com.google.gwt.user.client.ui.IsWidget").asType(); return typeUtils.isAssignable(classElement.asType(), requiredReturnType); } /** * Check whether the provided type extends IsElement. * @param type * @param processingEnvironment * @return */ public static boolean getIsElement(final TypeMirror type, final ProcessingEnvironment processingEnvironment) { final Types typeUtils = processingEnvironment.getTypeUtils(); final Elements elementUtils = processingEnvironment.getElementUtils(); final TypeMirror requiredReturnType = elementUtils.getTypeElement("org.jboss.errai.common.client.api.IsElement").asType(); return typeUtils.isAssignable(type, requiredReturnType); } public static boolean hasUberViewReference(final TypeElement classElement, final ProcessingEnvironment processingEnvironment, final ExecutableElement getWidgetMethod) { if (getWidgetMethod == null) { return false; } final Types typeUtils = processingEnvironment.getTypeUtils(); final Elements elementUtils = processingEnvironment.getElementUtils(); final TypeMirror requiredReturnType = elementUtils.getTypeElement("org.uberfire.client.mvp.UberView").asType(); return typeUtils.isAssignable(typeUtils.erasure(getWidgetMethod.getReturnType()), requiredReturnType); } public static boolean hasUberElementReference(final TypeElement classElement, final ProcessingEnvironment processingEnvironment, final ExecutableElement getWidgetMethod) { if (getWidgetMethod == null) { return false; } final Types typeUtils = processingEnvironment.getTypeUtils(); final Elements elementUtils = processingEnvironment.getElementUtils(); final TypeMirror requiredReturnType = elementUtils.getTypeElement("org.uberfire.client.mvp.UberElement").asType(); return typeUtils.isAssignable(typeUtils.erasure(getWidgetMethod.getReturnType()), requiredReturnType); } /** * Get the method name annotated with {@code @WorkbenchPartView}. The method * must be public, non-static, have a return-type of PopupPanel and take * zero parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getPopupMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getPopupMethodName(classElement, processingEnvironment, ClientAPIModule.getWorkbenchPartViewClass()); } /** * Check whether the provided type extends PopupPanel. * @param classElement * @param processingEnvironment * @return */ public static boolean getIsPopup(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) { final Types typeUtils = processingEnvironment.getTypeUtils(); final Elements elementUtils = processingEnvironment.getElementUtils(); final TypeMirror requiredReturnType = elementUtils.getTypeElement("com.google.gwt.user.client.ui.PopupPanel").asType(); return typeUtils.isAssignable(classElement.asType(), requiredReturnType); } /** * Get the method name annotated with {@code @IsDirty}. The method must be * public, non-static, have a return-type of void and take zero parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getIsDirtyMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getBooleanMethodName(classElement, processingEnvironment, APIModule.getIsDirtyClass()); } /** * Get the method name annotated with {@code @OnSave}. The method must be * public, non-static, have a return-type of void and take zero parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getOnSaveMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getVoidMethodName(classElement, processingEnvironment, APIModule.getOnSaveClass()); } /** * Get the method name annotated with {@code @WorkbenchMenu}. The method * must be public, non-static, have a return-type of WorkbenchMenuBar and * take zero parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getMenuBarMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getMenuBarMethodName(classElement, processingEnvironment, ClientAPIModule.getWorkbenchMenuClass()); } /** * Get the method name annotated with {@code @WorkbenchToolBar}. The method * must be public, non-static, have a return-type of WorkbenchToolBar and * take zero parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getToolBarMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getToolBarMethodName(classElement, processingEnvironment, ClientAPIModule.getWorkbenchToolBarClass()); } /** * Get the method name annotated with {@code @Perspective}. The method must * be public, non-static, have a return-type of PerspectiveDefinition and * take zero parameters. * @param classElement * @param processingEnvironment * @return null if none found * @throws GenerationException */ public static String getPerspectiveMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getMethodName(classElement, processingEnvironment, "org.uberfire.workbench.model.PerspectiveDefinition", ClientAPIModule.getPerspectiveClass()); } public static String getSplashFilterMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getMethodName(classElement, processingEnvironment, "org.uberfire.workbench.model.SplashScreenFilter", ClientAPIModule.getSplashFilterClass()); } public static String getBodyHeightMethodName(TypeElement classElement, ProcessingEnvironment processingEnvironment) throws GenerationException { return getMethodName(classElement, processingEnvironment, "java.lang.Integer", ClientAPIModule.getSplashBodyHeightClass()); } public static String getInterceptMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) throws GenerationException { return getMethodName(classElement, processingEnvironment, "java.lang.Boolean", ClientAPIModule.getInterceptClass()); } public static String getBeanActivatorClassName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment) { AnnotationMirror activatedByAnnotation = getAnnotation(processingEnvironment.getElementUtils(), classElement, APIModule.activatedBy); if (activatedByAnnotation != null) { return extractAnnotationStringValue(processingEnvironment.getElementUtils(), activatedByAnnotation, "value"); } return null; } /** * Returns the identifier (PlaceRequest ID) of the perspective that owns the given part. * @param screenOrEditorClass a type annotated with either {@code @WorkbenchScreen} or {@code @WorkbenchEditor}. Not null. * @param processingEnvironment the current annotation processing environment. * @return * @throws GenerationException if the owningPerspective parameter is present, but points to something other than a * {@code @WorkbenchPerspective} class. */ public static String getOwningPerspectivePlaceRequest(TypeElement screenOrEditorClass, ProcessingEnvironment processingEnvironment) throws GenerationException { Elements elementUtils = processingEnvironment.getElementUtils(); final Types typeUtils = processingEnvironment.getTypeUtils(); AnnotationMirror screenOrEditorAnnotation = getAnnotation(elementUtils, screenOrEditorClass, workbenchScreen); if (screenOrEditorAnnotation == null) { screenOrEditorAnnotation = getAnnotation(elementUtils, screenOrEditorClass, workbenchEditor); } AnnotationValue owningPerspectiveParam = extractAnnotationPropertyValue(elementUtils, screenOrEditorAnnotation, OWNING_PERSPECTIVE); final TypeElement owningPerspectiveType = (TypeElement) typeUtils.asElement((TypeMirror) owningPerspectiveParam.getValue()); if (owningPerspectiveType == null) { return null; } final String owningPerspectivePlace = ClientAPIModule.getWbPerspectiveScreenIdentifierValueOnClass(owningPerspectiveType); if (owningPerspectivePlace.equals("")) { processingEnvironment.getMessager() .printMessage(Kind.ERROR, "owningPerspective must be a class annotated with @WorkbenchPerspective.", screenOrEditorClass, screenOrEditorAnnotation, owningPerspectiveParam); return null; } return owningPerspectivePlace; } /** * Searches for an accessible method annotated with the given annotation. The method must be non-private, * non-static, take no arguments, and return void. * <p> * If a method with the given annotation is found but the method does not satisfy the requirements listed above, the * method will be marked with an error explaining the problem. * @param classElement the class to search for the annotated method. * @param processingEnvironment the current annotation processing environment. * @param annotationName the fully-qualified name of the annotation to search for * @return the name of the method that satisfies all the requirements and bears the given annotation, or null if * there is no such method. */ private static String getVoidMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment, final String annotationName) throws GenerationException { final Types typeUtils = processingEnvironment.getTypeUtils(); final TypeMirror requiredReturnType = typeUtils.getNoType(TypeKind.VOID); ExecutableElement match = getUniqueAnnotatedMethod( classElement, processingEnvironment, annotationName, requiredReturnType, NO_PARAMS); if (match == null) { return null; } return match.getSimpleName().toString(); } // Lookup a public method name with the given annotation. The method must be // public, non-static, have a return-type of void and take parameters matching // those provided. private static String getVoidMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment, final String[] parameterTypes, final String annotationName) throws GenerationException { final Types typeUtils = processingEnvironment.getTypeUtils(); final Elements elementUtils = processingEnvironment.getElementUtils(); final TypeMirror requiredReturnType = typeUtils.getNoType(TypeKind.VOID); final List<ExecutableElement> methods = ElementFilter.methodsIn(classElement.getEnclosedElements()); ExecutableElement match = null; for (ExecutableElement e : methods) { final TypeMirror actualReturnType = e.getReturnType(); //Check method if (getAnnotation(elementUtils, e, annotationName) == null) { continue; } if (!typeUtils.isSameType(actualReturnType, requiredReturnType)) { continue; } if (!doParametersMatch(typeUtils, elementUtils, e, parameterTypes)) { continue; } if (e.getModifiers().contains(Modifier.STATIC)) { continue; } if (!e.getModifiers().contains(Modifier.PUBLIC)) { continue; } if (match != null) { throw new GenerationException("Multiple methods with @" + fqcnToSimpleName(annotationName) + " detected."); } match = e; } if (match == null) { return null; } return match.getSimpleName().toString(); } /** * Checks whether the ExecutableElement's parameter list matches the requiredParameterTypes (order matters). * @param typeUtils type utils from current processing environment. * @param elementUtils element utils from current processing environment. * @param e the method whose parameter list to check. * @param requiredParameterTypes the required parameter types. Must not be null. * If a reference to {@link #ANY_PARAMS}, this method returns true without any further checks. * @return true if the target method's parameter list matches the given required parameter types, or if the special * {@link #ANY_PARAMS} value is passed as {@code requiredParameterTypes}. False otherwise. */ private static boolean doParametersMatch(final Types typeUtils, final Elements elementUtils, final ExecutableElement e, final String[] requiredParameterTypes) { if (requiredParameterTypes == ANY_PARAMS) { return true; } if (e.getParameters().size() != requiredParameterTypes.length) { return false; } List<TypeMirror> requiredTypes = new ArrayList<TypeMirror>(); for (String parameterType : requiredParameterTypes) { requiredTypes.add(elementUtils.getTypeElement(parameterType).asType()); } for (int i = 0; i < requiredTypes.size(); i++) { final TypeMirror actualType = e.getParameters().get(i).asType(); final TypeMirror requiredType = requiredTypes.get(i); if (!typeUtils.isAssignable(actualType, requiredType)) { return false; } } return true; } /** * Finds a public, non-static, no-args method annotated with the given annotation which returns boolean. * <p> * If a method with the given annotation is found but the method does not satisfy the requirements listed above, the * method will be marked with an error explaining the problem. * <p> * If more than one method satisfies all the criteria, all such methods are marked with an error explaining the * problem. * @param classElement the class to search for the annotated method. * @param processingEnvironment the current annotation processing environment. * @param annotationName the fully-qualified name of the annotation to search for * @return null if no such method exists; otherwise, the method's name. */ private static String getBooleanMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment, final String annotationName) throws GenerationException { final Elements elementUtils = processingEnvironment.getElementUtils(); final TypeMirror requiredReturnType = elementUtils.getTypeElement(Boolean.class.getName()).asType(); ExecutableElement match = getUniqueAnnotatedMethod( classElement, processingEnvironment, annotationName, requiredReturnType, NO_PARAMS); if (match == null) { return null; } return match.getSimpleName().toString(); } /** * Finds a public, non-static, no-args method annotated with the given annotation which returns String. * <p> * If a method with the given annotation is found but the method does not satisfy the requirements listed above, the * method will be marked with an error explaining the problem. * <p> * If more than one method satisfies all the criteria, all such methods are marked with an error explaining the * problem. * @return null if no such method exists; otherwise, the method's name. */ private static String getStringMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment, final String annotationName) throws GenerationException { final Elements elementUtils = processingEnvironment.getElementUtils(); ExecutableElement match = getUniqueAnnotatedMethod(classElement, processingEnvironment, annotationName, elementUtils.getTypeElement(String.class.getName()).asType(), NO_PARAMS); if (match == null) { return null; } return match.getSimpleName().toString(); } /** * Finds a public, non-static, no-args method annotated with the given annotation which returns boolean. * <p> * If a method with the given annotation is found but the method does not satisfy the requirements listed above, the * method will be marked with an error explaining the problem. * <p> * If more than one method satisfies all the criteria, all such methods are marked with an error explaining the * problem. * @return null if no such method exists; otherwise, the method's name. */ private static ExecutableElement getWidgetMethodName(final TypeElement originalClassElement, final ProcessingEnvironment processingEnvironment, final String annotationName) throws GenerationException { final Elements elementUtils = processingEnvironment.getElementUtils(); return getUniqueAnnotatedMethod(originalClassElement, processingEnvironment, annotationName, new TypeMirror[]{ elementUtils.getTypeElement("com.google.gwt.user.client.ui.IsWidget").asType(), elementUtils.getTypeElement("org.jboss.errai.common.client.api.IsElement").asType() }, NO_PARAMS); } /** * Finds a public, non-static, method annotated with the given annotation which returns the given type and accepts * the given arguments. * <p> * If a method with the given annotation is found but the method does not satisfy the requirements listed above, the * method will be marked with an error explaining the problem. This will trigger a compilation failure. * <p> * If more than one method satisfies all the criteria, all such methods are marked with an error explaining the * problem. * @param originalClassElement the class to search for the annotated method. * @param processingEnvironment the current annotation processing environment. * @param annotationName the fully-qualified name of the annotation to search for. * @param requiredReturnType the fully qualified name of the type the method must return. * @param requiredParameterTypes the parameter types the method must take. If the method must take no parameters, use * {@link #NO_PARAMS}. If the method can take any parameters, use {@link #ANY_PARAMS}. * @return null if no such method exists; null if multiple methods satisfying the criteria are found; otherwise, a * reference to the method. */ private static ExecutableElement getUniqueAnnotatedMethod(final TypeElement originalClassElement, final ProcessingEnvironment processingEnvironment, final String annotationName, final TypeMirror requiredReturnType, final String[] requiredParameterTypes) { return getUniqueAnnotatedMethod(originalClassElement, processingEnvironment, annotationName, new TypeMirror[]{requiredReturnType}, requiredParameterTypes); } /** * Finds a public, non-static, method annotated with the given annotation which returns the given type and accepts * the given arguments. * <p> * If a method with the given annotation is found but the method does not satisfy the requirements listed above, the * method will be marked with an error explaining the problem. This will trigger a compilation failure. * <p> * If more than one method satisfies all the criteria, all such methods are marked with an error explaining the * problem. * @param originalClassElement the class to search for the annotated method. * @param processingEnvironment the current annotation processing environment. * @param annotationName the fully-qualified name of the annotation to search for. * @param requiredReturnType the fully qualified name of the type the method must return. * @param requiredParameterTypes the parameter types the method must take. If the method must take no parameters, use * {@link #NO_PARAMS}. If the method can take any parameters, use {@link #ANY_PARAMS}. * @return null if no such method exists; null if multiple methods satisfying the criteria are found; otherwise, a * reference to the method. */ private static ExecutableElement getUniqueAnnotatedMethod(final TypeElement originalClassElement, final ProcessingEnvironment processingEnvironment, final String annotationName, final TypeMirror[] requiredReturnType, final String[] requiredParameterTypes) { List<ExecutableElement> matches = getAnnotatedMethods( originalClassElement, processingEnvironment, annotationName, requiredReturnType, requiredParameterTypes); if (matches.size() == 1) { return matches.get(0); } else if (matches.size() > 1) { for (ExecutableElement match : matches) { processingEnvironment.getMessager().printMessage( Kind.ERROR, "Found multiple methods annotated with @" + fqcnToSimpleName(annotationName) + ". There should only be one.", match); } } return null; } /** * Finds all public, non-static, no-args method annotated with the given annotation which returns the given type. * <p> * If a method with the given annotation is found but the method does not satisfy the requirements listed above, the * method will be marked with an error explaining the problem. This will trigger a compilation failure. * <p> * If more than one method satisfies all the criteria, all such methods are marked with an error explaining the * problem. * @param originalClassElement the class to search for the annotated method. * @param processingEnvironment the current annotation processing environment. * @param annotationName the fully-qualified name of the annotation to search for. * @param requiredReturnType the fully qualified name of the type the method must return. * @param requiredParameterTypes the parameter types the method must take. If the method must take no parameters, use * {@link #NO_PARAMS}. If the method can take any parameters, use {@link #ANY_PARAMS}. * @return a list of references to the methods that satisfy the criteria (empty list if no such method exists). */ private static List<ExecutableElement> getAnnotatedMethods(final TypeElement originalClassElement, final ProcessingEnvironment processingEnvironment, final String annotationName, final TypeMirror requiredReturnType, final String[] requiredParameterTypes) { return getAnnotatedMethods(originalClassElement, processingEnvironment, annotationName, new TypeMirror[]{requiredReturnType}, requiredParameterTypes); } /** * Finds all public, non-static, no-args method annotated with the given annotation which returns the given type. * <p> * If a method with the given annotation is found but the method does not satisfy the requirements listed above, the * method will be marked with an error explaining the problem. This will trigger a compilation failure. * <p> * If more than one method satisfies all the criteria, all such methods are marked with an error explaining the * problem. * @param originalClassElement the class to search for the annotated method. * @param processingEnvironment the current annotation processing environment. * @param annotationName the fully-qualified name of the annotation to search for. * @param requiredReturnTypes the fully qualified names of the valid types the method must return. * @param requiredParameterTypes the parameter types the method must take. If the method must take no parameters, use * {@link #NO_PARAMS}. If the method can take any parameters, use {@link #ANY_PARAMS}. * @return a list of references to the methods that satisfy the criteria (empty list if no such method exists). */ private static List<ExecutableElement> getAnnotatedMethods(final TypeElement originalClassElement, final ProcessingEnvironment processingEnvironment, final String annotationName, final TypeMirror[] requiredReturnTypes, final String[] requiredParameterTypes) { final Types typeUtils = processingEnvironment.getTypeUtils(); final Elements elementUtils = processingEnvironment.getElementUtils(); TypeElement classElement = originalClassElement; while (true) { final List<ExecutableElement> methods = ElementFilter.methodsIn(classElement.getEnclosedElements()); List<ExecutableElement> matches = new ArrayList<ExecutableElement>(); for (ExecutableElement e : methods) { final TypeMirror actualReturnType = e.getReturnType(); if (getAnnotation(elementUtils, e, annotationName) == null) { continue; } List<String> problems = new ArrayList<String>(); boolean foundRequiredType = false; for (TypeMirror requiredReturnType : requiredReturnTypes) { if (typeUtils.isAssignable(actualReturnType, requiredReturnType)) { foundRequiredType = true; break; } } if (!foundRequiredType) { if (requiredReturnTypes.length == 1) { problems.add("return " + requiredReturnTypes[0]); } else { final StringBuilder types = new StringBuilder("{"); for (final TypeMirror requiredReturnType : requiredReturnTypes) { types.append(requiredReturnType).append(", "); } problems.add("return " + types.substring(0, types.length() - 2) + "}"); } } if (!doParametersMatch(typeUtils, elementUtils, e, requiredParameterTypes)) { if (requiredParameterTypes.length == 0) { problems.add("take no parameters"); } else { StringBuilder sb = new StringBuilder(); sb.append("take ").append(requiredParameterTypes).append(" parameters of type ("); boolean first = true; for (String p : requiredParameterTypes) { if (!first) { sb.append(", "); } sb.append(p); first = false; } sb.append(")"); problems.add(sb.toString()); } } if (e.getModifiers().contains(Modifier.STATIC)) { problems.add("be non-static"); } if (e.getModifiers().contains(Modifier.PRIVATE)) { problems.add("be non-private"); } if (problems.isEmpty()) { matches.add(e); } else { processingEnvironment.getMessager().printMessage( Kind.ERROR, formatProblemsList(annotationName, problems), e); } } if (!matches.isEmpty()) { return matches; } TypeMirror superclass = classElement.getSuperclass(); if (superclass instanceof DeclaredType) { classElement = (TypeElement) ((DeclaredType) superclass).asElement(); } else { break; } } return Collections.emptyList(); } /** * Renders the given list of problems with an annotated method as an English sentence. * The sentence takes the form "Methods annotated with <i>annotationSimpleName</i> must <i>list of problems</i>". * Commas and "and" are inserted as appropriate. * @param annotationFqcn the fully-qualified name of the annotation the problems pertain to. * @param problems the list of problems, as verb phrases. Must not be null, and should contain at least one item. * @return a nice English sentence summarizing the problems. */ static String formatProblemsList(final String annotationFqcn, List<String> problems) { StringBuilder msg = new StringBuilder(); msg.append("Methods annotated with @") .append(fqcnToSimpleName(annotationFqcn)) .append(" must "); for (int i = 0; i < problems.size(); i++) { if (problems.size() > 2 && i > 0) { msg.append(", "); } if (problems.size() == 2 && i == 1) { msg.append(" and "); } if (problems.size() > 2 && i == problems.size() - 1) { msg.append("and "); } msg.append(problems.get(i)); } return msg.toString(); } /** * Lookup a public method name with the given annotation. The method must be * public, non-static, have a return-type of PopupPanel and take zero * parameters. */ private static String getPopupMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment, final String annotationName) throws GenerationException { final Types typeUtils = processingEnvironment.getTypeUtils(); final Elements elementUtils = processingEnvironment.getElementUtils(); final TypeMirror requiredReturnType = elementUtils.getTypeElement("com.google.gwt.user.client.ui.PopupPanel").asType(); final List<ExecutableElement> methods = ElementFilter.methodsIn(classElement.getEnclosedElements()); ExecutableElement match = null; for (ExecutableElement e : methods) { final TypeMirror actualReturnType = e.getReturnType(); //Check method if (getAnnotation(elementUtils, e, annotationName) == null) { continue; } if (!typeUtils.isAssignable(actualReturnType, requiredReturnType)) { continue; } if (e.getParameters().size() != 0) { continue; } if (e.getModifiers().contains(Modifier.STATIC)) { continue; } if (!e.getModifiers().contains(Modifier.PUBLIC)) { continue; } if (match != null) { throw new GenerationException("Multiple methods with @" + fqcnToSimpleName(annotationName) + " detected."); } match = e; } if (match == null) { return null; } return match.getSimpleName().toString(); } /** * Looks up a public method name with the given annotation. The method must be * public, non-static, have a return-type of Position and take zero * parameters. */ private static String getDefaultPositionMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment, final String annotationName) throws GenerationException { final Types typeUtils = processingEnvironment.getTypeUtils(); final Elements elementUtils = processingEnvironment.getElementUtils(); final TypeMirror requiredReturnType = elementUtils.getTypeElement(APIModule.getPositionClass()).asType(); final List<ExecutableElement> methods = ElementFilter.methodsIn(classElement.getEnclosedElements()); ExecutableElement match = null; for (ExecutableElement e : methods) { final TypeMirror actualReturnType = e.getReturnType(); //Check method if (getAnnotation(elementUtils, e, annotationName) == null) { continue; } if (!typeUtils.isAssignable(actualReturnType, requiredReturnType)) { continue; } if (e.getParameters().size() != 0) { continue; } if (e.getModifiers().contains(Modifier.STATIC)) { continue; } if (!e.getModifiers().contains(Modifier.PUBLIC)) { continue; } if (match != null) { throw new GenerationException("Multiple methods with @" + fqcnToSimpleName(annotationName) + " detected."); } match = e; } if (match == null) { return null; } return match.getSimpleName().toString(); } public static AnnotationMirror getAnnotation(Elements elementUtils, Element annotationTarget, String annotationName) { for (AnnotationMirror annotation : elementUtils.getAllAnnotationMirrors(annotationTarget)) { if (annotationName.contentEquals(getQualifiedName(annotation))) { return annotation; } } return null; } public static Name getQualifiedName(AnnotationMirror annotation) { return ((TypeElement) annotation.getAnnotationType().asElement()).getQualifiedName(); } // Lookup a public method name with the given annotation. The method must be // public, non-static, have a return-type of WorkbenchMenuBar and take zero // parameters. private static String getMenuBarMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment, final String annotationName) throws GenerationException { final Types typeUtils = processingEnvironment.getTypeUtils(); final Elements elementUtils = processingEnvironment.getElementUtils(); final TypeMirror requiredReturnType = elementUtils.getTypeElement("org.uberfire.workbench.model.menu.Menus").asType(); final List<ExecutableElement> methods = ElementFilter.methodsIn(classElement.getEnclosedElements()); ExecutableElement match = null; for (ExecutableElement e : methods) { final TypeMirror actualReturnType = e.getReturnType(); //Check method if (getAnnotation(elementUtils, e, annotationName) == null) { continue; } if (!typeUtils.isAssignable(actualReturnType, requiredReturnType)) { continue; } if (e.getParameters().size() != 0) { continue; } if (e.getModifiers().contains(Modifier.STATIC)) { continue; } if (!e.getModifiers().contains(Modifier.PUBLIC)) { continue; } if (match != null) { throw new GenerationException("Multiple methods with @" + fqcnToSimpleName(annotationName) + " detected."); } match = e; } if (match == null) { return null; } return match.getSimpleName().toString(); } // Lookup a public method name with the given annotation. The method must be // public, non-static, have a return-type of WorkbenchToolBar and take zero // parameters. private static String getToolBarMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment, final String annotationName) throws GenerationException { final Types typeUtils = processingEnvironment.getTypeUtils(); final Elements elementUtils = processingEnvironment.getElementUtils(); final TypeMirror requiredReturnType = elementUtils.getTypeElement("org.uberfire.workbench.model.toolbar.ToolBar").asType(); final List<ExecutableElement> methods = ElementFilter.methodsIn(classElement.getEnclosedElements()); ExecutableElement match = null; for (ExecutableElement e : methods) { final TypeMirror actualReturnType = e.getReturnType(); //Check method if (getAnnotation(elementUtils, e, annotationName) == null) { continue; } if (!typeUtils.isAssignable(actualReturnType, requiredReturnType)) { continue; } if (e.getParameters().size() != 0) { continue; } if (e.getModifiers().contains(Modifier.STATIC)) { continue; } if (!e.getModifiers().contains(Modifier.PUBLIC)) { continue; } if (match != null) { throw new GenerationException("Multiple methods with @" + fqcnToSimpleName(annotationName) + " detected."); } match = e; } if (match == null) { return null; } return match.getSimpleName().toString(); } /** * Finds a public, non-static, no-args method annotated with the given annotation which returns the given type. * <p> * If a method with the given annotation is found but the method does not satisfy the requirements listed above, the * method will be marked with an error explaining the problem. * <p> * If more than one method satisfies all the criteria, all such methods are marked with an error explaining the * problem. * @param classElement the class to search for the annotated method. * @param processingEnvironment the current annotation processing environment. * @param expectedReturnType the fully-qualified name of the type the method must return. * @param annotationName the fully-qualified name of the annotation to search for. * @return null if no such method exists; otherwise, the method's name. */ private static String getMethodName(final TypeElement classElement, final ProcessingEnvironment processingEnvironment, final String expectedReturnType, final String annotationName) throws GenerationException { final Elements elementUtils = processingEnvironment.getElementUtils(); final TypeMirror requiredReturnType = elementUtils.getTypeElement(expectedReturnType).asType(); ExecutableElement match = getUniqueAnnotatedMethod( classElement, processingEnvironment, annotationName, requiredReturnType, NO_PARAMS); if (match == null) { return null; } return match.getSimpleName().toString(); } /** * Provides a uniform way of working with single- and multi-valued AnnotationValue objects. * @return the annotation values as strings. For multi-valued annotation params, the collection's iteration order matches the * order the values appeared in the source code. Single-valued params are wrapped in a single-element collection. * In either case, don't attempt to modify the returned collection. */ public static Collection<String> extractValue(final AnnotationValue value) { if (value.getValue() instanceof Collection) { final Collection<?> varray = (List<?>) value.getValue(); final ArrayList<String> result = new ArrayList<String>(varray.size()); for (final Object active : varray) { result.addAll(extractValue((AnnotationValue) active)); } return result; } return Collections.singleton(value.getValue().toString()); } /** * Pulls nested annotations out of the annotation that contains them. * @param elementUtils the current Elements object from this round of annotation processing. * @param element The element targeted by the containing annotation. * @param annotationName The containing annotation's fully-qualified name. * @param paramName The name of the parameter on the containing annotation. The parameter's type must be an array of annotations. */ public static List<AnnotationMirror> extractAnnotationsFromAnnotation(Elements elementUtils, Element element, String annotationName, String paramName) { final AnnotationMirror am = getAnnotation(elementUtils, element, annotationName); AnnotationValue nestedAnnotations = GeneratorUtils.extractAnnotationPropertyValue(elementUtils, am, paramName); if (nestedAnnotations == null) { return Collections.emptyList(); } final List<AnnotationMirror> result = new ArrayList<AnnotationMirror>(); nestedAnnotations.accept(new SimpleAnnotationValueVisitor6<Void, Void>() { @Override public Void visitArray(List<? extends AnnotationValue> vals, Void x) { for (AnnotationValue av : vals) { av.accept(new SimpleAnnotationValueVisitor6<Void, Void>() { @Override public Void visitAnnotation(AnnotationMirror am, Void x) { result.add(am); return null; } }, null); } return null; } }, null); return result; } private static String collectionAsString(final Collection<String> collection) { final StringBuilder sb = new StringBuilder(); Iterator<String> iterator = collection.iterator(); int i = 0; while (iterator.hasNext()) { final String next = iterator.next(); sb.append('"').append(next).append('"'); if (i + 1 < collection.size()) { sb.append(", "); } i++; } return sb.toString(); } public static String formatAssociatedResources(final Collection<String> resourceTypes) { if (resourceTypes == null || resourceTypes.size() == 0) { return null; } final StringBuilder sb = new StringBuilder(); sb.append("@AssociatedResources").append("({\n"); for (final String resourceType : resourceTypes) { sb.append(" ").append(resourceType).append(".class").append(",\n"); } sb.delete(sb.length() - 2, sb.length()); sb.append("\n})\n"); return sb.toString(); } private static String fqcnToSimpleName(String fqcn) { int lastIndexOfDot = fqcn.lastIndexOf('.'); if (lastIndexOfDot != -1) { return fqcn.substring(lastIndexOfDot + 1); } return fqcn; } public static boolean debugLoggingEnabled() { return Boolean.parseBoolean(System.getProperty("org.uberfire.processors.debug", "false")); } public static String extractAnnotationStringValue(Elements elementUtils, AnnotationMirror annotation, CharSequence paramName) { final AnnotationValue av = extractAnnotationPropertyValue(elementUtils, annotation, paramName); if (av != null && av.getValue() != null) { return av.getValue().toString(); } return null; } static AnnotationValue extractAnnotationPropertyValue(Elements elementUtils, AnnotationMirror annotation, CharSequence annotationProperty) { Map<? extends ExecutableElement, ? extends AnnotationValue> annotationParams = elementUtils.getElementValuesWithDefaults(annotation); for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> param : annotationParams.entrySet()) { if (param.getKey().getSimpleName().contentEquals(annotationProperty)) { return param.getValue(); } } return null; } /** * This method builds a list of all qualifier annotations source-code declaration that annotates the passed element. * @param element {@link TypeElement} which will be scanned for qualifier annotations. * @return A list of the annotations source-code declarations. */ public static List<String> getAllQualifiersDeclarationFromType(TypeElement element) { List<String> qualifiers = new ArrayList<>(); for (final AnnotationMirror am : element.getAnnotationMirrors()) { final TypeElement annotationElement = (TypeElement) am.getAnnotationType().asElement(); if (annotationElement.getAnnotation(Qualifier.class) != null) { qualifiers.add(am.toString()); } } return qualifiers; } }