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<Argument> arguments = new ArrayList<Argument>(); 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 Argument(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 (Argument 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).name.compareTo(model2.arguments.get(i).name); 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; 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 return Array.newInstance(forName(className.substring(0, firstDim), classLoader), new int[nDimensions]).getClass(); 253 } 254 // try loading from default Java package java.lang 255 result = Class.forName(JAVA_LANG + className, true, classLoader); 256 } 257 258 return result; 259 } 260 261 public static final class ApiMethodModel { 262 private final String name; 263 private final Class<?> resultType; 264 private final List<Argument> arguments; 265 private final Method method; 266 267 private String uniqueName; 268 269 protected ApiMethodModel(String name, Class<?> resultType, List<Argument> arguments, Method method) { 270 this.name = name; 271 this.resultType = resultType; 272 this.arguments = arguments; 273 this.method = method; 274 } 275 276 protected ApiMethodModel(String uniqueName, String name, Class<?> resultType, List<Argument> arguments, Method method) { 277 this.name = name; 278 this.uniqueName = uniqueName; 279 this.resultType = resultType; 280 this.arguments = arguments; 281 this.method = method; 282 } 283 284 public String getUniqueName() { 285 return uniqueName; 286 } 287 288 public String getName() { 289 return name; 290 } 291 292 public Class<?> getResultType() { 293 return resultType; 294 } 295 296 public Method getMethod() { 297 return method; 298 } 299 300 public List<Argument> getArguments() { 301 return arguments; 302 } 303 304 @Override 305 public String toString() { 306 StringBuilder builder = new StringBuilder(); 307 builder.append(resultType.getName()).append(" "); 308 builder.append(name).append("("); 309 for (Argument argument : arguments) { 310 builder.append(argument.getType().getCanonicalName()).append(" "); 311 builder.append(argument.getName()).append(", "); 312 } 313 if (!arguments.isEmpty()) { 314 builder.delete(builder.length() - 2, builder.length()); 315 } 316 builder.append(");"); 317 return builder.toString(); 318 } 319 } 320 321 public static final class Argument { 322 private final String name; 323 private final Class<?> type; 324 private final String typeArgs; 325 326 public Argument(String name, Class<?> type, String typeArgs) { 327 this.name = name; 328 this.type = type; 329 this.typeArgs = typeArgs; 330 } 331 332 public String getName() { 333 return name; 334 } 335 336 public Class<?> getType() { 337 return type; 338 } 339 340 public String getTypeArgs() { 341 return typeArgs; 342 } 343 344 @Override 345 public String toString() { 346 StringBuilder builder = new StringBuilder(); 347 builder.append(type.getCanonicalName()); 348 if (typeArgs != null) { 349 builder.append("<").append(typeArgs).append(">"); 350 } 351 builder.append(" ").append(name); 352 return builder.toString(); 353 } 354 } 355}