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