001 /* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 * 014 * See the NOTICE file distributed with this work for additional 015 * information regarding copyright ownership. 016 */ 017 018 package com.osbcp.apbs.annotations; 019 020 import java.util.ArrayList; 021 import java.util.List; 022 import java.util.Set; 023 024 import javax.annotation.processing.AbstractProcessor; 025 import javax.annotation.processing.RoundEnvironment; 026 import javax.annotation.processing.SupportedAnnotationTypes; 027 import javax.lang.model.element.Element; 028 import javax.lang.model.element.ElementKind; 029 import javax.lang.model.element.Modifier; 030 import javax.lang.model.element.TypeElement; 031 import javax.lang.model.type.ExecutableType; 032 import javax.lang.model.type.TypeMirror; 033 import javax.lang.model.type.TypeVisitor; 034 import javax.lang.model.util.SimpleTypeVisitor6; 035 import javax.tools.Diagnostic.Kind; 036 037 /** 038 * The Annotation Processing Tool logic for the @IsAjaxPostBackEvent annotation. 039 * 040 * @see @IsAjaxPostBackEvent 041 * @see <a href="http://download.oracle.com/javase/6/docs/api/javax/annotation/processing/AbstractProcessor.html">AbstractProcessor</a> 042 * @author <a href="mailto:christoffer@christoffer.me">Christoffer Pettersson</a> 043 */ 044 045 @SupportedAnnotationTypes("com.osbcp.apbs.annotations.IsAjaxPostBackEvent") 046 public class AjaxPostBackAnnotationProcessor extends AbstractProcessor { 047 048 /** 049 * Used to toggle debug mode. 050 */ 051 052 public static boolean DEBUG = false; 053 054 /** 055 * AbstractProcessor.process 056 * 057 * @see <a href="http://download.oracle.com/javase/6/docs/api/javax/annotation/processing/AbstractProcessor.html#process%28java.util.Set,%20javax.annotation.processing.RoundEnvironment%29">AbstractProcessor.process</a> 058 * @param annotations The annotation types requested to be processed. 059 * @param roundEnvironment Environment for information about the current and prior round. 060 * @return Whether or not the set of annotations are claimed by this processor. 061 */ 062 063 @Override 064 public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnvironment) { 065 066 try { 067 068 StringBuilder debug = new StringBuilder(); 069 070 Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(IsAjaxPostBackEvent.class); 071 072 for (final Element classElement : elements) { 073 074 /* 075 * Check the parent class 076 */ 077 078 if (!classElement.getModifiers().contains(Modifier.PUBLIC)) { 079 error(classElement, debug, "The event '" + classElement.getSimpleName() + "' has to be public."); 080 return true; 081 } 082 083 /* 084 * Constructor 085 */ 086 087 List<Element> constructors = getEnclosedElements(debug, classElement, ElementKind.CONSTRUCTOR); 088 debug.append("found " + constructors.size() + " constructors\n"); 089 090 if (constructors.isEmpty()) { 091 error(classElement, debug, "The event '" + classElement.getSimpleName() + "' needs to have an constructor."); 092 } 093 094 if (constructors.size() > 1) { 095 error(classElement, debug, "The event '" + classElement.getSimpleName() + "' may only have one constructor."); 096 } 097 098 Element elementConstructor = constructors.get(0); 099 100 if (!elementConstructor.getModifiers().contains(Modifier.PUBLIC)) { 101 error(classElement, debug, "The event '" + classElement.getSimpleName() + "' has to have a public constructor."); 102 } 103 104 /* 105 * Get constructor data 106 */ 107 108 MethodData constructorData = getMethodData(elementConstructor); 109 debug.append("constructor-data-return-type:'" + constructorData.getReturnType() + "'\n"); 110 for (TypeMirror paraElemtn : constructorData.getParameterTypes()) { 111 debug.append("constructor-data-parameter:" + paraElemtn + "\n"); 112 } 113 for (Modifier modifier : elementConstructor.getModifiers()) { 114 debug.append("constructor-modifier:" + modifier.name() + "\n"); 115 } 116 117 /* 118 * Check for illegal constructor parameter types 119 */ 120 121 Object illegalParameterType = getIllegalConstructorParameterType(constructorData.getParameterTypes()); 122 if (illegalParameterType != null) { 123 error(classElement, debug, "The constructor parameter type '" + illegalParameterType.toString() + "' is illegal ."); 124 return true; 125 } 126 127 /* 128 * Check the call method 129 * But only if the class is not abstract 130 */ 131 132 if (!classElement.getModifiers().contains(Modifier.ABSTRACT)) { 133 134 List<Element> methods = getEnclosedElements(debug, classElement, ElementKind.METHOD); 135 List<Element> methodsCall = new ArrayList<Element>(); 136 137 for (Element method : methods) { 138 debug.append("method:" + method.getSimpleName() + ":" + method.getKind() + "\n"); 139 if ("call".equals(method.getSimpleName().toString())) { 140 methodsCall.add(method); 141 } 142 } 143 144 if (methodsCall.isEmpty()) { 145 error(classElement, debug, "The event '" + classElement.getSimpleName() + "' needs to have a public and static method namned 'call'."); 146 } 147 148 if (methodsCall.size() > 1) { 149 error(classElement, debug, "The event '" + classElement.getSimpleName() + "' may only have method called 'call'."); 150 } 151 152 Element elementCallMethod = methodsCall.get(0); 153 154 if (!elementCallMethod.getModifiers().contains(Modifier.PUBLIC) && !elementCallMethod.getModifiers().contains(Modifier.STATIC)) { 155 error(classElement, debug, "The 'call' method has to be both public and static."); 156 } 157 158 /* 159 * Get method data 160 */ 161 162 MethodData methodData = getMethodData(elementCallMethod); 163 debug.append("method-data-return-type1:'" + methodData.getReturnType() + "'\n"); 164 debug.append("method-data-return-type2:'" + methodData.getReturnType().toString() + "'\n"); 165 for (TypeMirror paraElemtn : methodData.getParameterTypes()) { 166 debug.append("method-data-parameter1:" + paraElemtn + "\n"); 167 } 168 169 /* 170 * Check the return type 171 */ 172 173 if (!"com.osbcp.apbs.AjaxPostBackCaller".equals(methodData.getReturnType().toString())) { 174 error(classElement, debug, "The 'call' method has to return the 'com.osbcp.apbs.AjaxPostBackCaller' type."); 175 } 176 177 /* 178 * Check the parameters 179 */ 180 181 if (constructorData.getParameterTypes().size() != methodData.getParameterTypes().size()) { 182 error(classElement, debug, getErrorMessageHasToHaveParameters(elementCallMethod, constructorData.getParameterTypes()) + " Incorrect number of parameters."); 183 return true; 184 } 185 186 // Check the parameters 187 for (int i = 0; i < methodData.getParameterTypes().size(); i++) { 188 189 String methodParameterType = methodData.getParameterTypes().get(i).toString(); 190 String constructorParameterType = constructorData.getParameterTypes().get(i).toString(); 191 192 boolean isSameParameterType = methodParameterType.equals(constructorParameterType); 193 boolean isJavaScriptType = "com.osbcp.jjsw.JavaScript".equals(methodParameterType); 194 195 debug.append("checking-parameter-" + i + ":" + constructorParameterType + "==" + methodParameterType + "?\n"); 196 debug.append("isSameParameterType=" + isSameParameterType + "\n"); 197 debug.append("isJavaScriptType=" + isJavaScriptType + "\n"); 198 199 if (!isSameParameterType && !isJavaScriptType) { 200 error(classElement, debug, getErrorMessageHasToHaveParameters(elementCallMethod, constructorData.getParameterTypes())); 201 return true; 202 } 203 204 } 205 206 } 207 208 } 209 210 } catch (final Throwable e) { 211 processingEnv.getMessager().printMessage(Kind.ERROR, e.toString()); 212 } 213 214 return true; 215 216 } 217 218 /** 219 * Checks a list of parameter types for any illegal types used for a constructor. 220 * 221 * @param parameterTypes The parameter types for a constructor. 222 * @return Any illegal parameter type. 223 */ 224 225 private Object getIllegalConstructorParameterType(final List<? extends TypeMirror> parameterTypes) { 226 227 List<String> legalTypes = new ArrayList<String>(); 228 legalTypes.add("java.lang.String"); 229 legalTypes.add("java.lang.Integer"); 230 legalTypes.add("int"); 231 legalTypes.add("java.lang.Byte"); 232 legalTypes.add("byte"); 233 legalTypes.add("java.lang.Short"); 234 legalTypes.add("short"); 235 legalTypes.add("java.lang.Long"); 236 legalTypes.add("long"); 237 legalTypes.add("java.lang.Float"); 238 legalTypes.add("float"); 239 legalTypes.add("java.lang.Double"); 240 legalTypes.add("double"); 241 legalTypes.add("java.lang.Boolean"); 242 legalTypes.add("boolean"); 243 legalTypes.add("java.lang.Character"); 244 legalTypes.add("character"); 245 246 for (Object parameter : parameterTypes) { 247 248 if (!legalTypes.contains(parameter.toString())) { 249 return parameter; 250 } 251 252 } 253 254 // No illegal type 255 return null; 256 257 } 258 259 /** 260 * Get a fancy error message for incorrect parameters. 261 * 262 * @param elementCallMethod The method element. 263 * @param list List of method parameter types. 264 * @return A fancy error message for incorrect parameters. 265 */ 266 private String getErrorMessageHasToHaveParameters(final Element elementCallMethod, final List<? extends TypeMirror> list) { 267 if (list.isEmpty()) { 268 return "The method '" + elementCallMethod.getSimpleName() + "' should not have any parameters."; 269 } else { 270 return "The method '" + elementCallMethod.getSimpleName() + "' has to have the parameters '" + StringUtil.toString(list) + "' or switch any parameter to a 'com.osbcp.jjsw.JavaScript' type."; 271 } 272 } 273 274 /** 275 * Displays the error message, uses the DEBUG variable to determine if the debug message should be displayed as well. 276 * 277 * @param element The element which caused the error. 278 * @param debug The Debug string. 279 * @param message The error message. 280 */ 281 282 private void error(final Element element, final StringBuilder debug, final String message) { 283 284 if (DEBUG) { 285 processingEnv.getMessager().printMessage(Kind.ERROR, debug.toString() + "\n\n" + message, element); 286 } else { 287 processingEnv.getMessager().printMessage(Kind.ERROR, message, element); 288 } 289 290 } 291 292 /** 293 * Returns method data of a method element. 294 * 295 * @param elementMethod The method element to check. 296 * @return Method data of a method element. 297 */ 298 299 private MethodData getMethodData(final Element elementMethod) { 300 301 MethodData data = new MethodData(); 302 303 TypeMirror mirror = elementMethod.asType(); 304 mirror.accept(visitor(data), null); 305 306 return data; 307 308 } 309 310 /** 311 * Method visitor 312 * @param data Method data that should be populated with data. 313 * @return A visitor 314 */ 315 316 private TypeVisitor<Boolean, Void> visitor(final MethodData data) { 317 318 return new SimpleTypeVisitor6<Boolean, Void>() { 319 320 public Boolean visitExecutable(ExecutableType t, Void v) { 321 data.setReturnType(t.getReturnType()); 322 data.setParameterTypes(t.getParameterTypes()); 323 return true; 324 } 325 326 }; 327 328 } 329 330 /** 331 * Returns a list of enclosed elements of an element. 332 * 333 * @param debug The debug string. 334 * @param element The parent element. 335 * @param elementKind The element kind. 336 * @return A list of enclosed elements of an element. 337 */ 338 339 private List<Element> getEnclosedElements(final StringBuilder debug, final Element element, final ElementKind elementKind) { 340 341 List<Element> list = new ArrayList<Element>(); 342 343 for (Element enclosedElement : element.getEnclosedElements()) { 344 345 if (enclosedElement.getKind() == elementKind) { 346 debug.append("adding " + elementKind + ":" + enclosedElement.getSimpleName() + ":" + enclosedElement.getKind() + "\n"); 347 list.add(enclosedElement); 348 } 349 350 } 351 352 return list; 353 354 } 355 356 }