Pierre De Rop | 6e8f921 | 2016-02-20 21:44:59 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Licensed to the Apache Software Foundation (ASF) under one |
| 3 | * or more contributor license agreements. See the NOTICE file |
| 4 | * distributed with this work for additional information |
| 5 | * regarding copyright ownership. The ASF licenses this file |
| 6 | * to you under the Apache License, Version 2.0 (the |
| 7 | * "License"); you may not use this file except in compliance |
| 8 | * with the License. You may obtain a copy of the License at |
| 9 | * |
| 10 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | * |
| 12 | * Unless required by applicable law or agreed to in writing, |
| 13 | * software distributed under the License is distributed on an |
| 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | * KIND, either express or implied. See the License for the |
| 16 | * specific language governing permissions and limitations |
| 17 | * under the License. |
| 18 | */ |
Pierre De Rop | faca289 | 2016-01-31 23:27:05 +0000 | [diff] [blame] | 19 | package org.apache.felix.dm.lambda.impl; |
| 20 | |
| 21 | import java.lang.invoke.SerializedLambda; |
| 22 | import java.lang.reflect.Method; |
| 23 | import java.lang.reflect.Parameter; |
| 24 | import java.lang.reflect.Proxy; |
| 25 | import java.util.ArrayList; |
| 26 | import java.util.List; |
| 27 | import java.util.Objects; |
| 28 | import java.util.regex.Matcher; |
| 29 | import java.util.regex.Pattern; |
| 30 | import java.util.stream.Stream; |
| 31 | |
| 32 | import org.apache.felix.dm.Component; |
Pierre De Rop | 603359a | 2016-02-28 19:11:09 +0000 | [diff] [blame] | 33 | import org.apache.felix.dm.context.ComponentContext; |
Pierre De Rop | faca289 | 2016-01-31 23:27:05 +0000 | [diff] [blame] | 34 | import org.apache.felix.dm.lambda.callbacks.SerializableLambda; |
Pierre De Rop | 603359a | 2016-02-28 19:11:09 +0000 | [diff] [blame] | 35 | import org.osgi.framework.BundleContext; |
Pierre De Rop | faca289 | 2016-01-31 23:27:05 +0000 | [diff] [blame] | 36 | |
| 37 | /** |
| 38 | * Various helper methods related to generics and lambda expressions. |
| 39 | */ |
| 40 | public class Helpers { |
| 41 | private final static Pattern LAMBDA_INSTANCE_METHOD_TYPE = Pattern.compile("(L[^;]+)+"); |
Pierre De Rop | 603359a | 2016-02-28 19:11:09 +0000 | [diff] [blame] | 42 | private final static String DEFAULT_REQUIRED_DEPENDENCY = "org.apache.felix.dependencymanager.lambda.defaultRequiredDependency"; |
| 43 | |
Pierre De Rop | faca289 | 2016-01-31 23:27:05 +0000 | [diff] [blame] | 44 | /** |
Pierre De Rop | faca289 | 2016-01-31 23:27:05 +0000 | [diff] [blame] | 45 | * Gets the class name of a given object. |
| 46 | * @param obj the object whose class has to be returned. |
| 47 | */ |
| 48 | public static Class<?> getClass(Object obj) { |
| 49 | Class<?> clazz = obj.getClass(); |
| 50 | if (Proxy.isProxyClass(clazz)) { |
| 51 | return Proxy.getProxyClass(clazz.getClassLoader(), clazz); |
| 52 | } |
| 53 | return clazz; |
| 54 | } |
| 55 | |
| 56 | /** |
| 57 | * Extracts the type of a given generic lambda parameter. |
| 58 | * Example: for "BiConsumer<String, Integer>", and with genericParamIndex=0, this method returns java.lang.String class. |
| 59 | * |
| 60 | * @param lambda a lambda expression, which must extends @link {@link SerializableLambda} interface. |
| 61 | * @param genericParamIndex the index of a given lambda generic parameter. |
| 62 | * @return the type of the lambda generic parameter that corresponds to the <code>genericParamIndex</code> |
| 63 | */ |
| 64 | @SuppressWarnings("unchecked") |
| 65 | public static <T> Class<T> getLambdaArgType(SerializableLambda lambda, int genericParamIndex) { |
| 66 | String[] lambdaParams = getGenericTypeStrings(lambda); |
| 67 | Class<?> clazz; |
| 68 | try { |
| 69 | clazz = lambda.getClass().getClassLoader().loadClass(lambdaParams[genericParamIndex]); |
| 70 | } catch (ClassNotFoundException e) { |
| 71 | throw new RuntimeException("Can't load class " + lambdaParams[genericParamIndex]); |
| 72 | } |
| 73 | return (Class<T>) clazz; |
| 74 | } |
| 75 | |
| 76 | /** |
| 77 | * Extracts the first parameter of a lambda. |
| 78 | */ |
| 79 | public static String getLambdaParameterName(SerializableLambda lambda, int index) { |
| 80 | SerializedLambda serialized = getSerializedLambda(lambda); |
| 81 | Method m = getLambdaMethod(serialized, lambda.getClass().getClassLoader()); |
| 82 | Parameter p = m.getParameters()[index]; |
| 83 | |
| 84 | if (Objects.equals("arg0", p.getName())) { |
| 85 | throw new IllegalStateException("Can'f find lambda method name (Please check you are using javac -parameters option)."); |
| 86 | } |
| 87 | return p.getName(); |
| 88 | } |
| 89 | |
| 90 | /** |
| 91 | * Returns the SerializedObject of a given lambda. |
| 92 | */ |
| 93 | private static SerializedLambda getSerializedLambda(SerializableLambda lambda) { |
| 94 | if (lambda == null) { |
| 95 | throw new IllegalArgumentException(); |
| 96 | } |
| 97 | |
| 98 | for (Class<?> clazz = lambda.getClass(); clazz != null; clazz = clazz.getSuperclass()) { |
| 99 | try { |
| 100 | Method replaceMethod = clazz.getDeclaredMethod("writeReplace"); |
| 101 | replaceMethod.setAccessible(true); |
| 102 | Object serializedForm = replaceMethod.invoke(lambda); |
| 103 | |
| 104 | if (serializedForm instanceof SerializedLambda) { |
| 105 | return (SerializedLambda) serializedForm; |
| 106 | } |
| 107 | } |
| 108 | catch (NoSuchMethodException e) { |
| 109 | // fall through the loop and try the next class |
| 110 | } |
| 111 | catch (Throwable t) { |
| 112 | throw new RuntimeException("Error while extracting serialized lambda", t); |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | throw new RuntimeException("writeReplace method not found"); |
| 117 | } |
| 118 | |
| 119 | /** |
| 120 | * Finds a composite |
| 121 | * @param component |
| 122 | * @param type |
| 123 | * @return |
| 124 | */ |
| 125 | @SuppressWarnings("unchecked") |
| 126 | public static <U> U findCompositeInstance(Component component, Class<U> type) { |
| 127 | U instance = (U) Stream.of(component.getInstances()) |
| 128 | .filter(inst -> Objects.equals(Helpers.getClass(inst), type)) |
| 129 | .findFirst() |
| 130 | .orElseThrow(() -> new RuntimeException("Did not find a component instance matching type " + type)); |
| 131 | return instance; |
| 132 | } |
| 133 | |
| 134 | /** |
Pierre De Rop | 603359a | 2016-02-28 19:11:09 +0000 | [diff] [blame] | 135 | * Is a dependency required by default ? |
| 136 | * |
| 137 | * @param c the component on which the dependency is added |
| 138 | * @param ctx the bundle context |
| 139 | * @return true if the dependency is required by default, false if not |
| 140 | */ |
| 141 | public static boolean isDependencyRequiredByDefault(Component c) { |
| 142 | BundleContext ctx = ((ComponentContext) c).getBundleContext(); |
| 143 | String defaultRequiredDependency = ctx.getProperty(DEFAULT_REQUIRED_DEPENDENCY); |
| 144 | if (defaultRequiredDependency != null) { |
| 145 | defaultRequiredDependency = defaultRequiredDependency.trim(); |
Pierre De Rop | 9ddcd3d | 2016-02-28 20:02:29 +0000 | [diff] [blame^] | 146 | String componentName = c.getComponentDeclaration().getClassName(); |
Pierre De Rop | 603359a | 2016-02-28 19:11:09 +0000 | [diff] [blame] | 147 | for (String pkg : defaultRequiredDependency.split(",")) { |
| 148 | if (componentName.startsWith(pkg)) { |
| 149 | return true; |
| 150 | } |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | return false; |
| 155 | } |
| 156 | |
| 157 | /** |
Pierre De Rop | faca289 | 2016-01-31 23:27:05 +0000 | [diff] [blame] | 158 | * Extracts the actual types of all lambda generic parameters. |
| 159 | * Example: for "BiConsumer<String, Integer>", this method returns ["java.lang.String", "java.lang.Integer"]. |
| 160 | */ |
| 161 | private static String[] getGenericTypeStrings(SerializableLambda lambda) { |
| 162 | // The only portable way to get the actual lambda generic parameters can be done using SerializedLambda. |
| 163 | SerializedLambda sl = getSerializedLambda(lambda); |
| 164 | String lambdaMethodType = sl.getInstantiatedMethodType(); |
| 165 | Matcher m = LAMBDA_INSTANCE_METHOD_TYPE.matcher(lambdaMethodType); |
| 166 | List<String> results = new ArrayList<>(); |
| 167 | while (m.find()) { |
| 168 | results.add(m.group().substring(1).replace("/", ".")); |
| 169 | } |
| 170 | return results.toArray(new String[0]); |
| 171 | } |
| 172 | |
| 173 | /** |
| 174 | * Extracts the actual java method from a given lambda. |
| 175 | */ |
| 176 | private static Method getLambdaMethod(SerializedLambda lambda, ClassLoader loader) { |
| 177 | String implClassName = lambda.getImplClass().replace('/', '.'); |
| 178 | Class<?> implClass; |
| 179 | try { |
| 180 | implClass = loader.loadClass(implClassName); |
| 181 | } catch (ClassNotFoundException e) { |
| 182 | throw new RuntimeException("Lambda Method not found (can not instantiate class " + implClassName); |
| 183 | } |
| 184 | |
| 185 | return Stream.of(implClass.getDeclaredMethods()) |
| 186 | .filter(method -> Objects.equals(method.getName(), lambda.getImplMethodName())) |
| 187 | .findFirst() |
| 188 | .orElseThrow(() -> new RuntimeException("Lambda Method not found")); |
| 189 | } |
Pierre De Rop | 603359a | 2016-02-28 19:11:09 +0000 | [diff] [blame] | 190 | |
Pierre De Rop | faca289 | 2016-01-31 23:27:05 +0000 | [diff] [blame] | 191 | } |