001    /*******************************************************************************
002     * Portions created by Sebastian Thomschke are copyright (c) 2005-2011 Sebastian
003     * Thomschke.
004     * 
005     * All Rights Reserved. This program and the accompanying materials
006     * are made available under the terms of the Eclipse Public License v1.0
007     * which accompanies this distribution, and is available at
008     * http://www.eclipse.org/legal/epl-v10.html
009     * 
010     * Contributors:
011     *     Sebastian Thomschke - initial implementation.
012     *     Chris Pheby - interface based method parameter validation (inspectInterfaces)
013     *******************************************************************************/
014    package net.sf.oval.configuration.annotation;
015    
016    import static net.sf.oval.Validator.getCollectionFactory;
017    
018    import java.lang.annotation.Annotation;
019    import java.lang.reflect.Constructor;
020    import java.lang.reflect.Field;
021    import java.lang.reflect.Method;
022    import java.util.List;
023    import java.util.Set;
024    
025    import net.sf.oval.Check;
026    import net.sf.oval.CheckExclusion;
027    import net.sf.oval.collection.CollectionFactory;
028    import net.sf.oval.configuration.CheckInitializationListener;
029    import net.sf.oval.configuration.Configurer;
030    import net.sf.oval.configuration.pojo.elements.ClassConfiguration;
031    import net.sf.oval.configuration.pojo.elements.ConstraintSetConfiguration;
032    import net.sf.oval.configuration.pojo.elements.ConstructorConfiguration;
033    import net.sf.oval.configuration.pojo.elements.FieldConfiguration;
034    import net.sf.oval.configuration.pojo.elements.MethodConfiguration;
035    import net.sf.oval.configuration.pojo.elements.MethodPostExecutionConfiguration;
036    import net.sf.oval.configuration.pojo.elements.MethodPreExecutionConfiguration;
037    import net.sf.oval.configuration.pojo.elements.MethodReturnValueConfiguration;
038    import net.sf.oval.configuration.pojo.elements.ObjectConfiguration;
039    import net.sf.oval.configuration.pojo.elements.ParameterConfiguration;
040    import net.sf.oval.exception.OValException;
041    import net.sf.oval.exception.ReflectionException;
042    import net.sf.oval.guard.Guarded;
043    import net.sf.oval.guard.Post;
044    import net.sf.oval.guard.PostCheck;
045    import net.sf.oval.guard.PostValidateThis;
046    import net.sf.oval.guard.Pre;
047    import net.sf.oval.guard.PreCheck;
048    import net.sf.oval.guard.PreValidateThis;
049    import net.sf.oval.internal.util.Assert;
050    import net.sf.oval.internal.util.LinkedSet;
051    import net.sf.oval.internal.util.ReflectionUtils;
052    
053    /**
054     * Configurer that configures constraints based on annotations tagged with {@link Constraint}
055     * 
056     * @author Sebastian Thomschke
057     */
058    public class AnnotationsConfigurer implements Configurer
059    {
060            protected final Set<CheckInitializationListener> listeners = new LinkedSet<CheckInitializationListener>(2);
061    
062            private List<ParameterConfiguration> _createParameterConfiguration(final Annotation[][] paramAnnotations,
063                            final Class< ? >[] parameterTypes)
064            {
065                    final CollectionFactory cf = getCollectionFactory();
066    
067                    final List<ParameterConfiguration> paramCfg = cf.createList(paramAnnotations.length);
068    
069                    List<Check> paramChecks = cf.createList(2);
070                    List<CheckExclusion> paramCheckExclusions = cf.createList(2);
071    
072                    // loop over all parameters of the current constructor
073                    for (int i = 0; i < paramAnnotations.length; i++)
074                    {
075                            // loop over all annotations of the current constructor parameter
076                            for (final Annotation annotation : paramAnnotations[i])
077                                    // check if the current annotation is a constraint annotation
078                                    if (annotation.annotationType().isAnnotationPresent(Constraint.class))
079                                            paramChecks.add(initializeCheck(annotation));
080                                    else if (annotation.annotationType().isAnnotationPresent(Constraints.class))
081                                            initializeChecks(annotation, paramChecks);
082                                    else if (annotation.annotationType().isAnnotationPresent(Exclusion.class))
083                                            paramCheckExclusions.add(initializeExclusion(annotation));
084    
085                            final ParameterConfiguration pc = new ParameterConfiguration();
086                            paramCfg.add(pc);
087                            pc.type = parameterTypes[i];
088                            if (paramChecks.size() > 0)
089                            {
090                                    pc.checks = paramChecks;
091                                    paramChecks = cf.createList(2); // create a new list for the next parameter having checks
092                            }
093                            if (paramCheckExclusions.size() > 0)
094                            {
095                                    pc.checkExclusions = paramCheckExclusions;
096                                    paramCheckExclusions = cf.createList(2); // create a new list for the next parameter having check exclusions
097                            }
098                    }
099                    return paramCfg;
100            }
101    
102            public boolean addCheckInitializationListener(final CheckInitializationListener listener)
103            {
104                    Assert.argumentNotNull("listener", "[listener] must not be null");
105                    return listeners.add(listener);
106            }
107    
108            protected void configureConstructorParameterChecks(final ClassConfiguration classCfg)
109            {
110                    final CollectionFactory cf = getCollectionFactory();
111    
112                    for (final Constructor< ? > ctor : classCfg.type.getDeclaredConstructors())
113                    {
114                            final List<ParameterConfiguration> paramCfg = _createParameterConfiguration(ctor.getParameterAnnotations(),
115                                            ctor.getParameterTypes());
116    
117                            final boolean postValidateThis = ctor.isAnnotationPresent(PostValidateThis.class);
118    
119                            if (paramCfg.size() > 0 || postValidateThis)
120                            {
121                                    if (classCfg.constructorConfigurations == null) classCfg.constructorConfigurations = cf.createSet(2);
122    
123                                    final ConstructorConfiguration cc = new ConstructorConfiguration();
124                                    cc.parameterConfigurations = paramCfg;
125                                    cc.postCheckInvariants = postValidateThis;
126                                    classCfg.constructorConfigurations.add(cc);
127                            }
128                    }
129            }
130    
131            protected void configureFieldChecks(final ClassConfiguration classCfg)
132            {
133                    final CollectionFactory cf = getCollectionFactory();
134    
135                    List<Check> checks = cf.createList(2);
136    
137                    for (final Field field : classCfg.type.getDeclaredFields())
138                    {
139                            // loop over all annotations of the current field
140                            for (final Annotation annotation : field.getAnnotations())
141                                    // check if the current annotation is a constraint annotation
142                                    if (annotation.annotationType().isAnnotationPresent(Constraint.class))
143                                            checks.add(initializeCheck(annotation));
144                                    else if (annotation.annotationType().isAnnotationPresent(Constraints.class))
145                                            initializeChecks(annotation, checks);
146    
147                            if (checks.size() > 0)
148                            {
149                                    if (classCfg.fieldConfigurations == null) classCfg.fieldConfigurations = cf.createSet(2);
150    
151                                    final FieldConfiguration fc = new FieldConfiguration();
152                                    fc.name = field.getName();
153                                    fc.checks = checks;
154                                    classCfg.fieldConfigurations.add(fc);
155                                    checks = cf.createList(2); // create a new list for the next field with checks
156                            }
157                    }
158            }
159    
160            /**
161             * configure method return value and parameter checks
162             */
163            protected void configureMethodChecks(final ClassConfiguration classCfg)
164            {
165                    final CollectionFactory cf = getCollectionFactory();
166    
167                    List<Check> returnValueChecks = cf.createList(2);
168                    List<PreCheck> preChecks = cf.createList(2);
169                    List<PostCheck> postChecks = cf.createList(2);
170    
171                    for (final Method method : classCfg.type.getDeclaredMethods())
172                    {
173                            /*
174                             * determine method return value checks and method pre/post
175                             * conditions
176                             */
177                            boolean preValidateThis = false;
178                            boolean postValidateThis = false;
179    
180                            // loop over all annotations
181                            for (final Annotation annotation : ReflectionUtils.getAnnotations(method,
182                                            Boolean.TRUE.equals(classCfg.inspectInterfaces)))
183                                    if (annotation instanceof Pre)
184                                    {
185                                            final PreCheck pc = new PreCheck();
186                                            pc.configure((Pre) annotation);
187                                            preChecks.add(pc);
188                                    }
189                                    else if (annotation instanceof PreValidateThis)
190                                            preValidateThis = true;
191                                    else if (annotation instanceof Post)
192                                    {
193                                            final PostCheck pc = new PostCheck();
194                                            pc.configure((Post) annotation);
195                                            postChecks.add(pc);
196                                    }
197                                    else if (annotation instanceof PostValidateThis)
198                                            postValidateThis = true;
199                                    else if (annotation.annotationType().isAnnotationPresent(Constraint.class))
200                                            returnValueChecks.add(initializeCheck(annotation));
201                                    else if (annotation.annotationType().isAnnotationPresent(Constraints.class))
202                                            initializeChecks(annotation, returnValueChecks);
203    
204                            /*
205                             * determine parameter checks
206                             */
207                            final List<ParameterConfiguration> paramCfg = _createParameterConfiguration(
208                                            ReflectionUtils.getParameterAnnotations(method, Boolean.TRUE.equals(classCfg.inspectInterfaces)),
209                                            method.getParameterTypes());
210    
211                            // check if anything has been configured for this method at all
212                            if (paramCfg.size() > 0 || returnValueChecks.size() > 0 || preChecks.size() > 0 || postChecks.size() > 0
213                                            || preValidateThis || postValidateThis)
214                            {
215                                    if (classCfg.methodConfigurations == null) classCfg.methodConfigurations = cf.createSet(2);
216    
217                                    final MethodConfiguration mc = new MethodConfiguration();
218                                    mc.name = method.getName();
219                                    mc.parameterConfigurations = paramCfg;
220                                    mc.isInvariant = ReflectionUtils.isAnnotationPresent(method, IsInvariant.class,
221                                                    Boolean.TRUE.equals(classCfg.inspectInterfaces));
222                                    mc.preCheckInvariants = preValidateThis;
223                                    mc.postCheckInvariants = postValidateThis;
224                                    if (returnValueChecks.size() > 0)
225                                    {
226                                            mc.returnValueConfiguration = new MethodReturnValueConfiguration();
227                                            mc.returnValueConfiguration.checks = returnValueChecks;
228                                            returnValueChecks = cf.createList(2); // create a new list for the next method having return value checks
229                                    }
230                                    if (preChecks.size() > 0)
231                                    {
232                                            mc.preExecutionConfiguration = new MethodPreExecutionConfiguration();
233                                            mc.preExecutionConfiguration.checks = preChecks;
234                                            preChecks = cf.createList(2); // create a new list for the next method having pre checks
235                                    }
236                                    if (postChecks.size() > 0)
237                                    {
238                                            mc.postExecutionConfiguration = new MethodPostExecutionConfiguration();
239                                            mc.postExecutionConfiguration.checks = postChecks;
240                                            postChecks = cf.createList(2); // create a new list for the next method having post checks
241                                    }
242                                    classCfg.methodConfigurations.add(mc);
243                            }
244                    }
245            }
246    
247            protected void configureObjectLevelChecks(final ClassConfiguration classCfg)
248            {
249                    final List<Check> checks = getCollectionFactory().createList(2);
250    
251                    for (final Annotation annotation : ReflectionUtils.getAnnotations(classCfg.type,
252                                    Boolean.TRUE.equals(classCfg.inspectInterfaces)))
253                            // check if the current annotation is a constraint annotation
254                            if (annotation.annotationType().isAnnotationPresent(Constraint.class))
255                                    checks.add(initializeCheck(annotation));
256                            else if (annotation.annotationType().isAnnotationPresent(Constraints.class))
257                                    initializeChecks(annotation, checks);
258    
259                    if (checks.size() > 0)
260                    {
261                            classCfg.objectConfiguration = new ObjectConfiguration();
262                            classCfg.objectConfiguration.checks = checks;
263                    }
264            }
265    
266            /**
267             * {@inheritDoc}
268             */
269            public ClassConfiguration getClassConfiguration(final Class< ? > clazz)
270            {
271                    final ClassConfiguration classCfg = new ClassConfiguration();
272                    classCfg.type = clazz;
273    
274                    final Guarded guarded = clazz.getAnnotation(Guarded.class);
275    
276                    if (guarded == null)
277                    {
278                            classCfg.applyFieldConstraintsToConstructors = false;
279                            classCfg.applyFieldConstraintsToSetters = false;
280                            classCfg.assertParametersNotNull = false;
281                            classCfg.checkInvariants = false;
282                            classCfg.inspectInterfaces = false;
283                    }
284                    else
285                    {
286                            classCfg.applyFieldConstraintsToConstructors = guarded.applyFieldConstraintsToConstructors();
287                            classCfg.applyFieldConstraintsToSetters = guarded.applyFieldConstraintsToSetters();
288                            classCfg.assertParametersNotNull = guarded.assertParametersNotNull();
289                            classCfg.checkInvariants = guarded.checkInvariants();
290                            classCfg.inspectInterfaces = guarded.inspectInterfaces();
291                    }
292    
293                    configureObjectLevelChecks(classCfg);
294                    configureFieldChecks(classCfg);
295                    configureConstructorParameterChecks(classCfg);
296                    configureMethodChecks(classCfg);
297    
298                    return classCfg;
299            }
300    
301            /**
302             * {@inheritDoc}
303             */
304            public ConstraintSetConfiguration getConstraintSetConfiguration(final String constraintSetId)
305            {
306                    return null;
307            }
308    
309            protected <ConstraintAnnotation extends Annotation> AnnotationCheck<ConstraintAnnotation> initializeCheck(
310                            final ConstraintAnnotation constraintAnnotation) throws ReflectionException
311            {
312                    assert constraintAnnotation != null;
313    
314                    final Constraint constraint = constraintAnnotation.annotationType().getAnnotation(Constraint.class);
315    
316                    // determine the check class
317                    @SuppressWarnings("unchecked")
318                    final Class<AnnotationCheck<ConstraintAnnotation>> checkClass = (Class<AnnotationCheck<ConstraintAnnotation>>) constraint
319                                    .checkWith();
320    
321                    // instantiate the appropriate check for the found constraint
322                    final AnnotationCheck<ConstraintAnnotation> check = newCheckInstance(checkClass);
323                    check.configure(constraintAnnotation);
324    
325                    for (final CheckInitializationListener listener : listeners)
326                            listener.onCheckInitialized(check);
327                    return check;
328            }
329    
330            protected <ConstraintsAnnotation extends Annotation> void initializeChecks(
331                            final ConstraintsAnnotation constraintsAnnotation, final List<Check> checks) throws ReflectionException
332            {
333                    try
334                    {
335                            final Method getValue = constraintsAnnotation.annotationType().getDeclaredMethod("value",
336                                            (Class< ? >[]) null);
337                            final Object[] constraintAnnotations = (Object[]) getValue.invoke(constraintsAnnotation, (Object[]) null);
338                            for (final Object ca : constraintAnnotations)
339                                    checks.add(initializeCheck((Annotation) ca));
340                    }
341                    catch (final ReflectionException ex)
342                    {
343                            throw ex;
344                    }
345                    catch (final Exception ex)
346                    {
347                            throw new ReflectionException("Cannot initialize constraint check "
348                                            + constraintsAnnotation.annotationType().getName(), ex);
349                    }
350            }
351    
352            protected <ExclusionAnnotation extends Annotation> AnnotationCheckExclusion<ExclusionAnnotation> initializeExclusion(
353                            final ExclusionAnnotation exclusionAnnotation) throws ReflectionException
354            {
355                    assert exclusionAnnotation != null;
356    
357                    final Exclusion constraint = exclusionAnnotation.annotationType().getAnnotation(Exclusion.class);
358    
359                    // determine the check class
360                    final Class< ? > exclusionClass = constraint.excludeWith();
361    
362                    try
363                    {
364                            // instantiate the appropriate exclusion for the found annotation
365                            @SuppressWarnings("unchecked")
366                            final AnnotationCheckExclusion<ExclusionAnnotation> exclusion = (AnnotationCheckExclusion<ExclusionAnnotation>) exclusionClass
367                                            .newInstance();
368                            exclusion.configure(exclusionAnnotation);
369                            return exclusion;
370                    }
371                    catch (final Exception ex)
372                    {
373                            throw new ReflectionException("Cannot initialize constraint exclusion " + exclusionClass.getName(), ex);
374                    }
375            }
376    
377            /**
378             * @return a new instance of the given constraint check implementation class
379             */
380            protected <ConstraintAnnotation extends Annotation> AnnotationCheck<ConstraintAnnotation> newCheckInstance(
381                            final Class<AnnotationCheck<ConstraintAnnotation>> checkClass) throws OValException
382            {
383                    try
384                    {
385                            return checkClass.newInstance();
386                    }
387                    catch (final InstantiationException ex)
388                    {
389                            throw new ReflectionException("Cannot initialize constraint check " + checkClass.getName(), ex);
390                    }
391                    catch (final IllegalAccessException ex)
392                    {
393                            throw new ReflectionException("Cannot initialize constraint check " + checkClass.getName(), ex);
394                    }
395            }
396    
397            public boolean removeCheckInitializationListener(final CheckInitializationListener listener)
398            {
399                    return listeners.remove(listener);
400            }
401    }