001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.util.component; 018 019import java.lang.reflect.Array; 020import java.lang.reflect.Method; 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.Comparator; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.regex.Matcher; 028import java.util.regex.Pattern; 029 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033/** 034 * Parser base class for generating ApiMethod enumerations. 035 */ 036public abstract class ApiMethodParser<T> { 037 038 // also used by JavadocApiMethodGeneratorMojo 039 public static final Pattern ARGS_PATTERN = Pattern.compile("\\s*([^<\\s]+)\\s*(<[^>]+>)?\\s+([^\\s,]+)\\s*,?"); 040 041 private static final String METHOD_PREFIX = "^(\\s*(public|final|synchronized|native)\\s+)*(\\s*<[^>]>)?\\s*(\\S+)\\s+([^\\(]+\\s*)\\("; 042 private static final Pattern METHOD_PATTERN = Pattern.compile("\\s*([^<\\s]+)\\s*(<[^>]+>)?\\s+(\\S+)\\s*\\(\\s*([\\S\\s,]*)\\)\\s*;?\\s*"); 043 044 private static final String JAVA_LANG = "java.lang."; 045 private static final Map<String, Class<?>> PRIMITIVE_TYPES; 046 047 static { 048 PRIMITIVE_TYPES = new HashMap<String, Class<?>>(); 049 PRIMITIVE_TYPES.put("int", Integer.TYPE); 050 PRIMITIVE_TYPES.put("long", Long.TYPE); 051 PRIMITIVE_TYPES.put("double", Double.TYPE); 052 PRIMITIVE_TYPES.put("float", Float.TYPE); 053 PRIMITIVE_TYPES.put("boolean", Boolean.TYPE); 054 PRIMITIVE_TYPES.put("char", Character.TYPE); 055 PRIMITIVE_TYPES.put("byte", Byte.TYPE); 056 PRIMITIVE_TYPES.put("void", Void.TYPE); 057 PRIMITIVE_TYPES.put("short", Short.TYPE); 058 } 059 060 061 private final Logger log = LoggerFactory.getLogger(getClass()); 062 063 private final Class<T> proxyType; 064 private List<String> signatures; 065 private ClassLoader classLoader = ApiMethodParser.class.getClassLoader(); 066 067 public ApiMethodParser(Class<T> proxyType) { 068 this.proxyType = proxyType; 069 } 070 071 public Class<T> getProxyType() { 072 return proxyType; 073 } 074 075 public final List<String> getSignatures() { 076 return signatures; 077 } 078 079 public final void setSignatures(List<String> signatures) { 080 this.signatures = new ArrayList<String>(); 081 this.signatures.addAll(signatures); 082 } 083 084 public final ClassLoader getClassLoader() { 085 return classLoader; 086 } 087 088 public final void setClassLoader(ClassLoader classLoader) { 089 this.classLoader = classLoader; 090 } 091 092 /** 093 * Parses the method signatures from {@code getSignatures()}. 094 * @return list of Api methods as {@link ApiMethodModel} 095 */ 096 public final List<ApiMethodModel> parse() { 097 // parse sorted signatures and generate descriptions 098 List<ApiMethodModel> result = new ArrayList<ApiMethodModel>(); 099 for (String signature : signatures) { 100 101 // remove all modifiers and type parameters for method 102 signature = signature.replaceAll(METHOD_PREFIX, "$4 $5("); 103 // remove all final modifiers for arguments 104 signature = signature.replaceAll("(\\(|,\\s*)final\\s+", "$1"); 105 // remove all redundant spaces in generic parameters 106 signature = signature.replaceAll("\\s*<\\s*", "<").replaceAll("\\s*>", ">"); 107 108 log.debug("Processing " + signature); 109 110 final Matcher methodMatcher = METHOD_PATTERN.matcher(signature); 111 if (!methodMatcher.matches()) { 112 throw new IllegalArgumentException("Invalid method signature " + signature); 113 } 114 115 // ignore generic type parameters in result, if any 116 final Class<?> resultType = forName(methodMatcher.group(1)); 117 final String name = methodMatcher.group(3); 118 final String argSignature = methodMatcher.group(4); 119 120 final List<Argument> arguments = new ArrayList<Argument>(); 121 final List<Class<?>> argTypes = new ArrayList<Class<?>>(); 122 123 final Matcher argsMatcher = ARGS_PATTERN.matcher(argSignature); 124 while (argsMatcher.find()) { 125 126 final Class<?> type = forName(argsMatcher.group(1)); 127 argTypes.add(type); 128 129 final String typeArgsGroup = argsMatcher.group(2); 130 final String typeArgs = typeArgsGroup != null 131 ? typeArgsGroup.substring(1, typeArgsGroup.length() - 1).replaceAll(" ", "") : null; 132 arguments.add(new Argument(argsMatcher.group(3), type, typeArgs)); 133 } 134 135 Method method; 136 try { 137 method = proxyType.getMethod(name, argTypes.toArray(new Class<?>[argTypes.size()])); 138 } catch (NoSuchMethodException e) { 139 throw new IllegalArgumentException("Method not found [" + signature + "] in type " + proxyType.getName()); 140 } 141 result.add(new ApiMethodModel(name, resultType, arguments, method)); 142 } 143 144 // allow derived classes to post process 145 result = processResults(result); 146 147 // check that argument names have the same type across methods 148 Map<String, Class<?>> allArguments = new HashMap<String, Class<?>>(); 149 for (ApiMethodModel model : result) { 150 for (Argument argument : model.getArguments()) { 151 String name = argument.getName(); 152 Class<?> argClass = allArguments.get(name); 153 Class<?> type = argument.getType(); 154 if (argClass == null) { 155 allArguments.put(name, type); 156 } else { 157 if (argClass != type) { 158 throw new IllegalArgumentException("Argument [" + name 159 + "] is used in multiple methods with different types " 160 + argClass.getCanonicalName() + ", " + type.getCanonicalName()); 161 } 162 } 163 } 164 } 165 allArguments.clear(); 166 167 Collections.sort(result, new Comparator<ApiMethodModel>() { 168 @Override 169 public int compare(ApiMethodModel model1, ApiMethodModel model2) { 170 final int nameCompare = model1.name.compareTo(model2.name); 171 if (nameCompare != 0) { 172 return nameCompare; 173 } else { 174 175 final int nArgs1 = model1.arguments.size(); 176 final int nArgsCompare = nArgs1 - model2.arguments.size(); 177 if (nArgsCompare != 0) { 178 return nArgsCompare; 179 } else { 180 // same number of args, compare arg names, kinda arbitrary to use alphabetized order 181 for (int i = 0; i < nArgs1; i++) { 182 final int argCompare = model1.arguments.get(i).name.compareTo(model2.arguments.get(i).name); 183 if (argCompare != 0) { 184 return argCompare; 185 } 186 } 187 // duplicate methods??? 188 log.warn("Duplicate methods found [" + model1 + "], [" + model2 + "]"); 189 return 0; 190 } 191 } 192 } 193 }); 194 195 // assign unique names to every method model 196 final Map<String, Integer> dups = new HashMap<String, Integer>(); 197 for (ApiMethodModel model : result) { 198 // locale independent upper case conversion 199 final String name = model.getName(); 200 final char[] upperCase = new char[name.length()]; 201 final char[] lowerCase = name.toCharArray(); 202 for (int i = 0; i < upperCase.length; i++) { 203 upperCase[i] = Character.toUpperCase(lowerCase[i]); 204 } 205 String uniqueName = new String(upperCase); 206 207 Integer suffix = dups.get(uniqueName); 208 if (suffix == null) { 209 dups.put(uniqueName, 1); 210 } else { 211 dups.put(uniqueName, suffix + 1); 212 StringBuilder builder = new StringBuilder(uniqueName); 213 builder.append("_").append(suffix); 214 uniqueName = builder.toString(); 215 } 216 model.uniqueName = uniqueName; 217 } 218 return result; 219 } 220 221 protected List<ApiMethodModel> processResults(List<ApiMethodModel> result) { 222 return result; 223 } 224 225 protected Class<?> forName(String className) { 226 try { 227 return forName(className, classLoader); 228 } catch (ClassNotFoundException e1) { 229 throw new IllegalArgumentException("Error loading class " + className); 230 } 231 } 232 233 public static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException { 234 Class<?> result; 235 try { 236 // lookup primitive types first 237 result = PRIMITIVE_TYPES.get(className); 238 if (result == null) { 239 result = Class.forName(className, true, classLoader); 240 } 241 } catch (ClassNotFoundException e) { 242 // check if array type 243 if (className.endsWith("[]")) { 244 final int firstDim = className.indexOf('['); 245 final int nDimensions = (className.length() - firstDim) / 2; 246 return Array.newInstance(forName(className.substring(0, firstDim), classLoader), new int[nDimensions]).getClass(); 247 } 248 // try loading from default Java package java.lang 249 result = Class.forName(JAVA_LANG + className, true, classLoader); 250 } 251 252 return result; 253 } 254 255 public static final class ApiMethodModel { 256 private final String name; 257 private final Class<?> resultType; 258 private final List<Argument> arguments; 259 private final Method method; 260 261 private String uniqueName; 262 263 protected ApiMethodModel(String name, Class<?> resultType, List<Argument> arguments, Method method) { 264 this.name = name; 265 this.resultType = resultType; 266 this.arguments = arguments; 267 this.method = method; 268 } 269 270 protected ApiMethodModel(String uniqueName, String name, Class<?> resultType, List<Argument> arguments, Method method) { 271 this.name = name; 272 this.uniqueName = uniqueName; 273 this.resultType = resultType; 274 this.arguments = arguments; 275 this.method = method; 276 } 277 278 public String getUniqueName() { 279 return uniqueName; 280 } 281 282 public String getName() { 283 return name; 284 } 285 286 public Class<?> getResultType() { 287 return resultType; 288 } 289 290 public Method getMethod() { 291 return method; 292 } 293 294 public List<Argument> getArguments() { 295 return arguments; 296 } 297 298 @Override 299 public String toString() { 300 StringBuilder builder = new StringBuilder(); 301 builder.append(resultType.getName()).append(" "); 302 builder.append(name).append("("); 303 for (Argument argument : arguments) { 304 builder.append(argument.getType().getCanonicalName()).append(" "); 305 builder.append(argument.getName()).append(", "); 306 } 307 if (!arguments.isEmpty()) { 308 builder.delete(builder.length() - 2, builder.length()); 309 } 310 builder.append(");"); 311 return builder.toString(); 312 } 313 } 314 315 public static final class Argument { 316 private final String name; 317 private final Class<?> type; 318 private final String typeArgs; 319 320 public Argument(String name, Class<?> type, String typeArgs) { 321 this.name = name; 322 this.type = type; 323 this.typeArgs = typeArgs; 324 } 325 326 public String getName() { 327 return name; 328 } 329 330 public Class<?> getType() { 331 return type; 332 } 333 334 public String getTypeArgs() { 335 return typeArgs; 336 } 337 338 @Override 339 public String toString() { 340 StringBuilder builder = new StringBuilder(); 341 builder.append(type.getCanonicalName()); 342 if (typeArgs != null) { 343 builder.append("<").append(typeArgs).append(">"); 344 } 345 builder.append(" ").append(name); 346 return builder.toString(); 347 } 348 } 349}