/** * Returns the target types that need to have conversion. The types contain first as many * constructor parameter types as we have and then the types of properties of object as given by * names of result-set. */ @NotNull private static Optional<List<Type>> findTargetTypes( @NotNull Constructor<?> ctor, @NotNull List<String> resultSetColumns) { List<Type> constructorParameterTypes = asList(ctor.getGenericParameterTypes()); int constructorParameterCount = constructorParameterTypes.size(); if (constructorParameterCount > resultSetColumns.size()) { // We don't have enough columns in ResultSet to instantiate this constructor, discard it. return Optional.empty(); } else if (constructorParameterCount == resultSetColumns.size()) { // We have exactly enough column in ResultSet. Use the constructor as it is. return Optional.of(constructorParameterTypes); } else { // Get the types of remaining properties ArrayList<Type> result = new ArrayList<>(resultSetColumns.size()); result.addAll(constructorParameterTypes); List<String> propertyNames = resultSetColumns.subList(constructorParameterCount, resultSetColumns.size()); for (String name : propertyNames) { Type type = PropertyAccessor.findPropertyType(ctor.getDeclaringClass(), name).orElse(null); if (type != null) result.add(type); else return Optional.empty(); } return Optional.of(result); } }
@NotNull private static Optional<TypeConversion> findEnumConversion(@NotNull Type target) { if (isEnum(target)) { @SuppressWarnings("rawtypes") Class<? extends Enum> cl = rawType(target).asSubclass(Enum.class); return Optional.ofNullable( TypeConversion.fromNonNullFunction(value -> Enum.valueOf(cl, value.toString()))); } return Optional.empty(); }
@NotNull private static Optional<Constructor<?>> dalesbredConstructor(@NotNull Class<?> cl) { List<Constructor<?>> candidates = Stream.of(cl.getDeclaredConstructors()) .filter(ctor -> ctor.isAnnotationPresent(DalesbredInstantiator.class)) .collect(toList()); if (candidates.size() == 1) return Optional.of(candidates.get(0)); else if (candidates.size() > 1) throw new InstantiationFailureException( "only one constructor of " + cl.getName() + " can be marked with @DalesbredConstructor. Found " + candidates.size()); else return Optional.empty(); }
/** * Returns the list of conversions that need to be performed to convert sourceTypes to * targetTypes, or empty if conversions can't be done. */ @NotNull private Optional<List<TypeConversion>> resolveConversions( @NotNull NamedTypeList sourceTypes, @NotNull List<Type> targetTypes) { if (targetTypes.size() != sourceTypes.size()) return Optional.empty(); ArrayList<TypeConversion> conversions = new ArrayList<>(targetTypes.size()); for (int i = 0, len = targetTypes.size(); i < len; i++) { TypeConversion conversion = findConversionFromDbValue(sourceTypes.getType(i), targetTypes.get(i)).orElse(null); if (conversion != null) conversions.add(conversion); else return Optional.empty(); } return Optional.of(conversions); }
@NotNull private Optional<Instantiator<?>> findExplicitInstantiatorFor( Class<?> cl, @NotNull NamedTypeList types) throws InstantiationFailureException { Constructor<?> constructor = dalesbredConstructor(cl).orElse(null); if (constructor == null) return Optional.empty(); try { ReflectionUtils.makeAccessible(constructor); } catch (SecurityException e) { throw new InstantiationFailureException( "Cannot instantiate " + constructor.getDeclaringClass().getName() + " using non-public constructor due to Security exception", e); } List<String> columnNames = types.getNames(); List<Type> constructorParameterTypes = asList(constructor.getGenericParameterTypes()); if (constructorParameterTypes.size() != columnNames.size()) throw new InstantiationFailureException( String.format( "Cannot instantiate %s, constructor takes %d arguments, but result set has %d", constructor.getDeclaringClass().getName(), constructorParameterTypes.size(), columnNames.size())); ReflectionInstantiator<?> instantiator = resolveConversions(types, constructorParameterTypes) .map( conversions -> new ReflectionInstantiator<>(constructor, conversions, Collections.emptyList())) .orElseThrow( () -> new InstantiationFailureException( "could not find a way to instantiate " + constructor.getDeclaringClass().getName() + " with parameters " + types)); return Optional.of(instantiator); }
/** * Returns conversion for converting value of source to target, or returns null if there's no such * conversion. */ @NotNull private Optional<TypeConversion> findConversionFromDbValue( @NotNull Type source, @NotNull Type target) { if (isAssignable(target, source)) return Optional.of(TypeConversion.identity()); Optional<TypeConversion> directConversion = typeConversionRegistry.findConversionFromDbValue(source, target); if (directConversion.isPresent()) return directConversion; Optional<TypeConversion> arrayConversion = findArrayConversion(source, target); if (arrayConversion.isPresent()) return arrayConversion; Optional<TypeConversion> optionalConversion = findOptionalConversion(source, target); if (optionalConversion.isPresent()) return optionalConversion; Optional<TypeConversion> enumConversion = findEnumConversion(target); if (enumConversion.isPresent()) return enumConversion; return Optional.empty(); }
@NotNull private Optional<TypeConversion> findArrayConversion(@NotNull Type source, @NotNull Type target) { Class<?> rawTarget = rawType(target); if (isAssignable(Array.class, source)) { if (rawTarget.equals(Set.class)) return Optional.of( SqlArrayConversion.sqlArray(typeParameter(target), this, LinkedHashSet::new)); if (rawTarget.isAssignableFrom(List.class)) return Optional.of( SqlArrayConversion.sqlArray(typeParameter(target), this, Function.identity())); if (rawTarget.isArray()) return Optional.of( SqlArrayConversion.sqlArray( rawTarget.getComponentType(), this, list -> arrayOfType(rawTarget.getComponentType(), list))); } return Optional.empty(); }
/** * Returns an instantiator that uses given constructor and given types to create instances, or * empty if there are no conversions that can be made to instantiate the type. */ @NotNull private <T> Optional<Instantiator<T>> implicitInstantiatorFrom( @NotNull Constructor<T> constructor, @NotNull NamedTypeList types) { if (!isPublic(constructor.getModifiers())) return Optional.empty(); List<String> columnNames = types.getNames(); return findTargetTypes(constructor, columnNames) .flatMap( targetTypes -> resolveConversions(types, targetTypes) .map( conversions -> new ReflectionInstantiator<>( constructor, conversions, createPropertyAccessorsForValuesNotCoveredByConstructor( constructor, columnNames)))); }
@NotNull private Optional<TypeConversion> findOptionalConversion( @NotNull Type source, @NotNull Type target) { Class<?> rawTarget = rawType(target); if (rawTarget == Optional.class) { return optionalConversion(source, typeParameter(target), Optional::ofNullable); } else if (rawTarget == OptionalInt.class) { return optionalConversion(source, Integer.class, OptionalUtils::optionalIntOfNullable); } else if (rawTarget == OptionalLong.class) { return optionalConversion(source, Long.class, OptionalUtils::optionalLongOfNullable); } else if (rawTarget == OptionalDouble.class) { return optionalConversion(source, Double.class, OptionalUtils::optionalDoubleOfNullable); } else { return Optional.empty(); } }