blob: db0dc5c405b4ae779c1904f8e44bdab2e9661a21 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.dm.lambda.impl;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.apache.felix.dm.Component;
import org.apache.felix.dm.context.ComponentContext;
import org.apache.felix.dm.lambda.callbacks.SerializableLambda;
import org.osgi.framework.BundleContext;
/**
* Various helper methods related to generics and lambda expressions.
*/
public class Helpers {
private final static Pattern LAMBDA_INSTANCE_METHOD_TYPE = Pattern.compile("(L[^;]+)+");
private final static String DEFAULT_REQUIRED_DEPENDENCY = "org.apache.felix.dependencymanager.lambda.defaultRequiredDependency";
/**
* Gets the class name of a given object.
* @param obj the object whose class has to be returned.
*/
public static Class<?> getClass(Object obj) {
Class<?> clazz = obj.getClass();
if (Proxy.isProxyClass(clazz)) {
return Proxy.getProxyClass(clazz.getClassLoader(), clazz);
}
return clazz;
}
/**
* Extracts the type of a given generic lambda parameter.
* Example: for "BiConsumer<String, Integer>", and with genericParamIndex=0, this method returns java.lang.String class.
*
* @param lambda a lambda expression, which must extends @link {@link SerializableLambda} interface.
* @param genericParamIndex the index of a given lambda generic parameter.
* @return the type of the lambda generic parameter that corresponds to the <code>genericParamIndex</code>
*/
@SuppressWarnings("unchecked")
public static <T> Class<T> getLambdaArgType(SerializableLambda lambda, int genericParamIndex) {
String[] lambdaParams = getGenericTypeStrings(lambda);
Class<?> clazz;
try {
clazz = lambda.getClass().getClassLoader().loadClass(lambdaParams[genericParamIndex]);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Can't load class " + lambdaParams[genericParamIndex]);
}
return (Class<T>) clazz;
}
/**
* Extracts the first parameter of a lambda.
*/
public static String getLambdaParameterName(SerializableLambda lambda, int index) {
SerializedLambda serialized = getSerializedLambda(lambda);
Method m = getLambdaMethod(serialized, lambda.getClass().getClassLoader());
Parameter p = m.getParameters()[index];
if (Objects.equals("arg0", p.getName())) {
throw new IllegalStateException("Can'f find lambda method name (Please check you are using javac -parameters option).");
}
return p.getName();
}
/**
* Returns the SerializedObject of a given lambda.
*/
private static SerializedLambda getSerializedLambda(SerializableLambda lambda) {
if (lambda == null) {
throw new IllegalArgumentException();
}
for (Class<?> clazz = lambda.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Method replaceMethod = clazz.getDeclaredMethod("writeReplace");
replaceMethod.setAccessible(true);
Object serializedForm = replaceMethod.invoke(lambda);
if (serializedForm instanceof SerializedLambda) {
return (SerializedLambda) serializedForm;
}
}
catch (NoSuchMethodException e) {
// fall through the loop and try the next class
}
catch (Throwable t) {
throw new RuntimeException("Error while extracting serialized lambda", t);
}
}
throw new RuntimeException("writeReplace method not found");
}
/**
* Finds a composite
* @param component
* @param type
* @return
*/
@SuppressWarnings("unchecked")
public static <U> U findCompositeInstance(Component component, Class<U> type) {
U instance = (U) Stream.of(component.getInstances())
.filter(inst -> Objects.equals(Helpers.getClass(inst), type))
.findFirst()
.orElseThrow(() -> new RuntimeException("Did not find a component instance matching type " + type));
return instance;
}
/**
* Is a dependency required by default ?
*
* @param c the component on which the dependency is added
* @param ctx the bundle context
* @return true if the dependency is required by default, false if not
*/
public static boolean isDependencyRequiredByDefault(Component c) {
BundleContext ctx = ((ComponentContext) c).getBundleContext();
String defaultRequiredDependency = ctx.getProperty(DEFAULT_REQUIRED_DEPENDENCY);
if (defaultRequiredDependency != null) {
defaultRequiredDependency = defaultRequiredDependency.trim();
String componentName = c.getComponentDeclaration().getClassName();
for (String pkg : defaultRequiredDependency.split(",")) {
if (componentName.startsWith(pkg)) {
return true;
}
}
}
return false;
}
/**
* Extracts the actual types of all lambda generic parameters.
* Example: for "BiConsumer<String, Integer>", this method returns ["java.lang.String", "java.lang.Integer"].
*/
private static String[] getGenericTypeStrings(SerializableLambda lambda) {
// The only portable way to get the actual lambda generic parameters can be done using SerializedLambda.
SerializedLambda sl = getSerializedLambda(lambda);
String lambdaMethodType = sl.getInstantiatedMethodType();
Matcher m = LAMBDA_INSTANCE_METHOD_TYPE.matcher(lambdaMethodType);
List<String> results = new ArrayList<>();
while (m.find()) {
results.add(m.group().substring(1).replace("/", "."));
}
return results.toArray(new String[0]);
}
/**
* Extracts the actual java method from a given lambda.
*/
private static Method getLambdaMethod(SerializedLambda lambda, ClassLoader loader) {
String implClassName = lambda.getImplClass().replace('/', '.');
Class<?> implClass;
try {
implClass = loader.loadClass(implClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Lambda Method not found (can not instantiate class " + implClassName);
}
return Stream.of(implClass.getDeclaredMethods())
.filter(method -> Objects.equals(method.getName(), lambda.getImplMethodName()))
.findFirst()
.orElseThrow(() -> new RuntimeException("Lambda Method not found"));
}
}