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.controls.system.jdbc.parser;
020:
021: import com.sun.mirror.declaration.ClassDeclaration;
022: import com.sun.mirror.declaration.FieldDeclaration;
023: import com.sun.mirror.declaration.MethodDeclaration;
024: import com.sun.mirror.declaration.ParameterDeclaration;
025: import com.sun.mirror.declaration.TypeDeclaration;
026: import com.sun.mirror.type.DeclaredType;
027: import com.sun.mirror.type.TypeMirror;
028: import com.sun.mirror.util.DeclarationFilter;
029: import org.apache.beehive.controls.api.ControlException;
030:
031: import java.util.ArrayList;
032: import java.util.Collection;
033: import java.util.HashMap;
034: import java.util.ResourceBundle;
035: import java.util.Locale;
036: import java.text.MessageFormat;
037:
038: /**
039: * Does compile-time checking of reflection parameteres in the SQL annotations's statement
040: * element vs. method parameters. Invoked by the JdbcControlChecker.
041: */
042: public class ParameterChecker {
043:
044: /**
045: * Verify that all reflection parameters in the statement element can be mapped to method parameters.
046: *
047: * @param statement The parsed statement element.
048: * @param methodDecl The method declaration which was annotated.
049: */
050: public static void checkReflectionParameters(
051: SqlFragmentContainer statement, MethodDeclaration methodDecl) {
052:
053: ArrayList<ParameterDeclaration> params = new ArrayList<ParameterDeclaration>(
054: methodDecl.getParameters());
055: HashMap<String, ParameterDeclaration> paramMap = new HashMap<String, ParameterDeclaration>();
056:
057: // don't run these checks if this is a compiled class file (method names replaced with arg0, arg1, etc)
058: if (params.size() > 0
059: && params.get(0).getSimpleName().equals("arg0")) {
060: return;
061: }
062:
063: for (int i = 0; i < params.size(); i++) {
064: paramMap.put(params.get(i).getSimpleName(), params.get(i));
065: }
066:
067: doCheck(statement, paramMap, methodDecl);
068: }
069:
070: /**
071: * Walk the tree of children of the statement, process all children of type ReflectionFragment.
072: *
073: * @param statement The parsed statement element.
074: * @param paramMap The method parameters, keyed by name.
075: * @param method The method declaration which was annotated.
076: */
077: private static void doCheck(SqlFragmentContainer statement,
078: HashMap<String, ParameterDeclaration> paramMap,
079: final MethodDeclaration method) {
080:
081: SqlFragment[] fragments = statement.getChildren();
082: for (SqlFragment fragment : fragments) {
083:
084: // if the fragment is a container check all of its children.
085: if (fragment instanceof SqlFragmentContainer) {
086: doCheck((SqlFragmentContainer) fragment, paramMap,
087: method);
088:
089: // reflection fragment - make sure it can be mapped using the method's param values.
090: } else if (fragment instanceof ReflectionFragment) {
091: checkReflectionFragment((ReflectionFragment) fragment,
092: paramMap, method);
093: }
094: }
095: }
096:
097: /**
098: * Check the fragment. Must be able to resolve references like 'foo.bar' -> 'foo' where 'foo' is a method param
099: * of a type which contains a public getter or field named 'getBar()' or 'bar' respectively.
100: *
101: * @param fragment The reflection fragment to check.
102: * @param paramMap The method parameters, keyed by name.
103: * @param method The method declaration which was annotated.
104: */
105: private static void checkReflectionFragment(
106: ReflectionFragment fragment,
107: HashMap<String, ParameterDeclaration> paramMap,
108: MethodDeclaration method) {
109:
110: final String[] paramNameQualifiers = ((ReflectionFragment) fragment)
111: .getParameterNameQualifiers();
112: final String parameterName = ((ReflectionFragment) fragment)
113: .getParameterName();
114:
115: if (paramMap.containsKey(paramNameQualifiers[0]) == false) {
116: throw new ControlException(buildMessage(parameterName,
117: method.getSimpleName()));
118: }
119:
120: ParameterDeclaration tpd = paramMap.get(paramNameQualifiers[0]);
121: TypeMirror type = tpd.getType();
122:
123: MethodDeclaration getterMethod = null;
124: FieldDeclaration field = null;
125:
126: for (int i = 1; i < paramNameQualifiers.length; i++) {
127:
128: getterMethod = null;
129: field = null;
130:
131: // loop through superclasses until we find a match or run out of superclasses
132: while (type != null) {
133:
134: if (type instanceof DeclaredType == false) {
135: throw new ControlException(buildMessage(
136: parameterName, method.getSimpleName()));
137: }
138:
139: TypeDeclaration td = ((DeclaredType) type)
140: .getDeclaration();
141: //
142: // abort if Map!!! No further checking can be done.
143: //
144: if (td.getQualifiedName().equals("java.util.Map")) {
145: return;
146: }
147:
148: Collection<? extends MethodDeclaration> methods = DeclarationFilter.FILTER_PUBLIC
149: .filter(td.getMethods());
150: for (MethodDeclaration m : methods) {
151: String upperFirst = paramNameQualifiers[i]
152: .substring(0, 1).toUpperCase();
153: if (paramNameQualifiers[i].length() > 1) {
154: upperFirst = upperFirst
155: + paramNameQualifiers[i].substring(1);
156: }
157: if (m.getSimpleName().equals("get" + upperFirst)
158: || m.getSimpleName().equals(
159: "is" + upperFirst)) {
160: getterMethod = m;
161: }
162: }
163:
164: if (getterMethod == null) {
165: Collection<FieldDeclaration> fields = DeclarationFilter.FILTER_PUBLIC
166: .filter(td.getFields());
167: for (FieldDeclaration fd : fields) {
168: if (fd.getSimpleName().equals(
169: paramNameQualifiers[i])) {
170: field = fd;
171: }
172: }
173: }
174:
175: // try the super-class
176: if (getterMethod == null && field == null) {
177: if (td instanceof ClassDeclaration) {
178: type = ((ClassDeclaration) td).getSuperclass();
179: continue;
180: }
181: }
182:
183: break;
184: } // while
185:
186: // found a match, get its type and continue within the for loop
187: if (getterMethod != null) {
188: type = getterMethod.getReturnType();
189: } else if (field != null) {
190: type = field.getType();
191: } else {
192: throw new ControlException(buildMessage(parameterName,
193: method.getSimpleName()));
194: }
195: }
196: }
197:
198: /**
199: * Build the error message for this module.
200: *
201: * @param parameterName
202: * @param methodName
203: * @return The generated messge.
204: */
205: private static String buildMessage(String parameterName,
206: String methodName) {
207:
208: ResourceBundle rb = ResourceBundle
209: .getBundle(
210: "org.apache.beehive.controls.system.jdbc.parser.strings",
211: Locale.getDefault());
212: String pattern = rb.getString("jdbccontrol.invalid.param");
213: return MessageFormat.format(pattern, parameterName, methodName);
214: }
215: }
|