/*
 * Copyright 2011-2020 the original author or authors.
 *
 * 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
 *
 *      https://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;

import lombok.EqualsAndHashCode;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

Value object to encapsulate the constructor to be used when mapping persistent data to objects.
Author:Oliver Gierke, Jon Brisbin, Thomas Darimont, Christoph Strobl, Mark Paluch
/** * Value object to encapsulate the constructor to be used when mapping persistent data to objects. * * @author Oliver Gierke * @author Jon Brisbin * @author Thomas Darimont * @author Christoph Strobl * @author Mark Paluch */
public class PreferredConstructor<T, P extends PersistentProperty<P>> { private final Constructor<T> constructor; private final List<Parameter<Object, P>> parameters; private final Map<PersistentProperty<?>, Boolean> isPropertyParameterCache = new HashMap<>(); private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock read = lock.readLock(); private final Lock write = lock.writeLock();
Creates a new PreferredConstructor from the given Constructor and Parameters.
Params:
  • constructor – must not be null.
  • parameters – must not be null.
/** * Creates a new {@link PreferredConstructor} from the given {@link Constructor} and {@link Parameter}s. * * @param constructor must not be {@literal null}. * @param parameters must not be {@literal null}. */
@SafeVarargs public PreferredConstructor(Constructor<T> constructor, Parameter<Object, P>... parameters) { Assert.notNull(constructor, "Constructor must not be null!"); Assert.notNull(parameters, "Parameters must not be null!"); ReflectionUtils.makeAccessible(constructor); this.constructor = constructor; this.parameters = Arrays.asList(parameters); }
Returns the underlying Constructor.
Returns:
/** * Returns the underlying {@link Constructor}. * * @return */
public Constructor<T> getConstructor() { return constructor; }
Returns the Parameters of the constructor.
Returns:
/** * Returns the {@link Parameter}s of the constructor. * * @return */
public List<Parameter<Object, P>> getParameters() { return parameters; }
Returns whether the constructor has Parameters.
See Also:
Returns:
/** * Returns whether the constructor has {@link Parameter}s. * * @see #isNoArgConstructor() * @return */
public boolean hasParameters() { return !parameters.isEmpty(); }
Returns whether the constructor does not have any arguments.
See Also:
  • hasParameters()
Returns:
/** * Returns whether the constructor does not have any arguments. * * @see #hasParameters() * @return */
public boolean isNoArgConstructor() { return parameters.isEmpty(); }
Returns whether the constructor was explicitly selected (by PersistenceConstructor).
Returns:
/** * Returns whether the constructor was explicitly selected (by {@link PersistenceConstructor}). * * @return */
public boolean isExplicitlyAnnotated() { return constructor.isAnnotationPresent(PersistenceConstructor.class); }
Returns whether the given PersistentProperty is referenced in a constructor argument of the PersistentEntity backing this PreferredConstructor.
Params:
  • property – must not be null.
Returns:
/** * Returns whether the given {@link PersistentProperty} is referenced in a constructor argument of the * {@link PersistentEntity} backing this {@link PreferredConstructor}. * * @param property must not be {@literal null}. * @return */
public boolean isConstructorParameter(PersistentProperty<?> property) { Assert.notNull(property, "Property must not be null!"); try { read.lock(); Boolean cached = isPropertyParameterCache.get(property); if (cached != null) { return cached; } } finally { read.unlock(); } try { write.lock(); for (Parameter<?, P> parameter : parameters) { if (parameter.maps(property)) { isPropertyParameterCache.put(property, true); return true; } } isPropertyParameterCache.put(property, false); return false; } finally { write.unlock(); } }
Returns whether the given Parameter is one referring to an enclosing class. That is in case the class this PreferredConstructor belongs to is a member class actually. If that's the case the compiler creates a first constructor argument of the enclosing class type.
Params:
  • parameter – must not be null.
Returns:
/** * Returns whether the given {@link Parameter} is one referring to an enclosing class. That is in case the class this * {@link PreferredConstructor} belongs to is a member class actually. If that's the case the compiler creates a first * constructor argument of the enclosing class type. * * @param parameter must not be {@literal null}. * @return */
public boolean isEnclosingClassParameter(Parameter<?, P> parameter) { Assert.notNull(parameter, "Parameter must not be null!"); if (parameters.isEmpty() || !parameter.isEnclosingClassParameter()) { return false; } return parameters.get(0).equals(parameter); }
Value object to represent constructor parameters.
Author:Oliver Gierke
Type parameters:
  • <T> – the type of the parameter
/** * Value object to represent constructor parameters. * * @param <T> the type of the parameter * @author Oliver Gierke */
@EqualsAndHashCode(exclude = { "enclosingClassCache", "hasSpelExpression" }) public static class Parameter<T, P extends PersistentProperty<P>> { private final @Nullable String name; private final TypeInformation<T> type; private final String key; private final @Nullable PersistentEntity<T, P> entity; private final Lazy<Boolean> enclosingClassCache; private final Lazy<Boolean> hasSpelExpression;
Creates a new Parameter with the given name, TypeInformation as well as an array of Annotations. Will inspect the annotations for an Value annotation to lookup a key or an SpEL expression to be evaluated.
Params:
  • name – the name of the parameter, can be null
  • type – must not be null
  • annotations – must not be null but can be empty
  • entity – must not be null.
/** * Creates a new {@link Parameter} with the given name, {@link TypeInformation} as well as an array of * {@link Annotation}s. Will inspect the annotations for an {@link Value} annotation to lookup a key or an SpEL * expression to be evaluated. * * @param name the name of the parameter, can be {@literal null} * @param type must not be {@literal null} * @param annotations must not be {@literal null} but can be empty * @param entity must not be {@literal null}. */
public Parameter(@Nullable String name, TypeInformation<T> type, Annotation[] annotations, @Nullable PersistentEntity<T, P> entity) { Assert.notNull(type, "Type must not be null!"); Assert.notNull(annotations, "Annotations must not be null!"); this.name = name; this.type = type; this.key = getValue(annotations); this.entity = entity; this.enclosingClassCache = Lazy.of(() -> { if (entity == null) { throw new IllegalStateException(); } Class<T> owningType = entity.getType(); return owningType.isMemberClass() && type.getType().equals(owningType.getEnclosingClass()); }); this.hasSpelExpression = Lazy.of(() -> StringUtils.hasText(getSpelExpression())); } private static String getValue(Annotation[] annotations) { return Arrays.stream(annotations)// .filter(it -> it.annotationType() == Value.class)// .findFirst().map(it -> ((Value) it).value())// .filter(StringUtils::hasText).orElse(null); }
Returns the name of the parameter.
Returns:
/** * Returns the name of the parameter. * * @return */
@Nullable public String getName() { return name; }
Returns the TypeInformation of the parameter.
Returns:
/** * Returns the {@link TypeInformation} of the parameter. * * @return */
public TypeInformation<T> getType() { return type; }
Returns the raw resolved type of the parameter.
Returns:
/** * Returns the raw resolved type of the parameter. * * @return */
public Class<T> getRawType() { return type.getType(); }
Returns the key to be used when looking up a source data structure to populate the actual parameter value.
Returns:
/** * Returns the key to be used when looking up a source data structure to populate the actual parameter value. * * @return */
public String getSpelExpression() { return key; }
Returns whether the constructor parameter is equipped with a SpEL expression.
Returns:
/** * Returns whether the constructor parameter is equipped with a SpEL expression. * * @return */
public boolean hasSpelExpression() { return this.hasSpelExpression.get(); }
Returns whether the Parameter maps the given PersistentProperty.
Params:
  • property –
Returns:
/** * Returns whether the {@link Parameter} maps the given {@link PersistentProperty}. * * @param property * @return */
boolean maps(PersistentProperty<?> property) { PersistentEntity<T, P> entity = this.entity; String name = this.name; P referencedProperty = entity == null ? null : name == null ? null : entity.getPersistentProperty(name); return property != null && property.equals(referencedProperty); } private boolean isEnclosingClassParameter() { return enclosingClassCache.get(); } } }