001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.compiler;
020:
021: import org.apache.beehive.netui.compiler.genmodel.GenStrutsApp;
022: import org.apache.beehive.netui.compiler.grammar.ActionGrammar;
023: import org.apache.beehive.netui.compiler.grammar.ExceptionHandlerGrammar;
024: import org.apache.beehive.netui.compiler.grammar.WebappPathType;
025: import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationInstance;
026: import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationValue;
027: import org.apache.beehive.netui.compiler.typesystem.declaration.ClassDeclaration;
028: import org.apache.beehive.netui.compiler.typesystem.declaration.FieldDeclaration;
029: import org.apache.beehive.netui.compiler.typesystem.declaration.MethodDeclaration;
030: import org.apache.beehive.netui.compiler.typesystem.declaration.Modifier;
031: import org.apache.beehive.netui.compiler.typesystem.declaration.PackageDeclaration;
032: import org.apache.beehive.netui.compiler.typesystem.declaration.ParameterDeclaration;
033: import org.apache.beehive.netui.compiler.typesystem.declaration.TypeDeclaration;
034: import org.apache.beehive.netui.compiler.typesystem.env.CoreAnnotationProcessorEnv;
035: import org.apache.beehive.netui.compiler.typesystem.type.ClassType;
036: import org.apache.beehive.netui.compiler.typesystem.type.TypeInstance;
037: import org.apache.beehive.netui.compiler.processor.SilentDiagnostics;
038:
039: import java.io.File;
040: import java.io.FilenameFilter;
041: import java.io.IOException;
042: import java.util.ArrayList;
043: import java.util.Collection;
044: import java.util.HashMap;
045: import java.util.HashSet;
046: import java.util.Iterator;
047: import java.util.List;
048: import java.util.Map;
049: import java.util.Set;
050:
051: public abstract class FlowControllerChecker extends BaseChecker
052: implements JpfLanguageConstants {
053: private AnnotationGrammar _controllerGrammar;
054: private AnnotationGrammar _actionGrammar;
055: private AnnotationGrammar _exceptionHandlerGrammar;
056: private AnnotationGrammar _actionGrammarSilentDiagnostics;
057: private AnnotationGrammar _exceptionHandlerGrammarSilentDiagnostics;
058: private FormBeanChecker _formBeanChecker;
059: private Map _checkResultMap;
060:
061: protected FlowControllerChecker(CoreAnnotationProcessorEnv env,
062: FlowControllerInfo fcInfo, Diagnostics diags) {
063: super (env, fcInfo, diags);
064: }
065:
066: protected void doAdditionalClassChecks(ClassDeclaration jpfClass) {
067: }
068:
069: protected Map getCheckResultMap() {
070: return _checkResultMap;
071: }
072:
073: protected abstract String getDesiredBaseClass(
074: ClassDeclaration jclass);
075:
076: protected abstract AnnotationGrammar getControllerGrammar();
077:
078: public Map onCheck(ClassDeclaration jclass)
079: throws FatalCompileTimeException {
080: FlowControllerInfo fcInfo = getFCSourceFileInfo();
081:
082: _checkResultMap = new HashMap();
083: _controllerGrammar = getControllerGrammar();
084: _actionGrammar = new ActionGrammar(getEnv(), getDiagnostics(),
085: getRuntimeVersionChecker(), fcInfo);
086: _exceptionHandlerGrammar = new ExceptionHandlerGrammar(
087: getEnv(), getDiagnostics(), getRuntimeVersionChecker(),
088: fcInfo);
089: _formBeanChecker = new FormBeanChecker(getEnv(),
090: getDiagnostics());
091:
092: SilentDiagnostics silentDiagnostics = new SilentDiagnostics();
093: _actionGrammarSilentDiagnostics = new ActionGrammar(getEnv(),
094: silentDiagnostics, getRuntimeVersionChecker(), fcInfo);
095: _exceptionHandlerGrammarSilentDiagnostics = new ExceptionHandlerGrammar(
096: getEnv(), silentDiagnostics,
097: getRuntimeVersionChecker(), fcInfo);
098:
099: fcInfo.startBuild(getEnv(), jclass);
100:
101: try {
102: return onCheckInternal(jclass);
103: } finally {
104: fcInfo.endBuild();
105: }
106: }
107:
108: private Map onCheckInternal(ClassDeclaration jclass)
109: throws FatalCompileTimeException {
110: FlowControllerInfo fcInfo = getFCSourceFileInfo();
111:
112: //
113: // Check the base class.
114: //
115: String desiredBaseClass = getDesiredBaseClass(jclass);
116: if (desiredBaseClass != null
117: && !CompilerUtils.isAssignableFrom(desiredBaseClass,
118: jclass, getEnv())) {
119: getDiagnostics().addError(jclass,
120: "error.does-not-extend-base", desiredBaseClass);
121: }
122:
123: //
124: // Check the annotations on the class.
125: //
126: startCheckClass(jclass);
127:
128: //
129: // Check the fields. Note that we're checking public and protected inherited fields, too.
130: //
131: Collection fields = CompilerUtils.getClassFields(jclass);
132:
133: for (Iterator ii = fields.iterator(); ii.hasNext();) {
134: FieldDeclaration field = (FieldDeclaration) ii.next();
135: checkField(field, jclass);
136: }
137:
138: //
139: // Check the methods. Note that we're checking public and protected inherited methods, too.
140: //
141: MethodDeclaration[] methods = CompilerUtils.getClassMethods(
142: jclass, null);
143:
144: for (int i = 0; i < methods.length; i++) {
145: MethodDeclaration method = methods[i];
146: TypeDeclaration declaringType = method.getDeclaringType();
147:
148: //
149: // Only add diagnostics if the method is in this class, or if it's inherited from a class that's *not* on
150: // sourcepath (i.e., its SourcePosition is null).
151: //
152: if (declaringType.equals(jclass)
153: || declaringType.getPosition() == null) {
154: checkMethod(method, jclass, _actionGrammar,
155: _exceptionHandlerGrammar);
156: } else {
157: //
158: // We still want to run the checks, which aggregate information into the FlowControllerInfo. We just
159: // don't want diagnostics to be printed.
160: //
161: checkMethod(method, jclass,
162: _actionGrammarSilentDiagnostics,
163: _exceptionHandlerGrammarSilentDiagnostics);
164: }
165: }
166:
167: //
168: // Check the inner classes.
169: //
170: Collection innerTypes = CompilerUtils
171: .getClassNestedTypes(jclass);
172:
173: for (Iterator ii = innerTypes.iterator(); ii.hasNext();) {
174: TypeDeclaration innerType = (TypeDeclaration) ii.next();
175: if (innerType instanceof ClassDeclaration)
176: checkInnerClass((ClassDeclaration) innerType);
177: }
178:
179: //
180: // Run additional .jpf- or .app-specific checks.
181: //
182: doAdditionalClassChecks(jclass);
183:
184: //
185: // Runtime performance enhancement: enable saving of previous-page and previous-action information based on
186: // whether there were Forwards that contained navigateTo attributes.
187: //
188: enableNavigateTo(jclass,
189: fcInfo.getMergedControllerAnnotation(), fcInfo);
190: Map sharedFlowTypes = fcInfo.getSharedFlowTypes();
191:
192: if (sharedFlowTypes != null) {
193: for (Iterator ii = sharedFlowTypes.values().iterator(); ii
194: .hasNext();) {
195: TypeDeclaration sharedFlowType = (TypeDeclaration) ii
196: .next();
197: //
198: // Saving of previous-page/previous-action info must be enabled if any of the referenced shared flows
199: // use this feature.
200: //
201: enableNavigateTo(sharedFlowType,
202: new MergedControllerAnnotation(sharedFlowType),
203: fcInfo);
204: }
205: }
206:
207: endCheckClass(jclass);
208: _checkResultMap.put(
209: JpfLanguageConstants.ExtraInfoKeys.flowControllerInfo,
210: fcInfo);
211: return _checkResultMap;
212: }
213:
214: private static void enableNavigateTo(
215: TypeDeclaration flowControllerClass,
216: MergedControllerAnnotation controllerAnn,
217: FlowControllerInfo fcInfo) {
218: //
219: // Look through Forwards and SimpleActions in the Controller annotation.
220: //
221: enableNavigateTo(controllerAnn.getForwards(), fcInfo);
222: enableNavigateTo(controllerAnn.getSimpleActions(), fcInfo);
223:
224: //
225: // Look through Forwards on Action and ExceptionHandler methods.
226: //
227: MethodDeclaration[] methods = CompilerUtils.getClassMethods(
228: flowControllerClass, null);
229:
230: for (int i = 0; i < methods.length; i++) {
231: MethodDeclaration method = methods[i];
232: AnnotationInstance ann = CompilerUtils.getAnnotation(
233: method, ACTION_TAG_NAME);
234:
235: if (ann != null) {
236: enableNavigateTo(CompilerUtils.getAnnotation(ann,
237: VALIDATION_ERROR_FORWARD_ATTR, true), fcInfo);
238: }
239:
240: if (ann == null)
241: ann = CompilerUtils.getAnnotation(method,
242: EXCEPTION_HANDLER_TAG_NAME);
243: if (ann != null)
244: enableNavigateTo(CompilerUtils.getAnnotationArray(ann,
245: FORWARDS_ATTR, true), fcInfo);
246: }
247: }
248:
249: private static void enableNavigateTo(Collection childAnnotations,
250: FlowControllerInfo fcInfo) {
251: if (childAnnotations != null) {
252: for (Iterator ii = childAnnotations.iterator(); ii
253: .hasNext();) {
254: AnnotationInstance childAnnotation = (AnnotationInstance) ii
255: .next();
256: enableNavigateTo(childAnnotation, fcInfo);
257: }
258: }
259: }
260:
261: private static void enableNavigateTo(AnnotationInstance ann,
262: FlowControllerInfo fcInfo) {
263: if (ann == null)
264: return;
265: String val = CompilerUtils.getEnumFieldName(ann,
266: NAVIGATE_TO_ATTR, true);
267:
268: if (val != null) {
269: if (val.equals(NAVIGATE_TO_CURRENT_PAGE_STR)
270: || val.equals(NAVIGATE_TO_PREVIOUS_PAGE_STR)
271: || val.equals(NAVIGATE_TO_PAGE_LEGACY_STR)) {
272: fcInfo.enableNavigateToPage();
273: } else if (val.equals(NAVIGATE_TO_PREVIOUS_ACTION_STR)) {
274: fcInfo.enableNavigateToAction();
275: }
276: }
277: }
278:
279: protected void endCheckClass(ClassDeclaration jclass) {
280: }
281:
282: protected abstract GenStrutsApp createStrutsApp(
283: ClassDeclaration jclass) throws IOException,
284: FatalCompileTimeException;
285:
286: protected void startCheckClass(ClassDeclaration jclass)
287: throws FatalCompileTimeException {
288: //
289: // Check for basic things like writability of the struts-config file.
290: //
291: GenStrutsApp strutsApp = null;
292: File strutsConfigFile = null;
293:
294: //
295: // Make sure we can write to the struts-config XML file.
296: //
297: try {
298: strutsApp = createStrutsApp(jclass);
299: strutsConfigFile = strutsApp.getStrutsConfigFile();
300: } catch (IOException e) {
301: // will be reported at generate time
302: }
303:
304: if (strutsConfigFile != null) {
305: getFCSourceFileInfo().addReferencedFile(strutsConfigFile);
306:
307: if (strutsConfigFile.exists() && strutsApp != null
308: && !strutsApp.canWrite()) {
309: getDiagnostics().addError(jclass,
310: "error.struts-config-not-writable",
311: strutsConfigFile);
312: }
313: }
314:
315: getRuntimeVersionChecker().checkRuntimeVersion(
316: VERSION_8_SP2_STRING, jclass, getDiagnostics(),
317: "warning.runtime-version",
318: new Object[] { PAGEFLOW_RUNTIME_JAR });
319:
320: //
321: // Check the Jpf.Controller annotation on this class.
322: //
323: AnnotationInstance controllerAnnotation = CompilerUtils
324: .getAnnotation(jclass, CONTROLLER_TAG_NAME);
325: if (controllerAnnotation != null)
326: _controllerGrammar
327: .check(controllerAnnotation, null, jclass);
328:
329: //
330: // Check relative paths on Jpf.Catch, Jpf.Forward, and Jpf.SimpleAction annotations on superclasses.
331: // If inheritLocalPaths is set to true on @Jpf.Controller, then we don't need to do this check, since
332: // inherited paths will always resolve.
333: //
334: if (!getFCSourceFileInfo().getMergedControllerAnnotation()
335: .isInheritLocalPaths()) {
336: checkInheritedRelativePaths(jclass);
337: }
338:
339: // check for duplicate expressions in conditional forwards
340: checkConditionalForwardsExpressions(jclass);
341: }
342:
343: /**
344: * Check relative paths in annotations inherited from a base class.
345: */
346: private void checkInheritedRelativePaths(ClassDeclaration jclass)
347: throws FatalCompileTimeException {
348: for (ClassType type = jclass.getSuperclass(); type != null
349: && CompilerUtils.isAssignableFrom(
350: FLOWCONTROLLER_BASE_CLASS, type, getEnv()); type = type
351: .getSuperclass()) {
352: TypeDeclaration decl = CompilerUtils.getDeclaration(type);
353:
354: //
355: // Check simple actions in the Controller annotation.
356: //
357: List simpleActions = CompilerUtils.getAnnotationArrayValue(
358: decl, CONTROLLER_TAG_NAME, SIMPLE_ACTIONS_ATTR,
359: true);
360:
361: if (simpleActions != null) {
362: for (Iterator j = simpleActions.iterator(); j.hasNext();) {
363: AnnotationInstance i = (AnnotationInstance) j
364: .next();
365: checkRelativePath(i, PATH_ATTR, jclass, decl, false);
366: List conditionalForwards = CompilerUtils
367: .getAnnotationArray(i,
368: CONDITIONAL_FORWARDS_ATTR, true);
369:
370: if (conditionalForwards != null) {
371: for (Iterator k = conditionalForwards
372: .iterator(); k.hasNext();) {
373: AnnotationInstance ann = (AnnotationInstance) k
374: .next();
375: checkRelativePath(ann, PATH_ATTR, jclass,
376: decl, false);
377: }
378: }
379: }
380: }
381:
382: //
383: // Check Forwards in the Controller annotation.
384: //
385: List forwards = CompilerUtils.getAnnotationArrayValue(decl,
386: CONTROLLER_TAG_NAME, FORWARDS_ATTR, true);
387:
388: if (forwards != null) {
389: for (Iterator ii = forwards.iterator(); ii.hasNext();) {
390: AnnotationInstance i = (AnnotationInstance) ii
391: .next();
392: checkRelativePath(i, PATH_ATTR, jclass, decl, false);
393: }
394: }
395:
396: //
397: // Check Catches in the Controller annotation.
398: //
399: List catches = CompilerUtils.getAnnotationArrayValue(decl,
400: CONTROLLER_TAG_NAME, CATCHES_ATTR, true);
401:
402: if (catches != null) {
403: for (Iterator j = catches.iterator(); j.hasNext();) {
404: AnnotationInstance i = (AnnotationInstance) j
405: .next();
406: checkRelativePath(i, PATH_ATTR, jclass, decl, false);
407: }
408: }
409:
410: //
411: // Check strutsMerge and validatorMerge in the Controller annotation.
412: //
413: AnnotationInstance controllerAnnotation = CompilerUtils
414: .getAnnotation(decl, CONTROLLER_TAG_NAME);
415:
416: if (controllerAnnotation != null) {
417: checkRelativePath(controllerAnnotation,
418: VALIDATOR_MERGE_ATTR, jclass, decl, true);
419: checkRelativePath(controllerAnnotation,
420: STRUTSMERGE_ATTR, jclass, decl, true);
421: }
422:
423: //
424: // Check Forwards and Catches on action methods and exception-handler methods.
425: //
426: MethodDeclaration[] methods = decl.getMethods();
427: for (int i = 0; i < methods.length; i++) {
428: MethodDeclaration method = methods[i];
429: AnnotationInstance ann = CompilerUtils.getAnnotation(
430: method, ACTION_TAG_NAME);
431: if (ann == null)
432: ann = CompilerUtils.getAnnotation(method,
433: EXCEPTION_HANDLER_TAG_NAME);
434:
435: if (ann != null) {
436: List methodForwards = CompilerUtils
437: .getAnnotationArray(ann, FORWARDS_ATTR,
438: true);
439: String methodName = method.getSimpleName();
440:
441: if (methodForwards != null) {
442: for (Iterator j = methodForwards.iterator(); j
443: .hasNext();) {
444: AnnotationInstance methodForward = (AnnotationInstance) j
445: .next();
446: checkRelativePath(methodName,
447: methodForward, PATH_ATTR, jclass,
448: decl, false);
449: }
450: }
451:
452: List methodCatches = CompilerUtils
453: .getAnnotationArray(ann, CATCHES_ATTR, true);
454:
455: if (methodCatches != null) {
456: for (Iterator j = methodCatches.iterator(); j
457: .hasNext();) {
458: AnnotationInstance methodCatch = (AnnotationInstance) j
459: .next();
460: checkRelativePath(methodName, methodCatch,
461: PATH_ATTR, jclass, decl, false);
462: }
463: }
464: }
465: }
466: }
467: }
468:
469: private void checkRelativePath(AnnotationInstance ann,
470: String memberName, TypeDeclaration jclass,
471: TypeDeclaration baseType, boolean isError)
472: throws FatalCompileTimeException {
473: if (ann != null) {
474: AnnotationValue pathVal = CompilerUtils.getAnnotationValue(
475: ann, memberName, true);
476:
477: if (pathVal != null) {
478: String path = (String) pathVal.getValue();
479:
480: if (path.length() > 0
481: && path.charAt(0) != '/'
482: && !WebappPathType.relativePathExists(path,
483: jclass, getEnv())) {
484: String[] args = {
485: path,
486: ANNOTATION_INTERFACE_PREFIX
487: + ann.getAnnotationType()
488: .getDeclaration()
489: .getSimpleName(),
490: baseType.getQualifiedName() };
491:
492: if (isError) {
493: getDiagnostics().addErrorArrayArgs(ann,
494: "message.inherited-file-not-found",
495: args);
496: } else {
497: getDiagnostics().addWarningArrayArgs(ann,
498: "message.inherited-file-not-found",
499: args);
500: }
501: }
502: }
503: }
504: }
505:
506: private void checkRelativePath(String methodName,
507: AnnotationInstance ann, String memberName,
508: TypeDeclaration jclass, TypeDeclaration baseType,
509: boolean isError) throws FatalCompileTimeException {
510: if (ann != null) {
511: AnnotationValue pathVal = CompilerUtils.getAnnotationValue(
512: ann, memberName, true);
513:
514: if (pathVal != null) {
515: String path = (String) pathVal.getValue();
516:
517: if (path.length() > 0
518: && path.charAt(0) != '/'
519: && !WebappPathType.relativePathExists(path,
520: jclass, getEnv())) {
521: String[] args = {
522: path,
523: ANNOTATION_INTERFACE_PREFIX
524: + ann.getAnnotationType()
525: .getDeclaration()
526: .getSimpleName(),
527: methodName, baseType.getQualifiedName() };
528:
529: if (isError) {
530: getDiagnostics()
531: .addErrorArrayArgs(
532: jclass,
533: "message.method-inherited-file-not-found",
534: args);
535: } else {
536: getDiagnostics()
537: .addWarningArrayArgs(
538: jclass,
539: "message.method-inherited-file-not-found",
540: args);
541: }
542: }
543: }
544: }
545: }
546:
547: /**
548: * Check for duplicate expressions in conditional forwards of the
549: * simple action annotations.
550: */
551: private void checkConditionalForwardsExpressions(
552: ClassDeclaration jclass) throws FatalCompileTimeException {
553: // Get simple actions in the Controller annotation.
554: List simpleActions = CompilerUtils.getAnnotationArrayValue(
555: jclass, CONTROLLER_TAG_NAME, SIMPLE_ACTIONS_ATTR, true);
556:
557: if (simpleActions != null) {
558: HashSet expressionsFound = new HashSet();
559: for (Iterator j = simpleActions.iterator(); j.hasNext();) {
560: AnnotationInstance sa = (AnnotationInstance) j.next();
561: List conditionalForwards = CompilerUtils
562: .getAnnotationArray(sa,
563: CONDITIONAL_FORWARDS_ATTR, true);
564:
565: if (conditionalForwards != null) {
566: for (Iterator k = conditionalForwards.iterator(); k
567: .hasNext();) {
568: AnnotationInstance cf = (AnnotationInstance) k
569: .next();
570: String expression = CompilerUtils.getString(cf,
571: CONDITION_ATTR, true);
572: assert expression != null;
573:
574: if (expressionsFound.contains(expression)) {
575: String name = CompilerUtils.getString(sa,
576: NAME_ATTR, true);
577: getDiagnostics()
578: .addWarning(
579: cf,
580: "warning.duplicate-conditional-forward-expression",
581: expression, name);
582: } else {
583: expressionsFound.add(expression);
584: }
585: }
586: }
587: expressionsFound.clear();
588: }
589: }
590: }
591:
592: protected void checkField(FieldDeclaration field,
593: TypeDeclaration jclass) {
594: //
595: // Only warn about nonserializable member data that's defined in this particular class.
596: //
597: if (CompilerUtils.typesAreEqual(jclass, field
598: .getDeclaringType())) {
599: TypeInstance type = field.getType();
600:
601: if (!field.hasModifier(Modifier.TRANSIENT)
602: && !field.hasModifier(Modifier.STATIC)
603: && type instanceof ClassType
604: && !CompilerUtils.isAssignableFrom(
605: SERIALIZABLE_CLASS_NAME, type, getEnv())) {
606: getDiagnostics().addWarning(field,
607: "warning.nonserializable-member-data");
608: }
609: }
610: }
611:
612: protected void checkMethod(MethodDeclaration method,
613: ClassDeclaration jclass, AnnotationGrammar actionGrammar,
614: AnnotationGrammar exceptionHandlerGrammar)
615: throws FatalCompileTimeException {
616: AnnotationInstance[] annotations = method
617: .getAnnotationInstances();
618:
619: for (int i = 0; i < annotations.length; i++) {
620: AnnotationInstance annotation = annotations[i];
621: String annotationName = CompilerUtils.getDeclaration(
622: annotation.getAnnotationType()).getSimpleName();
623:
624: if (annotationName.equals(ACTION_TAG_NAME)) {
625: actionGrammar.check(annotation, null, method);
626:
627: if (!CompilerUtils.isAssignableFrom(FORWARD_CLASS_NAME,
628: method.getReturnType(), getEnv())) {
629: getDiagnostics().addError(method,
630: "error.method-wrong-return-type",
631: FORWARD_CLASS_NAME);
632: }
633: } else if (annotationName
634: .equals(EXCEPTION_HANDLER_TAG_NAME)) {
635: exceptionHandlerGrammar.check(annotation, null, method);
636: checkExceptionHandlerMethod(method);
637: }
638: }
639: }
640:
641: /*
642: * Run a check of form bean classes that might not otherwise
643: * be processed. A @FormBean instance will get processed by the
644: * PageFlowCoreAnnotationProcessor and FormBeanChecker. Otherwise,
645: * run the class through the FormBeanChecker for other possible errors.
646: */
647: private void checkInnerClass(ClassDeclaration jclass)
648: throws FatalCompileTimeException {
649: boolean noFormBeanAnnotation = true;
650: if (CompilerUtils.getAnnotation(jclass, FORM_BEAN_TAG_NAME,
651: true) != null) {
652: noFormBeanAnnotation = false;
653: } else {
654: //
655: // check the methods for validatable property annotations that
656: // would be processed elsewhere.
657: //
658: MethodDeclaration[] methods = CompilerUtils
659: .getClassMethods(jclass, null);
660: for (int i = 0; i < methods.length && noFormBeanAnnotation; i++) {
661: MethodDeclaration method = methods[i];
662: if (CompilerUtils.getAnnotation(method,
663: VALIDATABLE_PROPERTY_TAG_NAME) != null) {
664: noFormBeanAnnotation = false;
665: }
666: }
667: }
668:
669: if (noFormBeanAnnotation) {
670: _formBeanChecker.check(jclass);
671: }
672: }
673:
674: private void checkExceptionHandlerMethod(MethodDeclaration method) {
675: if (!CompilerUtils.isAssignableFrom(FORWARD_CLASS_NAME, method
676: .getReturnType(), getEnv())) {
677: getDiagnostics().addError(method,
678: "error.method-wrong-return-type",
679: FORWARD_CLASS_NAME);
680: }
681:
682: ParameterDeclaration[] parameters = method.getParameters();
683:
684: if (parameters.length == 4) {
685: if (!CompilerUtils.isAssignableFrom(THROWABLE_CLASS_NAME,
686: parameters[0].getType(), getEnv())) {
687: getDiagnostics().addError(method,
688: "error.exception-method-wrong-exception-arg",
689: THROWABLE_CLASS_NAME);
690: }
691:
692: checkExceptionHandlerArgType(method, parameters, 1,
693: STRING_CLASS_NAME);
694: checkExceptionHandlerArgType(method, parameters, 2,
695: STRING_CLASS_NAME);
696:
697: //
698: // The use of org.apache.struts.action.ActionForm or org.apache.beehive.netui.pageflow.FormData as the
699: // fourth argument is deprecated. Forms can be any Object type now.
700: //
701: if (CompilerUtils.isAssignableFrom(STRUTS_FORM_CLASS_NAME,
702: parameters[3].getType(), getEnv())) {
703: getDiagnostics().addWarning(method,
704: "warning.exception-method-deprecated-form-arg");
705: } else {
706: checkExceptionHandlerArgType(method, parameters, 3,
707: OBJECT_CLASS_NAME);
708: }
709: } else {
710: getDiagnostics().addError(method,
711: "error.exception-method-wrong-arg-count",
712: new Integer(4));
713: }
714: }
715:
716: private void checkExceptionHandlerArgType(MethodDeclaration method,
717: ParameterDeclaration[] parameters, int index,
718: String className) {
719: if (!CompilerUtils.isOfClass(parameters[index].getType(),
720: className, getEnv())) {
721: getDiagnostics().addError(method,
722: "error.exception-method-wrong-arg-type",
723: new Integer(index + 1), className);
724: }
725: }
726:
727: protected void checkForOverlappingClasses(
728: ClassDeclaration jpfClass, String baseClass,
729: String fileExtension, String errorKey) {
730: File jpfFile = CompilerUtils.getSourceFile(jpfClass, true);
731: File parentDir = jpfFile.getParentFile();
732: PackageDeclaration pkg = jpfClass.getPackage();
733: ClassDeclaration[] packageClasses = pkg.getClasses();
734: Set overlapping = new HashSet();
735: List overlappingFiles = new ArrayList();
736:
737: //
738: // First go through the other classes in this package to look for other classes of this type. Only one per
739: // directory is allowed.
740: //
741: for (int i = 0; i < packageClasses.length; i++) {
742: ClassDeclaration classDecl = packageClasses[i];
743: if (CompilerUtils.getAnnotation(classDecl,
744: CONTROLLER_TAG_NAME) != null
745: && CompilerUtils.isAssignableFrom(baseClass,
746: classDecl, getEnv())) {
747: File file = CompilerUtils.getSourceFile(classDecl,
748: false);
749:
750: //
751: // Add the dependency if it's a different file and if the file exists (it may have been deleted
752: // sometime after the list of classes in this package got built.
753: //
754: if (!jpfFile.equals(file) && file != null
755: && file.exists()) {
756: overlapping.add(file.getName());
757: overlappingFiles.add(file);
758: }
759: }
760: }
761:
762: //
763: // Additionally, we'll go through the parent directory to make sure there are no other files of this type.
764: // This is a double-check for the case where duplicate files have the same class names inside them, which means
765: // that iterating through the list of package classes is hit or miss (only one of them will show up, and it may
766: // be this class or the duplicate class).
767: //
768: File[] peers = parentDir.listFiles(new ExtensionFileFilter(
769: fileExtension));
770:
771: if (peers != null) // make sure the directory hasn't been deleted while we're running
772: {
773: for (int i = 0; i < peers.length; i++) {
774: File peer = peers[i];
775: if (!peer.equals(jpfFile)) {
776: String name = peer.getName();
777:
778: if (!overlapping.contains(name)) {
779: overlapping.add(name);
780: overlappingFiles.add(peer);
781: }
782: }
783: }
784: }
785:
786: int len = overlapping.size();
787: if (len > 0) {
788: if (len > 3) {
789: getDiagnostics().addErrorArrayArgs(jpfClass, errorKey,
790: overlapping.toArray());
791: } else {
792: getDiagnostics().addErrorArrayArgs(jpfClass,
793: errorKey + len, overlapping.toArray());
794: }
795: }
796:
797: getCheckResultMap()
798: .put(
799: JpfLanguageConstants.ExtraInfoKeys.overlappingPageFlowFiles,
800: overlappingFiles);
801: }
802:
803: private static class ExtensionFileFilter implements FilenameFilter {
804: private String _extension;
805:
806: public ExtensionFileFilter(String extension) {
807: _extension = extension;
808: }
809:
810: public boolean accept(File dir, String name) {
811: return name.endsWith(_extension);
812: }
813: }
814:
815: protected FlowControllerInfo getFCSourceFileInfo() {
816: return (FlowControllerInfo) super.getSourceFileInfo();
817: }
818: }
|