View Javadoc

    1   /**
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd.lang.rule.properties;
    5
    6   import java.lang.reflect.Array;
    7   import java.lang.reflect.Method;
    8   import java.util.Map;
    9
    10  import net.sourceforge.pmd.PropertyDescriptorFactory;
    11  import net.sourceforge.pmd.lang.rule.properties.factories.BasicPropertyDescriptorFactory;
    12  import net.sourceforge.pmd.util.ClassUtil;
    13  import net.sourceforge.pmd.util.StringUtil;
    14
    15  /**
    16   * Defines a property type that can specify a single method to use as part of a rule.
    17   *
    18   * Rule developers can limit the rules to those within designated packages per the
    19   * 'legalPackages' argument in the constructor which can be an array of partial
    20   * package names, i.e., ["java.lang", "com.mycompany" ].
    21   *
    22   * @author Brian Remedios
    23   */
    24  public class MethodProperty extends AbstractPackagedProperty<Method> {
    25
    26      public static final char CLASS_METHOD_DELIMITER = '#';
    27      public static final char METHOD_ARG_DELIMITER = ',';
    28      public static final char[] METHOD_GROUP_DELIMITERS = new char[] { '(', ')' };
    29
    30      private static final String ARRAY_FLAG = "[]";
    31      private static final Map<Class<?>, String> TYPE_SHORTCUTS = ClassUtil.getClassShortNames();
    32
    33  	public static final PropertyDescriptorFactory FACTORY = new BasicPropertyDescriptorFactory<MethodProperty>(Method.class, packagedFieldTypesByKey) {
    34
    35  		public MethodProperty createWith(Map<String, String> valuesById) {
    36  			return new MethodProperty(
    37  					nameIn(valuesById),
    38  					descriptionIn(valuesById),
    39  					defaultValueIn(valuesById),
    40  					legalPackageNamesIn(valuesById),
    41  					0f);
    42  		}
    43  	};
    44
    45      /**
    46       * @param cls Class<?>
    47       * @return String
    48       */
    49      private static String shortestNameFor(Class<?> cls) {
    50          String compactName = TYPE_SHORTCUTS.get(cls);
    51          return compactName == null ? cls.getName() : compactName;
    52      }
    53
    54      /**
    55       * Return the value of `method' as a string that can be easily recognized
    56       * and parsed when we see it again.
    57       *
    58       * @param method the method to convert
    59       * @return the string value
    60       */
    61      public static String asStringFor(Method method) {
    62          StringBuilder sb = new StringBuilder();
    63          asStringOn(method, sb);
    64          return sb.toString();
    65      }
    66
    67      /**
    68       * @return String
    69       */
    70      protected String defaultAsString() {
    71          return asStringFor(defaultValue());
    72      }
    73      /**
    74       * @param type Class<?>
    75       * @param sb StringBuilder
    76       */
    77      private static void serializedTypeIdOn(Class<?> type, StringBuilder sb) {
    78
    79          Class<?> arrayType = type.getComponentType();
    80          if (arrayType == null) {
    81              sb.append(shortestNameFor(type));
    82              return;
    83          }
    84          sb.append(shortestNameFor(arrayType)).append(ARRAY_FLAG);
    85      }
    86
    87      /**
    88       * Serializes the method signature onto the specified buffer.
    89       *
    90       * @param method Method
    91       * @param sb StringBuilder
    92       */
    93      public static void asStringOn(Method method, StringBuilder sb) {
    94
    95          Class<?> clazz = method.getDeclaringClass();
    96
    97          sb.append(shortestNameFor(clazz));
    98          sb.append(CLASS_METHOD_DELIMITER);
    99          sb.append(method.getName());
    100
    101         sb.append(METHOD_GROUP_DELIMITERS[0]);
    102
    103         Class<?>[] argTypes = method.getParameterTypes();
    104         if (argTypes.length == 0) {
    105             sb.append(METHOD_GROUP_DELIMITERS[1]);
    106             return;
    107         }
    108
    109         serializedTypeIdOn(argTypes[0], sb);
    110         for (int i = 1; i < argTypes.length; i++) {
    111             sb.append(METHOD_ARG_DELIMITER);
    112             serializedTypeIdOn(argTypes[i], sb);
    113         }
    114         sb.append(METHOD_GROUP_DELIMITERS[1]);
    115     }
    116
    117     /**
    118      * @param typeName String
    119      * @return Class<?>
    120      */
    121     private static Class<?> typeFor(String typeName) {
    122
    123         Class<?> type = null;
    124
    125         if (typeName.endsWith(ARRAY_FLAG)) {
    126             String arrayTypeName = typeName.substring(0, typeName.length() - ARRAY_FLAG.length());
    127             type = typeFor(arrayTypeName); // recurse
    128             return Array.newInstance(type, 0).getClass(); // TODO is there a better way to get an array type?
    129         }
    130
    131         type = ClassUtil.getTypeFor(typeName); // try shortcut first
    132         if (type != null) {
    133             return type;
    134         }
    135
    136         try {
    137             return Class.forName(typeName);
    138         } catch (Exception ex) {
    139             return null;
    140         }
    141     }
    142
    143     /**
    144      * Returns the method specified within the string argument after parsing out its source class and
    145      * any optional arguments. Callers need to specify the delimiters expected between the various
    146      * elements.  I.e.:
    147      *
    148      * 	"String#isEmpty()"
    149      *  "String#indexOf(int)"
    150      *  "String#substring(int,int)"
    151      *
    152      *  If a method isn't part of the specified class we will walk up any superclasses to Object to try
    153      *  and find it.
    154      *
    155      *  If the classes are listed in the ClassUtil class within in Typemaps then you likely can avoid
    156      *  specifying fully-qualified class names per the above example.
    157      *
    158      *  Returns null if a matching method cannot be found.
    159      *
    160      * @param methodNameAndArgTypes
    161      * @param classMethodDelimiter
    162      * @param methodArgDelimiter
    163      * @return Method
    164      */
    165     public static Method methodFrom(String methodNameAndArgTypes, char classMethodDelimiter, char methodArgDelimiter) {
    166
    167         // classname#methodname(arg1,arg2)
    168         //          0          1         2
    169
    170         int delimPos0 = methodNameAndArgTypes.indexOf(classMethodDelimiter);
    171         if (delimPos0 < 0) {
    172             return null;
    173         }
    174
    175         String className = methodNameAndArgTypes.substring(0, delimPos0);
    176         Class<?> type = ClassUtil.getTypeFor(className);
    177         if (type == null) {
    178             return null;
    179         }
    180
    181         int delimPos1 = methodNameAndArgTypes.indexOf(METHOD_GROUP_DELIMITERS[0]);
    182         if (delimPos1 < 0) {
    183             String methodName = methodNameAndArgTypes.substring(delimPos0 + 1);
    184             return ClassUtil.methodFor(type, methodName, ClassUtil.EMPTY_CLASS_ARRAY);
    185         }
    186
    187         String methodName = methodNameAndArgTypes.substring(delimPos0 + 1, delimPos1);
    188         if (StringUtil.isEmpty(methodName)) {
    189             return null;
    190         } // missing method name?
    191
    192         int delimPos2 = methodNameAndArgTypes.indexOf(METHOD_GROUP_DELIMITERS[1]);
    193         if (delimPos2 < 0) {
    194             return null;
    195         } // error!
    196
    197         String argTypesStr = methodNameAndArgTypes.substring(delimPos1 + 1, delimPos2);
    198         if (StringUtil.isEmpty(argTypesStr)) {
    199             return ClassUtil.methodFor(type, methodName, ClassUtil.EMPTY_CLASS_ARRAY);
    200         } // no arg(s)
    201
    202         String[] argTypeNames = StringUtil.substringsOf(argTypesStr, methodArgDelimiter);
    203         Class<?>[] argTypes = new Class[argTypeNames.length];
    204         for (int i = 0; i < argTypes.length; i++) {
    205             argTypes[i] = typeFor(argTypeNames[i]);
    206         }
    207
    208         return ClassUtil.methodFor(type, methodName, argTypes);
    209     }
    210
    211     /**
    212      * @param methodStr String
    213      * @return Method
    214      */
    215     public static Method methodFrom(String methodStr) {
    216     	return methodFrom(methodStr, CLASS_METHOD_DELIMITER, METHOD_ARG_DELIMITER);
    217     }
    218
    219     /**
    220      * Constructor for MethodProperty.
    221      *
    222      * @param theName        String
    223      * @param theDescription String
    224      * @param theDefault     Method
    225      * @param legalPackageNames String[]
    226      * @param theUIOrder     float
    227      * @throws IllegalArgumentException
    228      */
    229     public MethodProperty(String theName, String theDescription, Method theDefault, String[] legalPackageNames, float theUIOrder) {
    230         super(theName, theDescription, theDefault, legalPackageNames, theUIOrder);
    231     }
    232
    233     /**
    234      * Constructor for MethodProperty.
    235      *
    236      * @param theName        String
    237      * @param theDescription String
    238      * @param defaultMethodStr String
    239      * @param legalPackageNames String[]
    240      * @param theUIOrder     float
    241      * @throws IllegalArgumentException
    242      */
    243     public MethodProperty(String theName, String theDescription, String defaultMethodStr, String[] legalPackageNames, float theUIOrder) {
    244         super(theName, theDescription, methodFrom(defaultMethodStr), legalPackageNames, theUIOrder);
    245     }
    246
    247     /**
    248      * Constructor for MethodProperty.
    249      *
    250      * @param theName        String
    251      * @param theDescription String
    252      * @param defaultMethodStr String
    253      * @param otherParams Map<String, String>
    254      * @param theUIOrder     float
    255      * @throws IllegalArgumentException
    256      */
    257     public MethodProperty(String theName, String theDescription, String defaultMethodStr, Map<String, String> otherParams, float theUIOrder) {
    258         this(theName, theDescription, methodFrom(defaultMethodStr), packageNamesIn(otherParams), theUIOrder);
    259     }
    260
    261     /**
    262      * Return the value as a string that can be easily recognized and parsed
    263      * when we see it again.
    264      *
    265      * @param value Object
    266      * @return String
    267      */
    268     @Override
    269     protected String asString(Object value) {
    270         return value == null ? "" : asStringFor((Method) value);
    271     }
    272
    273     /**
    274      * @param item Object
    275      * @return String
    276      */
    277     @Override
    278     protected String packageNameOf(Object item) {
    279
    280         final Method method = (Method) item;
    281         return method.getDeclaringClass().getName() + '.' + method.getName();
    282     }
    283
    284     /**
    285      * @return String
    286      */
    287     @Override
    288     protected String itemTypeName() {
    289         return "method";
    290     }
    291
    292     /**
    293      * @return Class
    294      * @see net.sourceforge.pmd.PropertyDescriptor#type()
    295      */
    296     public Class<Method> type() {
    297         return Method.class;
    298     }
    299
    300     /**
    301      * @param valueString  String
    302      * @return Object
    303      * @throws IllegalArgumentException
    304      * @see net.sourceforge.pmd.PropertyDescriptor#valueFrom(String)
    305      */
    306     public Method valueFrom(String valueString) throws IllegalArgumentException {
    307        return methodFrom(valueString);
    308     }
    309 }