| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.openjpa.kernel; |
| |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Member; |
| import java.lang.reflect.Method; |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.security.AccessController; |
| import java.security.PrivilegedActionException; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.GregorianCalendar; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.openjpa.lib.util.J2DoPrivHelper; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.lib.util.StringUtil; |
| import org.apache.openjpa.util.OpenJPAException; |
| import org.apache.openjpa.util.UserException; |
| |
| /** |
| * Helper class to pack results into the result class set on the query. |
| * |
| * @author Abe White |
| * @author Patrick Linskey |
| */ |
| public class ResultPacker { |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (ResultPacker.class); |
| private static final Set<Class<?>> _stdTypes = new HashSet<>(); |
| |
| static { |
| _stdTypes.add(Object[].class); |
| _stdTypes.add(Object.class); |
| _stdTypes.add(Map.class); |
| _stdTypes.add(HashMap.class); |
| _stdTypes.add(Character.class); |
| _stdTypes.add(Boolean.class); |
| _stdTypes.add(Byte.class); |
| _stdTypes.add(Short.class); |
| _stdTypes.add(Integer.class); |
| _stdTypes.add(Long.class); |
| _stdTypes.add(Float.class); |
| _stdTypes.add(Double.class); |
| _stdTypes.add(String.class); |
| _stdTypes.add(BigInteger.class); |
| _stdTypes.add(BigDecimal.class); |
| _stdTypes.add(Date.class); |
| _stdTypes.add(java.sql.Date.class); |
| _stdTypes.add(java.sql.Time.class); |
| _stdTypes.add(java.sql.Timestamp.class); |
| _stdTypes.add(Calendar.class); |
| _stdTypes.add(GregorianCalendar.class); |
| } |
| |
| private final Class<?> _resultClass; |
| private final String[] _aliases; |
| private final Member[] _sets; |
| private final Method _put; |
| private final Constructor<?> _constructor; |
| |
| /** |
| * Protected constructor to bypass this implementation but allow extension. |
| */ |
| protected ResultPacker() { |
| _resultClass = null; |
| _aliases = null; |
| _sets = null; |
| _put = null; |
| _constructor = null; |
| } |
| |
| /** |
| * Constructor for result class without a projection. |
| */ |
| public ResultPacker(Class<?> candidate, String alias, Class<?> resultClass) { |
| this(candidate, null, new String[]{ alias }, resultClass); |
| } |
| |
| /** |
| * Constructor for standard projection. |
| * |
| * @param types the projection value types |
| * @param aliases the alias for each projection value |
| * @param resultClass the class to pack into |
| */ |
| public ResultPacker(Class<?>[] types, String[] aliases, Class<?> resultClass) { |
| this(null, types, aliases, resultClass); |
| } |
| |
| /** |
| * Internal constructor. |
| */ |
| private ResultPacker(Class<?> candidate, Class<?>[] types, String[] aliases, Class<?> resultClass) { |
| _aliases = aliases; |
| if (candidate == resultClass || isInterface(resultClass, candidate) |
| ||(types != null && types.length == 1 && types[0] == resultClass) |
| || resultClass.isArray()) { |
| _resultClass = resultClass; |
| _sets = null; |
| _put = null; |
| _constructor = null; |
| } else if (resultClass.isPrimitive()) { |
| assertConvertable(candidate, types, resultClass); |
| _resultClass = Filters.wrap(resultClass); |
| _sets = null; |
| _put = null; |
| _constructor = null; |
| } else if (!_stdTypes.contains(_resultClass = resultClass)) { |
| // check for a constructor that matches the projection types |
| Constructor<?> cons = null; |
| if (types != null && types.length > 0) { |
| try { |
| cons = _resultClass.getConstructor(types); |
| } catch (NoSuchMethodException nsme) { |
| } |
| } |
| _constructor = cons; |
| |
| if (cons == null) { |
| Method[] methods = _resultClass.getMethods(); |
| Field[] fields = _resultClass.getFields(); |
| _put = findPut(methods); |
| _sets = new Member[aliases.length]; |
| |
| Class<?> type; |
| for (int i = 0; i < _sets.length; i++) { |
| type = (types == null) ? candidate : types[i]; |
| _sets[i] = findSet(aliases[i], type, fields, methods); |
| if (_sets[i] == null && _put == null) |
| throw new UserException(_loc.get("cant-set", |
| resultClass, aliases[i], |
| types == null ? null : Arrays.asList(types))); |
| } |
| } else { |
| _sets = null; |
| _put = null; |
| } |
| } else { |
| if (resultClass != Map.class && resultClass != HashMap.class |
| && resultClass != Object[].class) |
| assertConvertable(candidate, types, resultClass); |
| _sets = null; |
| _put = null; |
| _constructor = null; |
| } |
| } |
| |
| boolean isInterface(Class<?> intf, Class<?> actual) { |
| if (actual != null) { |
| Class<?>[] intfs = actual.getInterfaces(); |
| for (Class<?> c : intfs) { |
| if (c == intf) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Ensure that conversion is possible. |
| */ |
| private void assertConvertable(Class<?> candidate, Class<?>[] types, |
| Class<?> resultClass) { |
| Class<?> c = (types == null) ? candidate : types[0]; |
| if ((types != null && types.length != 1) || (c != null |
| && c != Object.class && !Filters.canConvert(c, resultClass, true))) |
| throw new UserException(_loc.get("cant-convert-result", |
| c, resultClass)); |
| } |
| |
| /** |
| * Pack the given object into an instance of the query's result class. |
| */ |
| public Object pack(Object result) { |
| if (result == null || _resultClass == result.getClass()) |
| return result; |
| // special cases for efficient basic types where we want to avoid |
| // creating an array for call to general pack method below |
| if (_resultClass == Object.class) |
| return result; |
| if (_resultClass == Object[].class) |
| return new Object[]{ result }; |
| if (_resultClass == HashMap.class || _resultClass == Map.class) { |
| HashMap<String,Object> map = new HashMap<>(1, 1F); |
| map.put(_aliases[0], result); |
| return map; |
| } |
| |
| // primitive or simple type? |
| if (_constructor == null && _sets == null) |
| return Filters.convert(result, _resultClass); |
| |
| // this is some complex case, so worth it to create the array and |
| // use the general pack method |
| return packUserType(new Object[]{ result }); |
| } |
| |
| /** |
| * Pack the given array into an instance of the query's result class. |
| */ |
| public Object pack(Object[] result) { |
| if (result == null || result.length == 0) |
| return null; |
| |
| // special cases for object arrays and maps |
| if (_resultClass == Object[].class) { |
| // the result might contain extra data at the end |
| if (result.length > _aliases.length) { |
| Object[] trim = new Object[_aliases.length]; |
| System.arraycopy(result, 0, trim, 0, trim.length); |
| return trim; |
| } |
| return result; |
| } |
| if (_resultClass.isArray()) { |
| Class<?> elementType = _resultClass.getComponentType(); |
| Object castResult = Array.newInstance(elementType, result.length); |
| for (int i = 0; i < result.length; i++) |
| Array.set(castResult, i, elementType.cast(result[i])); |
| return castResult; |
| } |
| if (_resultClass == Object.class) |
| return result[0]; |
| if (_resultClass == HashMap.class || _resultClass == Map.class) { |
| Map<String,Object> map = new HashMap<>(result.length); |
| for (int i = 0; i < _aliases.length; i++) |
| map.put(_aliases[i], result[i]); |
| return map; |
| } |
| |
| // primitive or simple type? |
| if (_sets == null && _constructor == null) |
| return Filters.convert(result[0], _resultClass); |
| |
| // must be a user-defined type |
| return packUserType(result); |
| } |
| |
| /** |
| * Pack the given result into the user-defined result class. |
| */ |
| private Object packUserType(Object[] result) { |
| try { |
| // use the constructor first, if we have one |
| if (_constructor != null) |
| return _constructor.newInstance(result); |
| |
| Object user = AccessController.doPrivileged( |
| J2DoPrivHelper.newInstanceAction(_resultClass)); |
| for (int i = 0; i < _aliases.length; i++) { |
| if (_sets[i] instanceof Method) { |
| Method meth = (Method) _sets[i]; |
| meth.invoke(user, new Object[]{ Filters.convert |
| (result[i], meth.getParameterTypes()[0]) }); |
| } else if (_sets[i] instanceof Field) { |
| Field field = (Field) _sets[i]; |
| field.set(user, Filters.convert(result[i], |
| field.getType())); |
| } else if (_put != null) { |
| _put.invoke(user, new Object[]{ _aliases[i], result[i] }); |
| } |
| } |
| return user; |
| } catch (OpenJPAException ke) { |
| throw ke; |
| } catch (PrivilegedActionException pae) { |
| throw new UserException(_loc.get("pack-instantiation-err", |
| _resultClass), pae.getException()); |
| } catch (InstantiationException ie) { |
| throw new UserException(_loc.get("pack-instantiation-err", |
| _resultClass), ie); |
| } catch (Exception e) { |
| throw new UserException(_loc.get("pack-err", _resultClass), e); |
| } |
| } |
| |
| /** |
| * Return the set method for the given property. |
| */ |
| private static Member findSet(String alias, Class<?> type, Field[] fields, |
| Method[] methods) { |
| if (StringUtil.isEmpty(alias)) |
| return null; |
| if (type == Object.class) |
| type = null; |
| |
| // check public fields first |
| Field field = null; |
| for (Field item : fields) { |
| // if we find a field with the exact name, either return it |
| // if it's the right type or give up if it's not |
| if (item.getName().equals(alias)) { |
| if (type == null |
| || Filters.canConvert(type, item.getType(), true)) |
| return item; |
| break; |
| } |
| |
| // otherwise if we find a field with the right name but the |
| // wrong case, record it and if we don't find an exact match |
| // for a field or setter we'll use it |
| if (field == null && item.getName().equalsIgnoreCase(alias) |
| && (type == null |
| || Filters.canConvert(type, item.getType(), true))) |
| field = item; |
| } |
| |
| // check setter methods |
| String setName = "set" + StringUtil.capitalize(alias); |
| Method method = null; |
| boolean eqName = false; |
| Class<?>[] params; |
| for (Method value : methods) { |
| if (!value.getName().equalsIgnoreCase(setName)) |
| continue; |
| params = value.getParameterTypes(); |
| if (params.length != 1) |
| continue; |
| |
| if (type != null && params[0] == Object.class) { |
| // we found a generic object setter; now see if the name |
| // is an exact match, and if so record this setter. if we |
| // don't find an exact type match later, we'll use it. if |
| // the names are not an exact match, only record this setter |
| // if we haven't found any others that match at all |
| if (value.getName().equals(setName)) { |
| eqName = true; |
| method = value; |
| } |
| else if (method == null) |
| method = value; |
| } |
| else if (type == null || Filters.canConvert(type, params[0], true)) { |
| // we found a setter with the right type; now see if the name |
| // is an exact match. if so, return the setter. if not, |
| // record the setter only if we haven't found a generic one |
| // with an exact name match |
| if (value.getName().equals(setName)) |
| return value; |
| if (method == null || !eqName) |
| method = value; |
| } |
| } |
| |
| // if we have an exact method name match, return it; otherwise favor |
| // an inexact field to an inexact method |
| if (eqName || field == null) |
| return method; |
| return field; |
| } |
| |
| /** |
| * Return the put method if one exists. |
| */ |
| private static Method findPut(Method[] methods) { |
| Class<?>[] params; |
| for (Method method : methods) { |
| if (!method.getName().equals("put")) |
| continue; |
| |
| params = method.getParameterTypes(); |
| if (params.length == 2 |
| && params[0] == Object.class |
| && params[1] == Object.class) |
| return method; |
| } |
| return null; |
| } |
| } |