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*(<[^>]+>)?\\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 // skip comment or empty lines 102 if (signature.startsWith("##") || ObjectHelper.isEmpty(signature)) { 103 continue; 104 } 105 106 // remove all modifiers and type parameters for method 107 signature = signature.replaceAll(METHOD_PREFIX, "$4 $5("); 108 // remove all final modifiers for arguments 109 signature = signature.replaceAll("(\\(|,\\s*)final\\s+", "$1"); 110 // remove all redundant spaces in generic parameters 111 signature = signature.replaceAll("\\s*<\\s*", "<").replaceAll("\\s*>", ">"); 112 113 log.debug("Processing " + signature); 114 115 final Matcher methodMatcher = METHOD_PATTERN.matcher(signature); 116 if (!methodMatcher.matches()) { 117 throw new IllegalArgumentException("Invalid method signature " + signature); 118 } 119 120 // ignore generic type parameters in result, if any 121 final Class<?> resultType = forName(methodMatcher.group(1)); 122 final String name = methodMatcher.group(3); 123 final String argSignature = methodMatcher.group(4); 124 125 final List<ApiMethodArg> arguments = new ArrayList<ApiMethodArg>(); 126 final List<Class<?>> argTypes = new ArrayList<Class<?>>(); 127 128 final Matcher argsMatcher = ARGS_PATTERN.matcher(argSignature); 129 while (argsMatcher.find()) { 130 131 final Class<?> type = forName(argsMatcher.group(1)); 132 argTypes.add(type); 133 134 final String typeArgsGroup = argsMatcher.group(2); 135 final String typeArgs = typeArgsGroup != null 136 ? typeArgsGroup.substring(1, typeArgsGroup.length() - 1).replaceAll(" ", "") : null; 137 arguments.add(new ApiMethodArg(argsMatcher.group(3), type, typeArgs)); 138 } 139 140 Method method; 141 try { 142 method = proxyType.getMethod(name, argTypes.toArray(new Class<?>[argTypes.size()])); 143 } catch (NoSuchMethodException e) { 144 throw new IllegalArgumentException("Method not found [" + signature + "] in type " + proxyType.getName()); 145 } 146 result.add(new ApiMethodModel(name, resultType, arguments, method)); 147 } 148 149 // allow derived classes to post process 150 result = processResults(result); 151 152 // check that argument names have the same type across methods 153 Map<String, Class<?>> allArguments = new HashMap<String, Class<?>>(); 154 for (ApiMethodModel model : result) { 155 for (ApiMethodArg argument : model.getArguments()) { 156 String name = argument.getName(); 157 Class<?> argClass = allArguments.get(name); 158 Class<?> type = argument.getType(); 159 if (argClass == null) { 160 allArguments.put(name, type); 161 } else { 162 if (argClass != type) { 163 throw new IllegalArgumentException("Argument [" + name 164 + "] is used in multiple methods with different types " 165 + argClass.getCanonicalName() + ", " + type.getCanonicalName()); 166 } 167 } 168 } 169 } 170 allArguments.clear(); 171 172 result.sort(new Comparator<ApiMethodModel>() { 173 @Override 174 public int compare(ApiMethodModel model1, ApiMethodModel model2) { 175 final int nameCompare = model1.name.compareTo(model2.name); 176 if (nameCompare != 0) { 177 return nameCompare; 178 } else { 179 180 final int nArgs1 = model1.arguments.size(); 181 final int nArgsCompare = nArgs1 - model2.arguments.size(); 182 if (nArgsCompare != 0) { 183 return nArgsCompare; 184 } else { 185 // same number of args, compare arg names, kinda arbitrary to use alphabetized order 186 for (int i = 0; i < nArgs1; i++) { 187 final int argCompare = model1.arguments.get(i).getName().compareTo(model2.arguments.get(i).getName()); 188 if (argCompare != 0) { 189 return argCompare; 190 } 191 } 192 // duplicate methods??? 193 log.warn("Duplicate methods found [" + model1 + "], [" + model2 + "]"); 194 return 0; 195 } 196 } 197 } 198 }); 199 200 // assign unique names to every method model 201 final Map<String, Integer> dups = new HashMap<String, Integer>(); 202 for (ApiMethodModel model : result) { 203 // locale independent upper case conversion 204 final String name = model.getName(); 205 final char[] upperCase = new char[name.length()]; 206 final char[] lowerCase = name.toCharArray(); 207 for (int i = 0; i < upperCase.length; i++) { 208 upperCase[i] = Character.toUpperCase(lowerCase[i]); 209 } 210 String uniqueName = new String(upperCase); 211 212 Integer suffix = dups.get(uniqueName); 213 if (suffix == null) { 214 dups.put(uniqueName, 1); 215 } else { 216 dups.put(uniqueName, suffix + 1); 217 StringBuilder builder = new StringBuilder(uniqueName); 218 builder.append("_").append(suffix); 219 uniqueName = builder.toString(); 220 } 221 model.uniqueName = uniqueName; 222 } 223 return result; 224 } 225 226 protected List<ApiMethodModel> processResults(List<ApiMethodModel> result) { 227 return result; 228 } 229 230 protected Class<?> forName(String className) { 231 try { 232 return forName(className, classLoader); 233 } catch (ClassNotFoundException e1) { 234 throw new IllegalArgumentException("Error loading class " + className); 235 } 236 } 237 238 public static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException { 239 Class<?> result = null; 240 try { 241 // lookup primitive types first 242 result = PRIMITIVE_TYPES.get(className); 243 if (result == null) { 244 result = Class.forName(className, true, classLoader); 245 } 246 } catch (ClassNotFoundException e) { 247 // check if array type 248 if (className.endsWith("[]")) { 249 final int firstDim = className.indexOf('['); 250 final int nDimensions = (className.length() - firstDim) / 2; 251 result = Array.newInstance(forName(className.substring(0, firstDim), classLoader), new int[nDimensions]).getClass(); 252 } else if (className.indexOf('.') != -1) { 253 // try replacing last '.' with $ to look for inner classes 254 String innerClass = className; 255 while (result == null && innerClass.indexOf('.') != -1) { 256 int endIndex = innerClass.lastIndexOf('.'); 257 innerClass = innerClass.substring(0, endIndex) + "$" + innerClass.substring(endIndex + 1); 258 try { 259 result = Class.forName(innerClass, true, classLoader); 260 } catch (ClassNotFoundException ignore) { 261 // ignore 262 } 263 } 264 } 265 if (result == null && !className.startsWith(JAVA_LANG)) { 266 // try loading from default Java package java.lang 267 try { 268 result = forName(JAVA_LANG + className, classLoader); 269 } catch (ClassNotFoundException ignore) { 270 // ignore 271 } 272 } 273 } 274 275 if (result == null) { 276 throw new ClassNotFoundException(className); 277 } 278 279 return result; 280 } 281 282 public static final class ApiMethodModel { 283 private final String name; 284 private final Class<?> resultType; 285 private final List<ApiMethodArg> arguments; 286 private final Method method; 287 288 private String uniqueName; 289 290 protected ApiMethodModel(String name, Class<?> resultType, List<ApiMethodArg> arguments, Method method) { 291 this.name = name; 292 this.resultType = resultType; 293 this.arguments = arguments; 294 this.method = method; 295 } 296 297 protected ApiMethodModel(String uniqueName, String name, Class<?> resultType, List<ApiMethodArg> arguments, Method method) { 298 this.name = name; 299 this.uniqueName = uniqueName; 300 this.resultType = resultType; 301 this.arguments = arguments; 302 this.method = method; 303 } 304 305 public String getUniqueName() { 306 return uniqueName; 307 } 308 309 public String getName() { 310 return name; 311 } 312 313 public Class<?> getResultType() { 314 return resultType; 315 } 316 317 public Method getMethod() { 318 return method; 319 } 320 321 public List<ApiMethodArg> getArguments() { 322 return arguments; 323 } 324 325 @Override 326 public String toString() { 327 StringBuilder builder = new StringBuilder(); 328 builder.append(resultType.getName()).append(" "); 329 builder.append(name).append("("); 330 for (ApiMethodArg argument : arguments) { 331 builder.append(argument.getType().getCanonicalName()).append(" "); 332 builder.append(argument.getName()).append(", "); 333 } 334 if (!arguments.isEmpty()) { 335 builder.delete(builder.length() - 2, builder.length()); 336 } 337 builder.append(");"); 338 return builder.toString(); 339 } 340 } 341}