Coverage Report - de.smartics.properties.spi.core.metadata.PropertyMetaDataParser
 
Classes in this File Line Coverage Branch Coverage Complexity
PropertyMetaDataParser
0%
0/169
0%
0/70
2,654
 
 1  
 /*
 2  
  * Copyright 2012-2013 smartics, Kronseder & Reiner GmbH
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package de.smartics.properties.spi.core.metadata;
 17  
 
 18  
 import static de.smartics.util.lang.StaticAnalysis.RAWTYPES;
 19  
 import static de.smartics.util.lang.StaticAnalysis.UNCHECKED;
 20  
 
 21  
 import java.lang.annotation.Annotation;
 22  
 import java.lang.reflect.Method;
 23  
 import java.util.ArrayList;
 24  
 import java.util.Collection;
 25  
 import java.util.List;
 26  
 
 27  
 import javax.validation.constraints.Size;
 28  
 
 29  
 import org.apache.commons.lang.ArrayUtils;
 30  
 
 31  
 import de.smartics.properties.api.core.annotations.AccessType;
 32  
 import de.smartics.properties.api.core.annotations.PropertyDefinitionTime;
 33  
 import de.smartics.properties.api.core.annotations.PropertyElementType;
 34  
 import de.smartics.properties.api.core.annotations.PropertyListElementConstraints;
 35  
 import de.smartics.properties.api.core.annotations.PropertySet;
 36  
 import de.smartics.properties.api.core.domain.PropertiesContext;
 37  
 import de.smartics.properties.api.core.domain.PropertyConstraint;
 38  
 import de.smartics.properties.api.core.domain.PropertyDescriptor;
 39  
 import de.smartics.properties.api.core.domain.PropertyExpression;
 40  
 import de.smartics.properties.api.core.domain.PropertyKey;
 41  
 import de.smartics.properties.api.core.domain.PropertyType;
 42  
 import de.smartics.properties.api.core.domain.PropertyValueRange;
 43  
 import de.smartics.properties.spi.core.constraint.jsr303.GenericPropertyConstraint;
 44  
 import de.smartics.properties.spi.core.constraints.ListValueConstraint;
 45  
 import de.smartics.properties.spi.core.constraints.PropertyRangeConstraint;
 46  
 import de.smartics.properties.spi.core.context.PropertyContextProxy;
 47  
 import de.smartics.properties.spi.core.metadata.PropertyMetaData.Builder;
 48  
 import de.smartics.properties.spi.core.util.PropertyUtils;
 49  
 import de.smartics.properties.spi.core.value.CollectionPropertyValueRange;
 50  
 import de.smartics.properties.spi.core.value.EnumeratedPropertyValueRange;
 51  
 import de.smartics.util.lang.Arguments;
 52  
 
 53  
 /**
 54  
  * Parses runtime annotations to create property metadata.
 55  
  */
 56  
 public final class PropertyMetaDataParser
 57  
 { // NOPMD
 58  
   // ********************************* Fields *********************************
 59  
 
 60  
   // --- constants ------------------------------------------------------------
 61  
 
 62  
   // --- members --------------------------------------------------------------
 63  
 
 64  
   /**
 65  
    * The context to access property metadata.
 66  
    */
 67  
   private final PropertiesContext context;
 68  
 
 69  
   // ****************************** Initializer *******************************
 70  
 
 71  
   // ****************************** Constructors ******************************
 72  
 
 73  
   /**
 74  
    * Default constructor to generate property descriptors with comments.
 75  
    *
 76  
    * @param context the context to access property metadata. May be
 77  
    *          <code>null</code> in which case no comments are generated.
 78  
    */
 79  
   private PropertyMetaDataParser(final PropertiesContext context)
 80  0
   {
 81  0
     this.context = context;
 82  0
   }
 83  
 
 84  
   // ****************************** Inner Classes *****************************
 85  
 
 86  
   // ********************************* Methods ********************************
 87  
 
 88  
   // --- init -----------------------------------------------------------------
 89  
 
 90  
   // --- factory --------------------------------------------------------------
 91  
 
 92  
   /**
 93  
    * Convenience constructor to generate property descriptors without comments.
 94  
    *
 95  
    * @return the created parser instance.
 96  
    */
 97  
   public static PropertyMetaDataParser createWithoutContextAccess()
 98  
   {
 99  0
     return new PropertyMetaDataParser(null);
 100  
   }
 101  
 
 102  
   /**
 103  
    * Convenience constructor to generate property descriptors with access to
 104  
    * property comments.
 105  
    *
 106  
    * @param context the context to access comment information for properties.
 107  
    * @return the created parser instance.
 108  
    * @throws NullPointerException if {@code context} is <code>null</code>.
 109  
    */
 110  
   public static PropertyMetaDataParser create(final PropertiesContext context)
 111  
     throws NullPointerException
 112  
   {
 113  0
     Arguments.checkNotNull("context", context);
 114  0
     return new PropertyMetaDataParser(context);
 115  
   }
 116  
 
 117  
   // --- get&set --------------------------------------------------------------
 118  
 
 119  
   // --- business -------------------------------------------------------------
 120  
 
 121  
   /**
 122  
    * Reads the property descriptor information from the type.
 123  
    *
 124  
    * @param propertySetType the type to read the property descriptor.
 125  
    * @return the property descriptor provided by the methods of the type.
 126  
    * @throws NullPointerException if {@code propertySetType} is
 127  
    *           <code>null</code>.
 128  
    * @throws IllegalArgumentException if {@code propertySetType} is not
 129  
    *           annotated with {@link PropertySet}.
 130  
    */
 131  
   public List<PropertyDescriptor> readDescriptors(final Class<?> propertySetType)
 132  
     throws NullPointerException, IllegalArgumentException
 133  
   {
 134  0
     Arguments.checkNotNull("propertySetType", propertySetType);
 135  0
     PropertyUtils.checkPropertySetType(propertySetType);
 136  
 
 137  0
     final Method[] methods = propertySetType.getMethods();
 138  0
     final List<PropertyDescriptor> descriptors =
 139  
         new ArrayList<PropertyDescriptor>(methods.length);
 140  0
     for (final Method method : methods)
 141  
     {
 142  0
       if (PropertyUtils.isPropertyMethod(method))
 143  
       {
 144  0
         final PropertyDescriptor descriptor = readDescriptor(method);
 145  0
         descriptors.add(descriptor);
 146  
       }
 147  
     }
 148  
 
 149  0
     return descriptors;
 150  
   }
 151  
 
 152  
   /**
 153  
    * Reads the property descriptor information from the method.
 154  
    *
 155  
    * @param method the method to read the property descriptor.
 156  
    * @return the property descriptor provided by this method.
 157  
    * @throws NullPointerException if {@code method} is <code>null</code>.
 158  
    */
 159  
   public PropertyDescriptor readDescriptor(final Method method)
 160  
     throws NullPointerException
 161  
   {
 162  0
     Arguments.checkNotNull("method", method);
 163  
 
 164  0
     final PropertyMetaData.Builder builder = new PropertyMetaData.Builder();
 165  
 
 166  0
     final Class<?> declaringType = method.getDeclaringClass();
 167  0
     final PropertyKey key = readKey(method);
 168  0
     final PropertyType type = readType(method);
 169  0
     builder.with(new PropertyContextProxy(context))
 170  
         .withDeclaringType(declaringType).with(key).with(type);
 171  
 
 172  0
     addValueRange(builder, method);
 173  0
     addExpression(builder, method);
 174  0
     addLifecycle(builder, method);
 175  0
     addConstraints(builder, method);
 176  0
     addDocumentProxy(builder, method);
 177  0
     addComments(builder);
 178  
 
 179  0
     return builder.build();
 180  
   }
 181  
 
 182  
   /**
 183  
    * Reads the property key information from the method.
 184  
    *
 185  
    * @param method the method to read the property key.
 186  
    * @return the property key provided by this method.
 187  
    */
 188  
   public PropertyKey readKey(final Method method)
 189  
   {
 190  0
     final String set = readPropertySetName(method);
 191  0
     final String name = readPropertyKeyName(method);
 192  0
     return PropertyKey.create(set, name);
 193  
   }
 194  
 
 195  
   /**
 196  
    * Reads the property set name of the type.
 197  
    *
 198  
    * @param type the type to read the property set name.
 199  
    * @return the property name as specified of defaulted.
 200  
    */
 201  
   public String readPropertySetName(final Class<?> type)
 202  
   {
 203  0
     String propertySetName = readPropertySetNameInternal(type);
 204  
 
 205  0
     if (" ".equals(propertySetName))
 206  
     {
 207  0
       propertySetName = type.getName();
 208  
     }
 209  0
     else if ("".equals(propertySetName))
 210  
     {
 211  0
       propertySetName = null;
 212  
     }
 213  
 
 214  0
     return propertySetName;
 215  
   }
 216  
 
 217  
   private static String readPropertyKeyName(final Method method)
 218  
   {
 219  
     final String keyName;
 220  
 
 221  0
     final de.smartics.properties.api.core.annotations.PropertyKeyName propertyKey =
 222  
         method
 223  
             .getAnnotation(de.smartics.properties.api.core.annotations.PropertyKeyName.class);
 224  0
     if (propertyKey != null)
 225  
     {
 226  0
       keyName = propertyKey.value();
 227  
     }
 228  
     else
 229  
     {
 230  0
       final String methodName = method.getName();
 231  0
       if (isGetterMethod(methodName))
 232  
       {
 233  0
         keyName = calcPropertyName(methodName);
 234  
       }
 235  
       else
 236  
       {
 237  0
         keyName = methodName;
 238  
       }
 239  
     }
 240  
 
 241  0
     return keyName;
 242  
   }
 243  
 
 244  
   private static String calcPropertyName(final String methodName)
 245  
   {
 246  0
     return Character.toLowerCase(methodName.charAt(3))
 247  
            + methodName.substring(4);
 248  
   }
 249  
 
 250  
   private static boolean isGetterMethod(final String methodName)
 251  
   {
 252  0
     return methodName.length() > 3 && methodName.startsWith("get")
 253  
            && Character.isUpperCase(methodName.charAt(3));
 254  
   }
 255  
 
 256  
   private static String readPropertySetName(final Method method)
 257  
   {
 258  
     final String propertySetName;
 259  
 
 260  0
     final PropertySet primaryPropertySet =
 261  
         method.getAnnotation(PropertySet.class);
 262  0
     if (primaryPropertySet != null)
 263  
     {
 264  0
       propertySetName = primaryPropertySet.value();
 265  
     }
 266  
     else
 267  
     {
 268  0
       final Class<?> type = method.getDeclaringClass();
 269  0
       propertySetName = readPropertySetNameInternal(type);
 270  
     }
 271  
 
 272  0
     if (" ".equals(propertySetName))
 273  
     {
 274  0
       final Class<?> type = method.getDeclaringClass();
 275  0
       return type.getName();
 276  
     }
 277  0
     else if ("".equals(propertySetName))
 278  
     {
 279  0
       return null;
 280  
     }
 281  
 
 282  0
     return propertySetName;
 283  
   }
 284  
 
 285  
   private static String readPropertySetNameInternal(final Class<?> type)
 286  
   {
 287  
     final String propertySetName;
 288  0
     final PropertySet propertySet = type.getAnnotation(PropertySet.class);
 289  0
     if (propertySet != null)
 290  
     {
 291  0
       propertySetName = propertySet.value();
 292  
     }
 293  
     else
 294  
     {
 295  0
       propertySetName = type.getName();
 296  
     }
 297  0
     return propertySetName;
 298  
   }
 299  
 
 300  
   private static PropertyType readType(final Method method)
 301  
   {
 302  0
     final Class<?> type = method.getReturnType();
 303  0
     final Class<?> subType = determineSubType(method);
 304  
 
 305  0
     return new PropertyType(type, subType);
 306  
   }
 307  
 
 308  
   private static Class<?> determineSubType(final Method method)
 309  
   {
 310  0
     final PropertyElementType elementType =
 311  
         method.getAnnotation(PropertyElementType.class);
 312  
     final Class<?> subType;
 313  0
     if (elementType != null)
 314  
     {
 315  0
       subType = elementType.value();
 316  
     }
 317  
     else
 318  
     {
 319  0
       subType = null;
 320  
     }
 321  0
     return subType;
 322  
   }
 323  
 
 324  
   @SuppressWarnings({ RAWTYPES, UNCHECKED })
 325  
   private static void addValueRange(final Builder builder, final Method method)
 326  
   {
 327  0
     final PropertyValueRange<?> valueRange = calculateValueRange(method);
 328  0
     if (valueRange != null)
 329  
     {
 330  0
       builder.with(valueRange);
 331  0
       final PropertyRangeConstraint<?> constraint =
 332  
           new PropertyRangeConstraint(valueRange);
 333  0
       builder.add(constraint);
 334  
     }
 335  0
   }
 336  
 
 337  
   @SuppressWarnings({ RAWTYPES, UNCHECKED })
 338  
   private static PropertyValueRange<?> calculateValueRange(final Method method)
 339  
   {
 340  0
     final Class<?> returnType = method.getReturnType();
 341  0
     if (returnType.isEnum())
 342  
     {
 343  0
       final PropertyValueRange<?> valueRange =
 344  
           new EnumeratedPropertyValueRange(returnType);
 345  0
       return valueRange;
 346  
     }
 347  
     else
 348  
     {
 349  0
       final de.smartics.properties.api.core.annotations.PropertyIntValueRange intAnnotation =
 350  
           method
 351  
               .getAnnotation(de.smartics.properties.api.core.annotations.PropertyIntValueRange.class);
 352  0
       if (intAnnotation != null)
 353  
       {
 354  0
         final Integer[] values =
 355  
             ArrayUtils.toObject(intAnnotation.value());
 356  0
         final CollectionPropertyValueRange<Integer> valueRange =
 357  
             new CollectionPropertyValueRange<Integer>(values);
 358  0
         return valueRange;
 359  
       }
 360  
 
 361  0
       final de.smartics.properties.api.core.annotations.PropertyStringValueRange annotation =
 362  
           method
 363  
               .getAnnotation(de.smartics.properties.api.core.annotations.PropertyStringValueRange.class);
 364  0
       if (annotation != null)
 365  
       {
 366  0
         final String[] values = annotation.value();
 367  0
         final CollectionPropertyValueRange<String> valueRange =
 368  
             new CollectionPropertyValueRange<String>(values);
 369  0
         return valueRange;
 370  
       }
 371  
     }
 372  
 
 373  0
     return null;
 374  
   }
 375  
 
 376  
   private static void addExpression(final Builder builder, final Method method)
 377  
   {
 378  0
     final de.smartics.properties.api.core.annotations.PropertyExpression annotation =
 379  
         method
 380  
             .getAnnotation(de.smartics.properties.api.core.annotations.PropertyExpression.class);
 381  0
     if (annotation != null)
 382  
     {
 383  0
       final String expressionString = annotation.value();
 384  0
       final PropertyExpression expression =
 385  
           PropertyExpression.create(expressionString);
 386  0
       builder.with(expression);
 387  
     }
 388  0
   }
 389  
 
 390  
   private void addLifecycle(final Builder builder, final Method method)
 391  
   {
 392  0
     final de.smartics.properties.api.core.annotations.PropertyLifecycle annotation =
 393  
         method
 394  
             .getAnnotation(de.smartics.properties.api.core.annotations.PropertyLifecycle.class);
 395  0
     if (annotation != null)
 396  
     {
 397  0
       final PropertyDefinitionTime configurationTime =
 398  
           annotation.definitionTime();
 399  0
       if (configurationTime != null)
 400  
       {
 401  0
         builder.with(configurationTime);
 402  
       }
 403  
 
 404  0
       final AccessType accessType = annotation.access();
 405  0
       if (accessType != null)
 406  
       {
 407  0
         builder.with(accessType);
 408  
       }
 409  
 
 410  0
       final long updateInterval = annotation.updateInterval();
 411  0
       builder.withUpdateIntervalInMs(updateInterval);
 412  
     }
 413  0
   }
 414  
 
 415  
   @SuppressWarnings({ RAWTYPES, UNCHECKED })
 416  
   private void addConstraints(final Builder builder, final Method method)
 417  
   {
 418  0
     final Annotation[] annotations = method.getAnnotations();
 419  0
     boolean collectionConstraintAdded = false;
 420  
 
 421  0
     for (final Annotation annotation : annotations)
 422  
     {
 423  0
       final javax.validation.Constraint annotationAnnotation =
 424  
           annotation.annotationType().getAnnotation(
 425  
               javax.validation.Constraint.class);
 426  0
       if (annotationAnnotation != null)
 427  
       {
 428  0
         if (annotation.annotationType() != Size.class)
 429  
         {
 430  0
           final GenericPropertyConstraint propertyConstraint =
 431  
               new GenericPropertyConstraint(annotation);
 432  0
           builder.add(propertyConstraint);
 433  0
         }
 434  
         else
 435  
         {
 436  0
           checkCollection(builder, method, (Size) annotation);
 437  0
           collectionConstraintAdded = true;
 438  
         }
 439  
       }
 440  
     }
 441  
 
 442  0
     if (!collectionConstraintAdded)
 443  
     {
 444  0
       checkCollection(builder, method, null);
 445  
     }
 446  0
   }
 447  
 
 448  
   private void checkCollection(final PropertyMetaData.Builder builder,
 449  
       final Method method, final Size size)
 450  
   {
 451  0
     final Class<?> type = builder.getType().getType();
 452  
 
 453  0
     if (Collection.class.isAssignableFrom(type))
 454  
     {
 455  0
       final PropertyElementType elementTypeAnnotation =
 456  
           method.getAnnotation(PropertyElementType.class);
 457  
 
 458  0
       final Class<?> elementType = calcElementType(elementTypeAnnotation);
 459  0
       final List<? extends PropertyConstraint<?>> elementConstraints =
 460  
           calcElementConstraints(method);
 461  0
       Integer min = null;
 462  0
       Integer max = null;
 463  0
       if (size != null)
 464  
       {
 465  0
         min = size.min();
 466  0
         max = size.max();
 467  
       }
 468  
 
 469  0
       final PropertyConstraint<?> propertyConstraint =
 470  
           new ListValueConstraint(elementType, min, max, elementConstraints);
 471  0
       builder.add(propertyConstraint);
 472  
     }
 473  0
   }
 474  
 
 475  
   private Class<?> calcElementType(
 476  
       final PropertyElementType elementTypeAnnotation)
 477  
   {
 478  
     final Class<?> elementType;
 479  0
     if (elementTypeAnnotation != null)
 480  
     {
 481  0
       elementType = elementTypeAnnotation.value();
 482  
     }
 483  
     else
 484  
     {
 485  0
       elementType = String.class;
 486  
     }
 487  0
     return elementType;
 488  
   }
 489  
 
 490  
   @SuppressWarnings({ RAWTYPES, UNCHECKED })
 491  
   private List<? extends PropertyConstraint<?>> calcElementConstraints(
 492  
       final Method method)
 493  
   {
 494  0
     final Annotation[] annotations = calcElementsConstraintsType(method);
 495  0
     if (annotations != null)
 496  
     {
 497  0
       final List constraints = new ArrayList(annotations.length);
 498  
 
 499  0
       for (final Annotation annotation : annotations)
 500  
       {
 501  0
         final javax.validation.Constraint annotationAnnotation =
 502  
             annotation.annotationType().getAnnotation(
 503  
                 javax.validation.Constraint.class);
 504  0
         if (annotationAnnotation != null)
 505  
         {
 506  0
           final GenericPropertyConstraint propertyConstraint =
 507  
               new GenericPropertyConstraint(annotation);
 508  0
           constraints.add(propertyConstraint);
 509  
         }
 510  
       }
 511  
 
 512  0
       return constraints;
 513  
     }
 514  
 
 515  0
     return new ArrayList(0);
 516  
   }
 517  
 
 518  
   private static Annotation[] calcElementsConstraintsType(final Method method) // NOPMD
 519  
   {
 520  0
     final Class<?> enclosingType = method.getDeclaringClass();
 521  0
     final Class<?>[] nestedClasses = enclosingType.getDeclaredClasses();
 522  0
     if (nestedClasses != null)
 523  
     {
 524  0
       for (final Class<?> nestedClass : nestedClasses)
 525  
       {
 526  0
         final PropertyListElementConstraints constraintsType =
 527  
             nestedClass.getAnnotation(PropertyListElementConstraints.class);
 528  0
         if (constraintsType != null)
 529  
         {
 530  0
           final String methodName = method.getName();
 531  0
           return calcElementConstraintMethod(nestedClass, methodName);
 532  
         }
 533  
       }
 534  
     }
 535  
 
 536  0
     return null;
 537  
   }
 538  
 
 539  
   private static Annotation[] calcElementConstraintMethod(
 540  
       final Class<?> nestedClass, final String methodName)
 541  
   {
 542  
     try
 543  
     {
 544  0
       final Method elementConstraintMethod =
 545  
           nestedClass.getMethod(methodName, new Class<?>[0]);
 546  0
       return elementConstraintMethod.getAnnotations();
 547  
     }
 548  0
     catch (final Exception e)
 549  
     {
 550  0
       return null;
 551  
     }
 552  
   }
 553  
 
 554  
   private void addDocumentProxy(final Builder builder, final Method method)
 555  
   {
 556  0
     final DocumentMetaDataProxy proxy =
 557  
         new MetaInfDocumentMetaDataProxy(context, method);
 558  0
     builder.with(proxy);
 559  0
   }
 560  
 
 561  
   private void addComments(final Builder builder)
 562  
   {
 563  0
     final PropertyCommentProxy commentProxy = new PropertyCommentProxy(context);
 564  0
     builder.with(commentProxy);
 565  0
   }
 566  
 
 567  
   // --- object basics --------------------------------------------------------
 568  
 
 569  
 }