Skip to content

Commit

Permalink
CAMEL-10106: [api-component-framework] Reduce object allocation in Ap…
Browse files Browse the repository at this point in the history
…iMethodHelper, ApiMethodImpl
  • Loading branch information
lburgazzoli committed Jul 21, 2016
1 parent 90ac673 commit aac2662
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 36 deletions.
Expand Up @@ -28,7 +28,6 @@
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;


import org.apache.camel.RuntimeCamelException; import org.apache.camel.RuntimeCamelException;
import org.slf4j.Logger; import org.slf4j.Logger;
Expand All @@ -42,16 +41,18 @@ public final class ApiMethodHelper<T extends Enum<T> & ApiMethod> {
private static final Logger LOG = LoggerFactory.getLogger(ApiMethodHelper.class); private static final Logger LOG = LoggerFactory.getLogger(ApiMethodHelper.class);


// maps method name to ApiMethod // maps method name to ApiMethod
private final Map<String, List<T>> methodMap = new HashMap<String, List<T>>(); private final Map<String, List<T>> methodMap;


// maps method name to method arguments of the form Class type1, String name1, Class type2, String name2,... // maps method name to method arguments of the form Class type1, String name1, Class type2, String name2,...
private final Map<String, List<Object>> argumentsMap = new HashMap<String, List<Object>>(); private final Map<String, List<Object>> argumentsMap;


// maps argument name to argument type // maps argument name to argument type
private final Map<String, Class<?>> validArguments = new HashMap<String, Class<?>>(); private final Map<String, Class<?>> validArguments;


// maps aliases to actual method names // maps aliases to actual method names
private final HashMap<String, Set<String>> aliasesMap = new HashMap<String, Set<String>>(); private final Map<String, Set<String>> aliasesMap;

// nullable args
private final List<String> nullableArguments; private final List<String> nullableArguments;


/** /**
Expand All @@ -62,6 +63,11 @@ public final class ApiMethodHelper<T extends Enum<T> & ApiMethod> {
*/ */
public ApiMethodHelper(Class<T> apiMethodEnum, Map<String, String> aliases, List<String> nullableArguments) { public ApiMethodHelper(Class<T> apiMethodEnum, Map<String, String> aliases, List<String> nullableArguments) {


Map<String, List<T>> tmpMethodMap = new HashMap<>();
Map<String, List<Object>> tmpArgumentsMap = new HashMap<>();
Map<String, Class<?>> tmpValidArguments = new HashMap<>();
Map<String, Set<String>> tmpAliasesMap = new HashMap<>();

// validate ApiMethod Enum // validate ApiMethod Enum
if (apiMethodEnum == null) { if (apiMethodEnum == null) {
throw new IllegalArgumentException("ApiMethod enumeration cannot be null"); throw new IllegalArgumentException("ApiMethod enumeration cannot be null");
Expand Down Expand Up @@ -103,28 +109,28 @@ public ApiMethodHelper(Class<T> apiMethodEnum, Map<String, String> aliases, List
builder.append(Character.toLowerCase(firstChar)).append(alias.substring(1)); builder.append(Character.toLowerCase(firstChar)).append(alias.substring(1));
alias = builder.toString(); alias = builder.toString();
} }
Set<String> names = aliasesMap.get(alias); Set<String> names = tmpAliasesMap.get(alias);
if (names == null) { if (names == null) {
names = new HashSet<String>(); names = new HashSet<String>();
aliasesMap.put(alias, names); tmpAliasesMap.put(alias, names);
} }
names.add(name); names.add(name);
} }
} }


// map method name to Enum // map method name to Enum
List<T> overloads = methodMap.get(name); List<T> overloads = tmpMethodMap.get(name);
if (overloads == null) { if (overloads == null) {
overloads = new ArrayList<T>(); overloads = new ArrayList<T>();
methodMap.put(method.getName(), overloads); tmpMethodMap.put(method.getName(), overloads);
} }
overloads.add(method); overloads.add(method);


// add arguments for this method // add arguments for this method
List<Object> arguments = argumentsMap.get(name); List<Object> arguments = tmpArgumentsMap.get(name);
if (arguments == null) { if (arguments == null) {
arguments = new ArrayList<Object>(); arguments = new ArrayList<Object>();
argumentsMap.put(name, arguments); tmpArgumentsMap.put(name, arguments);
} }


// process all arguments for this method // process all arguments for this method
Expand All @@ -140,27 +146,27 @@ public ApiMethodHelper(Class<T> apiMethodEnum, Map<String, String> aliases, List
} }


