blob: db0dc5c405b4ae779c1904f8e44bdab2e9661a21 [file] [log] [blame]
Pierre De Rop6e8f9212016-02-20 21:44:59 +00001/*
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 Ropfaca2892016-01-31 23:27:05 +000019package org.apache.felix.dm.lambda.impl;
20
21import java.lang.invoke.SerializedLambda;
22import java.lang.reflect.Method;
23import java.lang.reflect.Parameter;
24import java.lang.reflect.Proxy;
25import java.util.ArrayList;
26import java.util.List;
27import java.util.Objects;
28import java.util.regex.Matcher;
29import java.util.regex.Pattern;
30import java.util.stream.Stream;
31
32import org.apache.felix.dm.Component;
Pierre De Rop603359a2016-02-28 19:11:09 +000033import org.apache.felix.dm.context.ComponentContext;
Pierre De Ropfaca2892016-01-31 23:27:05 +000034import org.apache.felix.dm.lambda.callbacks.SerializableLambda;
Pierre De Rop603359a2016-02-28 19:11:09 +000035import org.osgi.framework.BundleContext;
Pierre De Ropfaca2892016-01-31 23:27:05 +000036
37/**
38 * Various helper methods related to generics and lambda expressions.
39 */
40public class Helpers {
41 private final static Pattern LAMBDA_INSTANCE_METHOD_TYPE = Pattern.compile("(L[^;]+)+");
Pierre De Rop603359a2016-02-28 19:11:09 +000042 private final static String DEFAULT_REQUIRED_DEPENDENCY = "org.apache.felix.dependencymanager.lambda.defaultRequiredDependency";
43
Pierre De Ropfaca2892016-01-31 23:27:05 +000044 /**
Pierre De Ropfaca2892016-01-31 23:27:05 +000045 * 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 Rop603359a2016-02-28 19:11:09 +0000135 * 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 Rop9ddcd3d2016-02-28 20:02:29 +0000146 String componentName = c.getComponentDeclaration().getClassName();
Pierre De Rop603359a2016-02-28 19:11:09 +0000147 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 Ropfaca2892016-01-31 23:27:05 +0000158 * 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 Rop603359a2016-02-28 19:11:09 +0000190
Pierre De Ropfaca2892016-01-31 23:27:05 +0000191}