1 /*
2  * Hibernate Validator, declare and validate application constraints
3  *
4  * License: Apache License, Version 2.0
5  * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
6  */

7 package org.hibernate.validator.internal.metadata.raw;
8
9 import static org.hibernate.validator.internal.util.CollectionHelper.newArrayList;
10 import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet;
11
12 import java.lang.invoke.MethodHandles;
13 import java.util.Collections;
14 import java.util.HashSet;
15 import java.util.List;
16 import java.util.Set;
17
18 import javax.validation.metadata.ConstraintDescriptor;
19
20 import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder;
21 import org.hibernate.validator.internal.metadata.core.MetaConstraint;
22 import org.hibernate.validator.internal.properties.Callable;
23 import org.hibernate.validator.internal.util.CollectionHelper;
24 import org.hibernate.validator.internal.util.logging.Log;
25 import org.hibernate.validator.internal.util.logging.LoggerFactory;
26 import org.hibernate.validator.internal.util.stereotypes.Immutable;
27
28 /**
29  * Represents a method or constructor of a Java type and all its associated
30  * meta-data relevant in the context of bean validation, for instance the
31  * constraints at its parameters or return value.
32  *
33  * @author Gunnar Morling
34  * @author Guillaume Smet
35  */

36 public class ConstrainedExecutable extends AbstractConstrainedElement {
37
38     private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
39
40     private final Callable callable;
41
42     /**
43      * Constrained-related meta data for this executable's parameters.
44      */

45     @Immutable
46     private final List<ConstrainedParameter> parameterMetaData;
47
48     private final boolean hasParameterConstraints;
49
50     @Immutable
51     private final Set<MetaConstraint<?>> crossParameterConstraints;
52
53     /**
54      * Creates a new executable meta data object for a parameter-less executable.
55      *
56      * @param source The source of meta data.
57      * @param callable The represented executable.
58      * @param returnValueConstraints Type arguments constraints, if any.
59      * @param typeArgumentConstraints The type argument constraints on the return value of the represented executable,
60      * if any.
61      * @param cascadingMetaDataBuilder The cascaded validation metadata for this element and its container elements.
62      */

63     public ConstrainedExecutable(
64             ConfigurationSource source,
65             Callable callable,
66             Set<MetaConstraint<?>> returnValueConstraints,
67             Set<MetaConstraint<?>> typeArgumentConstraints,
68             CascadingMetaDataBuilder cascadingMetaDataBuilder) {
69         this(
70                 source,
71                 callable,
72                 Collections.<ConstrainedParameter>emptyList(),
73                 Collections.<MetaConstraint<?>>emptySet(),
74                 returnValueConstraints,
75                 typeArgumentConstraints,
76                 cascadingMetaDataBuilder
77         );
78     }
79
80     /**
81      * Creates a new executable meta data object.
82      *
83      * @param source The source of meta data.
84      * @param callable The represented executable.
85      * @param parameterMetaData A list with parameter meta data. The length must correspond with the number of
86      * parameters of the represented executable. So this list may be empty (in case of a parameterless executable), but
87      * never {@code null}.
88      * @param crossParameterConstraints the cross parameter constraints
89      * @param returnValueConstraints The return value constraints of the represented executable, if any.
90      * @param typeArgumentConstraints The type argument constraints on the return value of the represented executable,
91      * if any.
92      * @param cascadingMetaDataBuilder The cascaded validation metadata for this element and its container elements.
93      */

94     public ConstrainedExecutable(
95             ConfigurationSource source,
96             Callable callable,
97             List<ConstrainedParameter> parameterMetaData,
98             Set<MetaConstraint<?>> crossParameterConstraints,
99             Set<MetaConstraint<?>> returnValueConstraints,
100             Set<MetaConstraint<?>> typeArgumentConstraints,
101             CascadingMetaDataBuilder cascadingMetaDataBuilder) {
102         super(
103                 source,
104                 callable.getConstrainedElementKind(),
105                 returnValueConstraints,
106                 typeArgumentConstraints,
107                 cascadingMetaDataBuilder
108         );
109
110         this.callable = callable;
111
112         if ( parameterMetaData.size() != callable.getParameterCount() ) {
113             throw LOG.getInvalidLengthOfParameterMetaDataListException(
114                     callable,
115                     callable.getParameterCount(),
116                     parameterMetaData.size()
117             );
118         }
119
120         this.crossParameterConstraints = CollectionHelper.toImmutableSet( crossParameterConstraints );
121         this.parameterMetaData = CollectionHelper.toImmutableList( parameterMetaData );
122         this.hasParameterConstraints = hasParameterConstraints( parameterMetaData ) || !crossParameterConstraints.isEmpty();
123     }
124
125     /**
126      * Constraint meta data for the specified parameter.
127      *
128      * @param parameterIndex The index in this executable's parameter array of the parameter of
129      * interest.
130      *
131      * @return Meta data for the specified parameter. Will never be {@code null}.
132      *
133      * @throws IllegalArgumentException In case this executable doesn't have a parameter with the
134      * specified index.
135      */

136     public ConstrainedParameter getParameterMetaData(int parameterIndex) {
137         if ( parameterIndex < 0 || parameterIndex > parameterMetaData.size() - 1 ) {
138             throw LOG.getInvalidExecutableParameterIndexException(
139                     callable,
140                     parameterIndex
141             );
142         }
143
144         return parameterMetaData.get( parameterIndex );
145     }
146
147     /**
148      * Returns meta data for all parameters of the represented executable.
149      *
150      * @return A list with parameter meta data. The length corresponds to the
151      *         number of parameters of the executable represented by this meta data
152      *         object, so an empty list may be returned (in case of a
153      *         parameterless executable), but never {@code null}.
154      */

155     public List<ConstrainedParameter> getAllParameterMetaData() {
156         return parameterMetaData;
157     }
158
159     public Set<MetaConstraint<?>> getCrossParameterConstraints() {
160         return crossParameterConstraints;
161     }
162
163     /**
164      * Whether the represented executable is constrained or not. This is the case if
165      * it has at least one constrained parameter, at least one parameter marked
166      * for cascaded validation, at least one cross-parameter constraint, at
167      * least one return value constraint or if the return value is marked for
168      * cascaded validation.
169      *
170      * @return {@code True} if this executable is constrained by any means,
171      *         {@code false} otherwise.
172      */

173     @Override
174     public boolean isConstrained() {
175         return super.isConstrained() || hasParameterConstraints;
176     }
177
178     /**
179      * Whether this executable has at least one cascaded parameter or at least one
180      * parameter with constraints or at least one cross-parameter constraint.
181      *
182      * @return {@code True}, if this executable is parameter-constrained by any
183      *         means, {@code false} otherwise.
184      */

185     public boolean hasParameterConstraints() {
186         return hasParameterConstraints;
187     }
188
189     public Callable getCallable() {
190         return callable;
191     }
192
193     @Override
194     public String toString() {
195         return "ConstrainedExecutable [executable=" + callable
196                 + ", parameterMetaData=" + parameterMetaData
197                 + ", hasParameterConstraints=" + hasParameterConstraints + "]";
198     }
199
200     private boolean hasParameterConstraints(List<ConstrainedParameter> parameterMetaData) {
201         for ( ConstrainedParameter oneParameter : parameterMetaData ) {
202             if ( oneParameter.isConstrained() ) {
203                 return true;
204             }
205         }
206
207         return false;
208     }
209
210     /**
211      * Whether this and the given other executable have the same parameter
212      * constraints.
213      *
214      * @param other The other executable to check.
215      *
216      * @return True if this and the other executable have the same parameter
217      *         constraints (including cross- parameter constraints and parameter
218      *         cascades), false otherwise.
219      */

220     public boolean isEquallyParameterConstrained(ConstrainedExecutable other) {
221         if ( !getDescriptors( crossParameterConstraints ).equals( getDescriptors( other.crossParameterConstraints ) ) ) {
222             return false;
223         }
224
225         int i = 0;
226         for ( ConstrainedParameter parameter : parameterMetaData ) {
227             ConstrainedParameter otherParameter = other.getParameterMetaData( i );
228             // FIXME: how to deal with method overriding with type overloading of one of the parameters?
229             if ( !parameter.getCascadingMetaDataBuilder().equals( otherParameter.getCascadingMetaDataBuilder() )
230                 || !getDescriptors( parameter.getConstraints() ).equals( getDescriptors( otherParameter.getConstraints() ) ) ) {
231                 return false;
232             }
233             i++;
234         }
235
236         return true;
237     }
238
239     /**
240      * Creates a new constrained executable object by merging this and the given
241      * other executable. Both executables must have the same location, i.e.
242      * represent the same executable on the same type.
243      *
244      * @param other The executable to merge.
245      *
246      * @return A merged executable.
247      */

248     public ConstrainedExecutable merge(ConstrainedExecutable other) {
249         ConfigurationSource mergedSource = ConfigurationSource.max( source, other.source );
250
251         List<ConstrainedParameter> mergedParameterMetaData = newArrayList( parameterMetaData.size() );
252         int i = 0;
253         for ( ConstrainedParameter parameter : parameterMetaData ) {
254             mergedParameterMetaData.add( parameter.merge( other.getParameterMetaData( i ) ) );
255             i++;
256         }
257
258         Set<MetaConstraint<?>> mergedCrossParameterConstraints = newHashSet( crossParameterConstraints );
259         mergedCrossParameterConstraints.addAll( other.crossParameterConstraints );
260
261         Set<MetaConstraint<?>> mergedReturnValueConstraints = newHashSet( constraints );
262         mergedReturnValueConstraints.addAll( other.constraints );
263
264         Set<MetaConstraint<?>> mergedTypeArgumentConstraints = new HashSet<>( typeArgumentConstraints );
265         mergedTypeArgumentConstraints.addAll( other.typeArgumentConstraints );
266
267         CascadingMetaDataBuilder mergedCascadingMetaDataBuilder = cascadingMetaDataBuilder.merge( other.cascadingMetaDataBuilder );
268
269         return new ConstrainedExecutable(
270                 mergedSource,
271                 callable,
272                 mergedParameterMetaData,
273                 mergedCrossParameterConstraints,
274                 mergedReturnValueConstraints,
275                 mergedTypeArgumentConstraints,
276                 mergedCascadingMetaDataBuilder
277         );
278     }
279
280     private Set<ConstraintDescriptor<?>> getDescriptors(Iterable<MetaConstraint<?>> constraints) {
281         Set<ConstraintDescriptor<?>> descriptors = newHashSet();
282
283         for ( MetaConstraint<?> constraint : constraints ) {
284             descriptors.add( constraint.getDescriptor() );
285         }
286
287         return descriptors;
288     }
289
290     @Override
291     public int hashCode() {
292         final int prime = 31;
293         int result = super.hashCode();
294         result = prime * result + callable.hashCode();
295         return result;
296     }
297
298     @Override
299     public boolean equals(Object obj) {
300         if ( this == obj ) {
301             return true;
302         }
303         if ( !super.equals( obj ) ) {
304             return false;
305         }
306         if ( getClass() != obj.getClass() ) {
307             return false;
308         }
309         ConstrainedExecutable other = (ConstrainedExecutable) obj;
310          if ( !callable.equals( other.callable ) ) {
311             return false;
312         }
313         return true;
314     }
315 }
316