001: /**********************************************************************
002: Copyright (c) 2005 Andy Jefferson and others. All rights reserved.
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014:
015:
016: Contributors:
017: ...
018: **********************************************************************/package org.jpox.store.query;
019:
020: import java.lang.reflect.Constructor;
021: import java.lang.reflect.Field;
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Modifier;
024: import java.math.BigDecimal;
025: import java.math.BigInteger;
026: import java.text.CharacterIterator;
027: import java.text.StringCharacterIterator;
028: import java.util.ArrayList;
029: import java.util.Map;
030:
031: import org.jpox.ClassNameConstants;
032: import org.jpox.exceptions.JPOXUserException;
033: import org.jpox.util.ClassUtils;
034: import org.jpox.util.JPOXLogger;
035: import org.jpox.util.Localiser;
036: import org.jpox.util.TypeConversionHelper;
037:
038: /**
039: * Utilities for use in queries.
040: *
041: * @version $Revision: 1.23 $
042: */
043: public class QueryUtils {
044: /** Localiser for messages. */
045: protected static final Localiser LOCALISER = Localiser
046: .getInstance("org.jpox.store.Localisation");
047:
048: /**
049: * Utility to return if the passed result class is a user-type, and so requires fields matching up.
050: * @param className the class name looked for
051: * @return Whether it is a user class
052: */
053: public static boolean resultClassIsUserType(String className) {
054: return !resultClassIsSimple(className)
055: && !className.equals(java.util.Map.class.getName())
056: && !className.equals(ClassNameConstants.Object);
057: }
058:
059: /**
060: * Utility to return if the passed result class is a simple type with a single value.
061: * Checks the class name against the supported "simple" JDOQL result-class types.
062: * @param className the class name looked for
063: * @return Whether the result class is "simple".
064: */
065: public static boolean resultClassIsSimple(String className) {
066: if (className.equals(ClassNameConstants.JAVA_LANG_BOOLEAN)
067: || className.equals(ClassNameConstants.JAVA_LANG_BYTE)
068: || className
069: .equals(ClassNameConstants.JAVA_LANG_CHARACTER)
070: || className
071: .equals(ClassNameConstants.JAVA_LANG_DOUBLE)
072: || className.equals(ClassNameConstants.JAVA_LANG_FLOAT)
073: || className
074: .equals(ClassNameConstants.JAVA_LANG_INTEGER)
075: || className.equals(ClassNameConstants.JAVA_LANG_LONG)
076: || className.equals(ClassNameConstants.JAVA_LANG_SHORT)
077: || className
078: .equals(ClassNameConstants.JAVA_LANG_STRING)
079: || className.equals(BigDecimal.class.getName())
080: || className.equals(BigInteger.class.getName())
081: || className.equals(java.util.Date.class.getName())
082: || className.equals(java.sql.Date.class.getName())
083: || className.equals(java.sql.Time.class.getName())
084: || className.equals(java.sql.Timestamp.class.getName())
085: || className.equals(ClassNameConstants.Object)) {
086: return true;
087: } else {
088: return false;
089: }
090: }
091:
092: /**
093: * Convenience method to create an instance of the result class with the provided field
094: * values, using a constructor taking the arguments. If the returned object is null there
095: * is no constructor with the correct signature.
096: * @param resultClass The class of results that need creating
097: * @param fieldValues The field values
098: * @return The result class object
099: */
100: public static Object createResultObjectUsingArgumentedConstructor(
101: Class resultClass, Object[] fieldValues) {
102: Object obj = null;
103: Class[] ctrTypes = new Class[fieldValues.length];
104: for (int i = 0; i < ctrTypes.length; i++) {
105: ctrTypes[i] = (fieldValues[i] != null ? fieldValues[i]
106: .getClass() : null);
107: }
108:
109: Constructor ctr = ClassUtils.getConstructorWithArguments(
110: resultClass, ctrTypes);
111: if (ctr != null) {
112: try {
113: obj = ctr.newInstance(fieldValues);
114: } catch (Exception e) {
115: // do nothing
116: }
117: }
118:
119: return obj;
120: }
121:
122: /**
123: * Convenience method to create an instance of the result class with the provided field
124: * values, using the default constructor and setting the fields using either public fields,
125: * or setters, or a put method. If one of these parts is not found in the result class the
126: * returned object is null.
127: * @param resultClass Result class that we need to create an object of
128: * @param resultFieldNames Names of the fields in the results
129: * @param resultClassFieldNames Map of the result class fields, keyed by the field name
130: * @param fieldValues The field values
131: * @return The result class object
132: */
133: public static Object createResultObjectUsingDefaultConstructorAndSetters(
134: Class resultClass, String[] resultFieldNames,
135: Map resultClassFieldNames, Object[] fieldValues) {
136: Object obj = null;
137: try {
138: // Create the object
139: obj = resultClass.newInstance();
140: } catch (Exception e) {
141: String msg = LOCALISER.msg("021037", resultClass.getName());
142: JPOXLogger.QUERY.error(msg);
143: throw new JPOXUserException(msg);
144: }
145:
146: for (int i = 0; i < fieldValues.length; i++) {
147: // Update the fields of our object with the field values
148: if (!setFieldForResultObject(resultClassFieldNames, obj,
149: resultFieldNames[i], fieldValues[i])) {
150: String fieldType = "null";
151: if (fieldValues[i] != null) {
152: fieldType = fieldValues[i].getClass().getName();
153: }
154: String msg = LOCALISER.msg("021036", resultClass
155: .getName(), resultFieldNames[i], fieldType);
156: JPOXLogger.QUERY.error(msg);
157: throw new JPOXUserException(msg);
158: }
159: }
160:
161: return obj;
162: }
163:
164: /**
165: * Method to set a field of an object, using set/put methods, or via a public field.
166: * See JDO2 spec [14.6.12] describing the 3 ways of setting the field values
167: * after creating the result object using the default constructor.
168: * @param resultClassFieldName Map of the field keyed by field name
169: * @param obj The object to update the field for
170: * @param fieldName Name of the field
171: * @param value The value to apply
172: * @return Whether it was updated
173: */
174: private static boolean setFieldForResultObject(
175: Map resultClassFieldNames, Object obj, String fieldName,
176: Object value) {
177: boolean fieldSet = false;
178:
179: // Try setting the (public) field directly
180: if (!fieldSet) {
181: String declaredFieldName = fieldName;
182: Field field = (Field) resultClassFieldNames.get(fieldName
183: .toUpperCase());
184: if (field != null) {
185: declaredFieldName = field.getName();
186: }
187: Field f = ClassUtils.getFieldForClass(obj.getClass(),
188: declaredFieldName);
189: if (f != null && Modifier.isPublic(f.getModifiers())) {
190: try {
191: f.set(obj, value);
192: fieldSet = true;
193: } catch (Exception e) {
194: // Unable to set directly, so try converting the value to the type of the field
195: Object convertedValue = TypeConversionHelper
196: .convertTo(value, f.getType());
197: if (convertedValue != value) {
198: // Value has been converted so try setting the field now
199: try {
200: f.set(obj, convertedValue);
201: fieldSet = true;
202: } catch (Exception e2) {
203: // Do nothing since unable to convert it
204: }
205: }
206: }
207: }
208: if (!fieldSet && JPOXLogger.QUERY.isDebugEnabled()) {
209: JPOXLogger.QUERY.debug(LOCALISER.msg("021041", obj
210: .getClass().getName(), declaredFieldName));
211: }
212: }
213:
214: // Try (public) setMethod()
215: if (!fieldSet) {
216: String setMethodName = "set"
217: + fieldName.substring(0, 1).toUpperCase()
218: + fieldName.substring(1);
219: Field field = (Field) resultClassFieldNames.get(fieldName
220: .toUpperCase());
221: if (field != null) {
222: setMethodName = "set"
223: + fieldName.substring(0, 1).toUpperCase()
224: + field.getName().substring(1);
225: }
226:
227: Class argType = null;
228: if (value != null) {
229: argType = value.getClass();
230: } else if (field != null) {
231: argType = field.getType();
232: }
233: Method m = ClassUtils.getMethodWithArgument(obj.getClass(),
234: setMethodName, argType);
235: if (m != null && Modifier.isPublic(m.getModifiers())) {
236: // Where a set method with the exact argument type exists use it
237: try {
238: m.invoke(obj, new Object[] { value });
239: fieldSet = true;
240: } catch (Exception e) {
241: //do nothing
242: }
243: } else if (m == null) {
244: // Find a method with the right name and a single arg and try conversion of the supplied value
245: Method[] methods = obj.getClass().getDeclaredMethods();
246: for (int i = 0; i < methods.length; i++) {
247: Class[] args = methods[i].getParameterTypes();
248: if (methods[i].getName().equals(setMethodName)
249: && Modifier.isPublic(methods[i]
250: .getModifiers()) && args != null
251: && args.length == 1) {
252: try {
253: methods[i].invoke(obj,
254: new Object[] { ClassUtils
255: .convertValue(value,
256: args[0]) });
257: fieldSet = true;
258: break;
259: } catch (Exception e) {
260: //do nothing
261: }
262: }
263: }
264: }
265: if (!fieldSet && JPOXLogger.QUERY.isDebugEnabled()) {
266: JPOXLogger.QUERY.debug(LOCALISER.msg("021039", obj
267: .getClass().getName(), setMethodName, argType
268: .getName()));
269: }
270: }
271:
272: // Try (public) putMethod()
273: if (!fieldSet) {
274: Method m = getPublicPutMethodForResultClass(obj.getClass());
275: if (m != null) {
276: try {
277: m.invoke(obj, new Object[] { fieldName, value });
278: fieldSet = true;
279: } catch (Exception e) {
280: //do nothing
281: }
282: }
283: if (!fieldSet && JPOXLogger.QUERY.isDebugEnabled()) {
284: JPOXLogger.QUERY.debug(LOCALISER.msg("021040", obj
285: .getClass().getName(), "put"));
286: }
287: }
288:
289: return fieldSet;
290: }
291:
292: /**
293: * Convenience method to return the setXXX method for a field of the result class.
294: * @param resultClass The result class
295: * @param fieldName Name of the field
296: * @param fieldType The type of the field being set
297: * @return The setter method
298: */
299: public static Method getPublicSetMethodForFieldOfResultClass(
300: Class resultClass, String fieldName, Class fieldType) {
301: String setMethodName = "set"
302: + fieldName.substring(0, 1).toUpperCase()
303: + fieldName.substring(1);
304: Method m = ClassUtils.getMethodWithArgument(resultClass,
305: setMethodName, fieldType);
306:
307: if (m != null && Modifier.isPublic(m.getModifiers())) {
308: return m;
309: }
310: return null;
311: }
312:
313: /**
314: * Convenience method to return the put(Object, Object method for the result class.
315: * @param resultClass The result class
316: * @return The put(Object, Object) method
317: */
318: public static Method getPublicPutMethodForResultClass(
319: Class resultClass) {
320: String putMethodName = "put";
321: Method m = ClassUtils.getMethodForClass(resultClass,
322: putMethodName,
323: new Class[] { Object.class, Object.class });
324:
325: if (m != null && Modifier.isPublic(m.getModifiers())) {
326: return m;
327: }
328: return null;
329: }
330:
331: /**
332: * Convenience method to split an expression string into its constituent parts where separated by commas.
333: * This is used in the case of, for example, a result specification, to get the column definitions.
334: * @param str The expression string
335: * @return The expression parts
336: */
337: public static String[] getExpressionsFromString(String str) {
338: CharacterIterator ci = new StringCharacterIterator(str);
339: int braces = 0;
340: String text = "";
341: ArrayList exprList = new ArrayList();
342: while (ci.getIndex() != ci.getEndIndex()) {
343: char c = ci.current();
344: if (c == ',' && braces == 0) {
345: exprList.add(text);
346: text = "";
347: } else if (c == '(') {
348: braces++;
349: text += c;
350: } else if (c == ')') {
351: braces--;
352: text += c;
353: } else {
354: text += c;
355: }
356: ci.next();
357: }
358: exprList.add(text);
359: return (String[]) exprList.toArray(new String[exprList.size()]);
360: }
361: }
|