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.Comparator; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028 029import org.apache.camel.util.ObjectHelper; 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*(<[^>]+>)?(<(?<genericTypeParameterName>\\S+)\\s+extends\\s+" 043 + "(?<genericTypeParameterUpperBound>\\S+)>\\s+(?<returnType>\\S+))?\\s+(\\S+)\\s*\\(\\s*(?<signature>[\\S\\s,]*)\\)\\s*;?\\s*"); 044 045 private static final String JAVA_LANG = "java.lang."; 046 private static final Map<String, Class<?>> PRIMITIVE_TYPES; 047 048 static { 049 PRIMITIVE_TYPES = new HashMap<>(); 050 PRIMITIVE_TYPES.put("int", Integer.TYPE); 051 PRIMITIVE_TYPES.put("long", Long.TYPE); 052 PRIMITIVE_TYPES.put("double", Double.TYPE); 053 PRIMITIVE_TYPES.put("float", Float.TYPE); 054 PRIMITIVE_TYPES.put("boolean", Boolean.TYPE); 055 PRIMITIVE_TYPES.put("char", Character.TYPE); 056 PRIMITIVE_TYPES.put("byte", Byte.TYPE); 057 PRIMITIVE_TYPES.put("void", Void.TYPE); 058 PRIMITIVE_TYPES.put("short", Short.TYPE); 059 } 060 061 062 private final Logger log = LoggerFactory.getLogger(getClass()); 063 064 private final Class<T> proxyType; 065 private List<String> signatures; 066 private ClassLoader classLoader = ApiMethodParser.class.getClassLoader(); 067 068 public ApiMethodParser(Class<T> proxyType) { 069 this.proxyType = proxyType; 070 } 071 072 public Class<T> getProxyType() { 073 return proxyType; 074 } 075 076 public final List<String> getSignatures() { 077 return signatures; 078 } 079 080 public final void setSignatures(List<String> signatures) { 081 this.signatures = new ArrayList<>(); 082 this.signatures.addAll(signatures); 083 } 084 085 public final ClassLoader getClassLoader() { 086 return classLoader; 087 } 088 089 public final void setClassLoader(ClassLoader classLoader) { 090 this.classLoader = classLoader; 091 } 092 093 /** 094 * Parses the method signatures from {@code getSignatures()}. 095 * @return list of Api methods as {@link ApiMethodModel} 096 */ 097 public final List<ApiMethodModel> parse() { 098 // parse sorted signatures and generate descriptions 099 List<ApiMethodModel> result = new ArrayList<>(); 100 for (String signature : signatures) { 101 102 // skip comment or empty lines 103 if (signature.startsWith("##") || ObjectHelper.isEmpty(signature)) { 104 continue; 105 } 106 107 // remove all modifiers and type parameters for method 108 signature = signature.replaceAll(METHOD_PREFIX, "$4 $5("); 109 // remove all final modifiers for arguments 110 signature = signature.replaceAll("(\\(|,\\s*)final\\s+", "$1"); 111 // remove all redundant spaces in generic parameters 112 signature = signature.replaceAll("\\s*<\\s*", "<").replaceAll("\\s*>", ">"); 113 114 log.debug("Processing {}", signature); 115 116 final Matcher methodMatcher = METHOD_PATTERN.matcher(signature); 117 if (!methodMatcher.matches()) { 118 throw new IllegalArgumentException("Invalid method signature " + signature); 119 } 120 // handle generic methods with single bounded type parameters 121 String genericTypeParameterName = null; 122 String genericTypeParameterUpperBound = null; 123 String returnType = null; 124 try { 125 genericTypeParameterName = methodMatcher.group("genericTypeParameterName"); 126 genericTypeParameterUpperBound = methodMatcher.group("genericTypeParameterUpperBound"); 127 returnType = methodMatcher.group("returnType"); 128 if (returnType != null && returnType.equals(genericTypeParameterName)) { 129 returnType = genericTypeParameterUpperBound; 130 } 131 } catch (IllegalArgumentException e) { 132 // ignore 133 } 134 135 final Class<?> resultType = returnType != null ? forName(returnType) : forName(methodMatcher.group(1)); 136 final String name = methodMatcher.group(7); 137 final String argSignature = methodMatcher.group(8); 138 139 final List<ApiMethodArg> arguments = new ArrayList<>(); 140 final List<Class<?>> argTypes = new ArrayList<>(); 141 142 final Matcher argsMatcher = ARGS_PATTERN.matcher(argSignature); 143 while (argsMatcher.find()) { 144 String genericParameterName = argsMatcher.group(1); 145 if (genericTypeParameterName != null && genericTypeParameterName.equals(genericParameterName)) { 146 genericParameterName = genericTypeParameterUpperBound; 147 } 148 final Class<?> type = forName(genericParameterName); 149 argTypes.add(type); 150 String genericParameterUpperbound = argsMatcher.group(2); 151 String typeArgs = genericParameterUpperbound != null 152 ? genericParameterUpperbound.substring(1, genericParameterUpperbound.length() - 1).replaceAll(" ", "") : null; 153 if (typeArgs != null && typeArgs.equals(genericTypeParameterName)) { 154 typeArgs = genericTypeParameterUpperBound; 155 } 156 arguments.add(new ApiMethodArg(argsMatcher.group(3), type, typeArgs)); 157 } 158 159 Method method; 160 try { 161 method = proxyType.getMethod(name, argTypes.toArray(new Class<?>[argTypes.size()])); 162 } catch (NoSuchMethodException e) { 163 throw new IllegalArgumentException("Method not found [" + signature + "] in type " + proxyType.getName()); 164 } 165 result.add(new ApiMethodModel(name, resultType, arguments, method)); 166 } 167 168 // allow derived classes to post process 169 result = processResults(result); 170 171 // check that argument names have the same type across methods 172 Map<String, Class<?>> allArguments = new HashMap<>(); 173 for (ApiMethodModel model : result) { 174 for (ApiMethodArg argument : model.getArguments()) { 175 String name = argument.getName(); 176 Class<?> argClass = allArguments.get(name); 177 Class<?> type = argument.getType(); 178 if (argClass == null) { 179 allArguments.put(name, type); 180 } else { 181 if (argClass != type) { 182 throw new IllegalArgumentException("Argument [" + name 183 + "] is used in multiple methods with different types " 184 + argClass.getCanonicalName() + ", " + type.getCanonicalName()); 185 } 186 } 187 } 188 } 189 allArguments.clear(); 190 191 result.sort(new Comparator<ApiMethodModel>() { 192 @Override 193 public int compare(ApiMethodModel model1, ApiMethodModel model2) { 194 final int nameCompare = model1.name.compareTo(model2.name); 195 if (nameCompare != 0) { 196 return nameCompare; 197 } else { 198 199 final int nArgs1 = model1.arguments.size(); 200 final int nArgsCompare = nArgs1 - model2.arguments.size(); 201 if (nArgsCompare != 0) { 202 return nArgsCompare; 203 } else { 204 // same number of args, compare arg names, kinda arbitrary to use alphabetized order 205 for (int i = 0; i < nArgs1; i++) { 206 final int argCompare = model1.arguments.get(i).getName().compareTo(model2.arguments.get(i).getName()); 207 if (argCompare != 0) { 208 return argCompare; 209 } 210 } 211 // duplicate methods??? 212 log.warn("Duplicate methods found [{}], [{}]", model1, model2); 213 return 0; 214 } 215 } 216 } 217 }); 218 219 // assign unique names to every method model 220 final Map<String, Integer> dups = new HashMap<>(); 221 for (ApiMethodModel model : result) { 222 // locale independent upper case conversion 223 final String name = model.getName(); 224 final char[] upperCase = new char[name.length()]; 225 final char[] lowerCase = name.toCharArray(); 226 for (int i = 0; i < upperCase.length; i++) { 227 upperCase[i] = Character.toUpperCase(lowerCase[i]); 228 } 229 String uniqueName = new String(upperCase); 230 231 Integer suffix = dups.get(uniqueName); 232 if (suffix == null) { 233 dups.put(uniqueName, 1); 234 } else { 235 dups.put(uniqueName, suffix + 1); 236 StringBuilder builder = new StringBuilder(uniqueName); 237 builder.append("_").append(suffix); 238 uniqueName = builder.toString(); 239 } 240 model.uniqueName = uniqueName; 241 } 242 return result; 243 } 244 245 protected List<ApiMethodModel> processResults(List<ApiMethodModel> result) { 246 return result; 247 } 248 249 protected Class<?> forName(String className) { 250 try { 251 return forName(className, classLoader); 252 } catch (ClassNotFoundException e1) { 253 throw new IllegalArgumentException("Error loading class " + className); 254 } 255 } 256 257 public static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException { 258 Class<?> result = null; 259 try { 260 // lookup primitive types first 261 result = PRIMITIVE_TYPES.get(className); 262 if (result == null) { 263 result = Class.forName(className, true, classLoader); 264 } 265 } catch (ClassNotFoundException e) { 266 // check if array type 267 if (className.endsWith("[]")) { 268 final int firstDim = className.indexOf('['); 269 final int nDimensions = (className.length() - firstDim) / 2; 270 result = Array.newInstance(forName(className.substring(0, firstDim), classLoader), new int[nDimensions]).getClass(); 271 } else if (className.indexOf('.') != -1) { 272 // try replacing last '.' with $ to look for inner classes 273 String innerClass = className; 274 while (result == null && innerClass.indexOf('.') != -1) { 275 int endIndex = innerClass.lastIndexOf('.'); 276 innerClass = innerClass.substring(0, endIndex) + "$" + innerClass.substring(endIndex + 1); 277 try { 278 result = Class.forName(innerClass, true, classLoader); 279 } catch (ClassNotFoundException ignore) { 280 // ignore 281 } 282 } 283 } 284 if (result == null && !className.startsWith(JAVA_LANG)) { 285 // try loading from default Java package java.lang 286 try { 287 result = forName(JAVA_LANG + className, classLoader); 288 } catch (ClassNotFoundException ignore) { 289 // ignore 290 } 291 } 292 } 293 294 if (result == null) { 295 throw new ClassNotFoundException(className); 296 } 297 298 return result; 299 } 300 301 public static final class ApiMethodModel { 302 private final String name; 303 private final Class<?> resultType; 304 private final List<ApiMethodArg> arguments; 305 private final Method method; 306 307 private String uniqueName; 308 309 protected ApiMethodModel(String name, Class<?> resultType, List<ApiMethodArg> arguments, Method method) { 310 this.name = name; 311 this.resultType = resultType; 312 this.arguments = arguments; 313 this.method = method; 314 } 315 316 protected ApiMethodModel(String uniqueName, String name, Class<?> resultType, List<ApiMethodArg> arguments, Method method) { 317 this.name = name; 318 this.uniqueName = uniqueName; 319 this.resultType = resultType; 320 this.arguments = arguments; 321 this.method = method; 322 } 323 324 public String getUniqueName() { 325 return uniqueName; 326 } 327 328 public String getName() { 329 return name; 330 } 331 332 public Class<?> getResultType() { 333 return resultType; 334 } 335 336 public Method getMethod() { 337 return method; 338 } 339 340 public List<ApiMethodArg> getArguments() { 341 return arguments; 342 } 343 344 @Override 345 public String toString() { 346 StringBuilder builder = new StringBuilder(); 347 builder.append(resultType.getName()).append(" "); 348 builder.append(name).append("("); 349 for (ApiMethodArg argument : arguments) { 350 builder.append(argument.getType().getCanonicalName()).append(" "); 351 builder.append(argument.getName()).append(", "); 352 } 353 if (!arguments.isEmpty()) { 354 builder.delete(builder.length() - 2, builder.length()); 355 } 356 builder.append(");"); 357 return builder.toString(); 358 } 359 } 360}