// also collect argument names for all methods, and detect clashes here // also collect argument names for all methods, and detect clashes here
final Class<?> previousType = validArguments.get(argName); final Class<?> previousType = tmpValidArguments.get(argName);
if (previousType != null && previousType != argType) { if (previousType != null && previousType != argType) {
throw new IllegalArgumentException(String.format( throw new IllegalArgumentException(String.format(
"Argument %s has ambiguous types (%s, %s) across methods!", "Argument %s has ambiguous types (%s, %s) across methods!",
name, previousType, argType)); name, previousType, argType));
} else if (previousType == null) { } else if (previousType == null) {
validArguments.put(argName, argType); tmpValidArguments.put(argName, argType);
} }
} }


} }


// validate nullableArguments // validate nullableArguments
if (!validArguments.keySet().containsAll(this.nullableArguments)) { if (!tmpValidArguments.keySet().containsAll(this.nullableArguments)) {
List<String> unknowns = new ArrayList<String>(this.nullableArguments); List<String> unknowns = new ArrayList<String>(this.nullableArguments);
unknowns.removeAll(validArguments.keySet()); unknowns.removeAll(tmpValidArguments.keySet());
throw new IllegalArgumentException("Unknown nullable arguments " + unknowns.toString()); throw new IllegalArgumentException("Unknown nullable arguments " + unknowns.toString());
} }


// validate aliases // validate aliases
for (Map.Entry<String, Set<String>> entry : aliasesMap.entrySet()) { for (Map.Entry<String, Set<String>> entry : tmpAliasesMap.entrySet()) {


// look for aliases that match multiple methods // look for aliases that match multiple methods
final Set<String> methodNames = entry.getValue(); final Set<String> methodNames = entry.getValue();
Expand All @@ -169,7 +175,7 @@ public ApiMethodHelper(Class<T> apiMethodEnum, Map<String, String> aliases, List
// get mapped methods // get mapped methods
final List<T> aliasedMethods = new ArrayList<T>(); final List<T> aliasedMethods = new ArrayList<T>();
for (String methodName : methodNames) { for (String methodName : methodNames) {
List<T> mappedMethods = methodMap.get(methodName); List<T> mappedMethods = tmpMethodMap.get(methodName);
aliasedMethods.addAll(mappedMethods); aliasedMethods.addAll(mappedMethods);
} }


Expand Down Expand Up @@ -199,7 +205,12 @@ public ApiMethodHelper(Class<T> apiMethodEnum, Map<String, String> aliases, List
} }
} }


LOG.debug("Found {} unique method names in {} methods", methodMap.size(), methods.length); this.methodMap = Collections.unmodifiableMap(tmpMethodMap);
this.argumentsMap = Collections.unmodifiableMap(tmpArgumentsMap);
this.validArguments = Collections.unmodifiableMap(tmpValidArguments);
this.aliasesMap = Collections.unmodifiableMap(tmpAliasesMap);

LOG.debug("Found {} unique method names in {} methods", tmpMethodMap.size(), methods.length);
} }


