/* * 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")); } }