/*
* Copyright 2011-2017 by the original author(s).
*
* Licensed 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.springframework.data.mapping.model;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Optional;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
/**
* Helper class to find a {@link PreferredConstructor}.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class PreferredConstructorDiscoverer<T, P extends PersistentProperty<P>> {
private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
private Optional<PreferredConstructor<T, P>> constructor;
/**
* Creates a new {@link PreferredConstructorDiscoverer} for the given type.
*
* @param type must not be {@literal null}.
*/
public PreferredConstructorDiscoverer(Class<T> type) {
this(ClassTypeInformation.from(type), null);
}
/**
* Creates a new {@link PreferredConstructorDiscoverer} for the given {@link PersistentEntity}.
*
* @param entity must not be {@literal null}.
*/
public PreferredConstructorDiscoverer(PersistentEntity<T, P> entity) {
this(entity.getTypeInformation(), Optional.ofNullable(entity));
}
/**
* Creates a new {@link PreferredConstructorDiscoverer} for the given type.
*
* @param type must not be {@literal null}.
* @param entity
*/
protected PreferredConstructorDiscoverer(TypeInformation<T> type, Optional<PersistentEntity<T, P>> entity) {
boolean noArgConstructorFound = false;
int numberOfArgConstructors = 0;
Class<?> rawOwningType = type.getType();
for (Constructor<?> candidate : rawOwningType.getDeclaredConstructors()) {
PreferredConstructor<T, P> preferredConstructor = buildPreferredConstructor(candidate, type, entity);
// Explicitly defined constructor trumps all
if (preferredConstructor.isExplicitlyAnnotated()) {
this.constructor = Optional.of(preferredConstructor);
return;
}
// No-arg constructor trumps custom ones
if (this.constructor == null || preferredConstructor.isNoArgConstructor()) {
this.constructor = Optional.of(preferredConstructor);
}
if (preferredConstructor.isNoArgConstructor()) {
noArgConstructorFound = true;
} else {
numberOfArgConstructors++;
}
}
if (!noArgConstructorFound && numberOfArgConstructors > 1) {
this.constructor = Optional.empty();
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private PreferredConstructor<T, P> buildPreferredConstructor(Constructor<?> constructor,
TypeInformation<T> typeInformation, Optional<PersistentEntity<T, P>> entity) {
List<TypeInformation<?>> parameterTypes = typeInformation.getParameterTypes(constructor);
if (parameterTypes.isEmpty()) {
return new PreferredConstructor<>((Constructor<T>) constructor);
}
String[] parameterNames = discoverer.getParameterNames(constructor);
Parameter<Object, P>[] parameters = new Parameter[parameterTypes.size()];
Annotation[][] parameterAnnotations = constructor.getParameterAnnotations();
for (int i = 0; i < parameterTypes.size(); i++) {
Optional<String> name = Optional.ofNullable(parameterNames == null ? null : parameterNames[i]);
TypeInformation<?> type = parameterTypes.get(i);
Annotation[] annotations = parameterAnnotations[i];
parameters[i] = new Parameter(name, type, annotations, entity);
}
return new PreferredConstructor<>((Constructor<T>) constructor, parameters);
}
/**
* Returns the discovered {@link PreferredConstructor}.
*
* @return
*/
public Optional<PreferredConstructor<T, P>> getConstructor() {
return constructor;
}
}