/** /**
Expand All @@ -225,10 +236,10 @@ public List<ApiMethod> getCandidateMethods(String name, Collection<String> argNa
List<T> methods = methodMap.get(name); List<T> methods = methodMap.get(name);
if (methods == null) { if (methods == null) {
if (aliasesMap.containsKey(name)) { if (aliasesMap.containsKey(name)) {
methods = aliasesMap.get(name) methods = new ArrayList<T>();
.stream() for (String method : aliasesMap.get(name)) {
.map(method -> (T)methodMap.get(method)) methods.addAll(methodMap.get(method));
.collect(Collectors.toList()); }
} }
} }
if (methods == null) { if (methods == null) {
Expand Down Expand Up @@ -282,9 +293,9 @@ public List<ApiMethod> filterMethods(List<? extends ApiMethod> methods, MatchTyp
} }


// list of methods that have all args in the given names // list of methods that have all args in the given names
final List<ApiMethod> result = new ArrayList<ApiMethod>(); List<ApiMethod> result = new ArrayList<>();
final List<ApiMethod> extraArgs = new ArrayList<ApiMethod>(); List<ApiMethod> extraArgs = null;
final List<ApiMethod> nullArgs = new ArrayList<ApiMethod>(); List<ApiMethod> nullArgs = null;


for (ApiMethod method : methods) { for (ApiMethod method : methods) {
final List<String> methodArgs = method.getArgNames(); final List<String> methodArgs = method.getArgNames();
Expand All @@ -308,22 +319,35 @@ public List<ApiMethod> filterMethods(List<? extends ApiMethod> methods, MatchTyp
if (methodArgs.containsAll(argNames)) { if (methodArgs.containsAll(argNames)) {
// prefer exact match to avoid unused args // prefer exact match to avoid unused args
result.add(method); result.add(method);
} else { } else if (result.isEmpty()) {
// if result is empty, add method to extra args list
if (extraArgs == null) {
extraArgs = new ArrayList<>();
}
// method takes a subset, unused args // method takes a subset, unused args
extraArgs.add(method); extraArgs.add(method);
} }
} else if (result.isEmpty() && extraArgs.isEmpty()) { } else if (result.isEmpty() && extraArgs != null && extraArgs.isEmpty()) {
// avoid looking for nullable args by checking for empty result and extraArgs // avoid looking for nullable args by checking for empty result and extraArgs
if (withNullableArgsList != null && withNullableArgsList.containsAll(methodArgs)) { if (withNullableArgsList != null && withNullableArgsList.containsAll(methodArgs)) {
if (nullArgs == null) {
nullArgs = new ArrayList<>();
}
nullArgs.add(method); nullArgs.add(method);
} }
} }
break; break;
} }
} }


List<ApiMethod> methodList = result.isEmpty()
? (extraArgs != null && extraArgs.isEmpty())
? nullArgs
: extraArgs
: result;

// preference order is exact match, matches with extra args, matches with null args // preference order is exact match, matches with extra args, matches with null args
return Collections.unmodifiableList(result.isEmpty() ? (extraArgs.isEmpty() ? nullArgs : extraArgs) : result); return methodList != null ? Collections.unmodifiableList(methodList) : Collections.emptyList();
} }


/** /**
Expand Down Expand Up @@ -372,15 +396,15 @@ public Set<String> getMissingProperties(String methodName, Set<String> argNames)
* @return alias names mapped to method names. * @return alias names mapped to method names.
*/ */
public Map<String, Set<String>> getAliases() { public Map<String, Set<String>> getAliases() {
return Collections.unmodifiableMap(aliasesMap); return aliasesMap;
} }


/** /**
* Returns argument types and names used by all methods. * Returns argument types and names used by all methods.
* @return map with argument names as keys, and types as values * @return map with argument names as keys, and types as values
*/ */
public Map<String, Class<?>> allArguments() { public Map<String, Class<?>> allArguments() {
return Collections.unmodifiableMap(validArguments); return validArguments;
} }


/** /**
Expand Down
Expand Up @@ -65,14 +65,18 @@ public ApiMethodImpl(Class<?> proxyType, Class<?> resultType, String name, Objec
throw new IllegalArgumentException("Invalid parameter list, " throw new IllegalArgumentException("Invalid parameter list, "
+ "must be of the form 'Class arg1, String arg1Name, Class arg2, String arg2Name..."); + "must be of the form 'Class arg1, String arg1Name, Class arg2, String arg2Name...");
} }

int nArgs = args.length / 2; int nArgs = args.length / 2;
this.argNames = new ArrayList<String>(nArgs); final List<String> tmpArgNames = new ArrayList<>(nArgs);
this.argTypes = new ArrayList<Class<?>>(nArgs); final List<Class<?>> tmpArgTypes = new ArrayList<>(nArgs);
for (int i = 0; i < nArgs; i++) { for (int i = 0; i < nArgs; i++) {
this.argTypes.add((Class<?>) args[i * 2]); tmpArgTypes.add((Class<?>) args[i * 2]);
this.argNames.add((String) args[i * 2 + 1]); tmpArgNames.add((String) args[i * 2 + 1]);
} }


this.argNames = Collections.unmodifiableList(tmpArgNames);
this.argTypes = Collections.unmodifiableList(tmpArgTypes);

// find method in Proxy type // find method in Proxy type
try { try {
this.method = proxyType.getMethod(name, argTypes.toArray(new Class[nArgs])); this.method = proxyType.getMethod(name, argTypes.toArray(new Class[nArgs]));
Expand All @@ -95,12 +99,12 @@ public Class<?> getResultType() {


@Override @Override
public List<String> getArgNames() { public List<String> getArgNames() {
return Collections.unmodifiableList(argNames); return argNames;
} }


@Override @Override
public List<Class<?>> getArgTypes() { public List<Class<?>> getArgTypes() {
return Collections.unmodifiableList(argTypes); return argTypes;
} }


@Override @Override
Expand All @@ -117,6 +121,7 @@ public String toString() {
.append(", argNames=").append(argNames) .append(", argNames=").append(argNames)
.append(", argTypes=").append(argTypes) .append(", argTypes=").append(argTypes)
.append("}"); .append("}");

return builder.toString(); return builder.toString();
} }
} }

0 comments on commit aac2662

Please sign in to comment.