| // Copyright 2006-2014 The Apache Software Foundation |
| // |
| // Licensed 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.tapestry5.ioc.internal.util; |
| |
| import org.apache.tapestry5.func.F; |
| import org.apache.tapestry5.func.Mapper; |
| import org.apache.tapestry5.func.Predicate; |
| import org.apache.tapestry5.internal.plastic.PlasticInternalUtils; |
| import org.apache.tapestry5.ioc.*; |
| import org.apache.tapestry5.ioc.annotations.*; |
| import org.apache.tapestry5.ioc.def.*; |
| import org.apache.tapestry5.ioc.internal.NullAnnotationProvider; |
| import org.apache.tapestry5.ioc.internal.ServiceDefImpl; |
| import org.apache.tapestry5.ioc.services.Coercion; |
| import org.apache.tapestry5.ioc.services.PlasticProxyFactory; |
| import org.apache.tapestry5.ioc.util.ExceptionUtils; |
| import org.apache.tapestry5.plastic.PlasticUtils; |
| import org.slf4j.Logger; |
| |
| import javax.annotation.PostConstruct; |
| import javax.inject.Named; |
| |
| import java.io.Closeable; |
| import java.io.IOException; |
| import java.lang.annotation.Annotation; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.lang.reflect.*; |
| import java.net.URL; |
| import java.util.*; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Utilities used within various internal implementations of the tapestry-ioc module. |
| */ |
| @SuppressWarnings("all") |
| public class InternalUtils |
| { |
| /** |
| * @since 5.2.2 |
| */ |
| public static final boolean SERVICE_CLASS_RELOADING_ENABLED = Boolean.parseBoolean(System.getProperty( |
| IOCConstants.SERVICE_CLASS_RELOADING_ENABLED, "true")); |
| |
| |
| /** |
| * Pattern used to eliminate leading and trailing underscores and dollar signs. |
| */ |
| private static final Pattern NAME_PATTERN = Pattern.compile("^[_|$]*([\\p{javaJavaIdentifierPart}]+?)[_|$]*$", |
| Pattern.CASE_INSENSITIVE); |
| |
| /** |
| * @since 5.3 |
| */ |
| public static AnnotationProvider NULL_ANNOTATION_PROVIDER = new NullAnnotationProvider(); |
| |
| /** |
| * Converts a method to a user presentable string using a {@link PlasticProxyFactory} to obtain a {@link Location} |
| * (where possible). {@link #asString(Method)} is used under the covers, to present a detailed, but not excessive, |
| * description of the class, method and parameters. |
| * |
| * @param method |
| * method to convert to a string |
| * @param proxyFactory |
| * used to obtain the {@link Location} |
| * @return the method formatted for presentation to the user |
| */ |
| public static String asString(Method method, PlasticProxyFactory proxyFactory) |
| { |
| Location location = proxyFactory.getMethodLocation(method); |
| |
| return location != null ? location.toString() : asString(method); |
| } |
| |
| /** |
| * Converts a method to a user presentable string consisting of the containing class name, the method name, and the |
| * short form of the parameter list (the class name of each parameter type, shorn of the package name portion). |
| * |
| * @param method |
| * @return short string representation |
| */ |
| public static String asString(Method method) |
| { |
| StringBuilder buffer = new StringBuilder(); |
| |
| buffer.append(method.getDeclaringClass().getName()); |
| buffer.append("."); |
| buffer.append(method.getName()); |
| buffer.append("("); |
| |
| for (int i = 0; i < method.getParameterTypes().length; i++) |
| { |
| if (i > 0) |
| buffer.append(", "); |
| |
| String name = method.getParameterTypes()[i].getSimpleName(); |
| |
| buffer.append(name); |
| } |
| |
| return buffer.append(")").toString(); |
| } |
| |
| /** |
| * Returns the size of an object array, or null if the array is empty. |
| */ |
| |
| public static int size(Object[] array) |
| { |
| return array == null ? 0 : array.length; |
| } |
| |
| public static int size(Collection collection) |
| { |
| return collection == null ? 0 : collection.size(); |
| } |
| |
| /** |
| * Strips leading "_" and "$" and trailing "_" from the name. |
| */ |
| public static String stripMemberName(String memberName) |
| { |
| assert InternalUtils.isNonBlank(memberName); |
| Matcher matcher = NAME_PATTERN.matcher(memberName); |
| |
| if (!matcher.matches()) |
| throw new IllegalArgumentException(String.format("Input '%s' is not a valid Java identifier.", memberName)); |
| |
| return matcher.group(1); |
| } |
| |
| /** |
| * Converts an enumeration (of Strings) into a sorted list of Strings. |
| */ |
| public static List<String> toList(Enumeration e) |
| { |
| List<String> result = CollectionFactory.newList(); |
| |
| while (e.hasMoreElements()) |
| { |
| String name = (String) e.nextElement(); |
| |
| result.add(name); |
| } |
| |
| Collections.sort(result); |
| |
| return result; |
| } |
| |
| /** |
| * Finds a specific annotation type within an array of annotations. |
| * |
| * @param <T> |
| * @param annotations |
| * to search |
| * @param annotationClass |
| * to match |
| * @return the annotation instance, if found, or null otherwise |
| */ |
| public static <T extends Annotation> T findAnnotation(Annotation[] annotations, Class<T> annotationClass) |
| { |
| for (Annotation a : annotations) |
| { |
| if (annotationClass.isInstance(a)) |
| return annotationClass.cast(a); |
| } |
| |
| return null; |
| } |
| |
| private static ObjectCreator<Object> asObjectCreator(final Object fixedValue) |
| { |
| return new ObjectCreator<Object>() |
| { |
| @Override |
| public Object createObject() |
| { |
| return fixedValue; |
| } |
| }; |
| } |
| |
| private static ObjectCreator calculateInjection(final Class injectionType, Type genericType, final Annotation[] annotations, |
| final ObjectLocator locator, InjectionResources resources) |
| { |
| final AnnotationProvider provider = new AnnotationProvider() |
| { |
| @Override |
| public <T extends Annotation> T getAnnotation(Class<T> annotationClass) |
| { |
| return findAnnotation(annotations, annotationClass); |
| } |
| }; |
| |
| // At some point, it would be nice to eliminate InjectService, and rely |
| // entirely on service interface type and point-of-injection markers. |
| |
| InjectService is = provider.getAnnotation(InjectService.class); |
| |
| if (is != null) |
| { |
| String serviceId = is.value(); |
| |
| return asObjectCreator(locator.getService(serviceId, injectionType)); |
| } |
| |
| Named named = provider.getAnnotation(Named.class); |
| |
| if (named != null) |
| { |
| return asObjectCreator(locator.getService(named.value(), injectionType)); |
| } |
| |
| // In the absence of @InjectService, try some autowiring. First, does the |
| // parameter type match one of the resources (the parameter defaults)? |
| |
| if (provider.getAnnotation(Inject.class) == null) |
| { |
| Object result = resources.findResource(injectionType, genericType); |
| |
| if (result != null) |
| { |
| return asObjectCreator(result); |
| } |
| } |
| |
| // TAP5-1765: For @Autobuild, special case where we always compute a fresh value |
| // for the injection on every use. Elsewhere, we compute once when generating the |
| // construction plan and just use the singleton value repeatedly. |
| |
| if (provider.getAnnotation(Autobuild.class) != null) |
| { |
| return new ObjectCreator() |
| { |
| @Override |
| public Object createObject() |
| { |
| return locator.getObject(injectionType, provider); |
| } |
| }; |
| } |
| |
| // Otherwise, make use of the MasterObjectProvider service to resolve this type (plus |
| // any other information gleaned from additional annotation) into the correct object. |
| |
| return asObjectCreator(locator.getObject(injectionType, provider)); |
| } |
| |
| public static ObjectCreator[] calculateParametersForMethod(Method method, ObjectLocator locator, |
| InjectionResources resources, OperationTracker tracker) |
| { |
| |
| return calculateParameters(locator, resources, method.getParameterTypes(), method.getGenericParameterTypes(), |
| method.getParameterAnnotations(), tracker); |
| } |
| |
| public static ObjectCreator[] calculateParameters(final ObjectLocator locator, final InjectionResources resources, |
| Class[] parameterTypes, final Type[] genericTypes, Annotation[][] parameterAnnotations, |
| OperationTracker tracker) |
| { |
| int parameterCount = parameterTypes.length; |
| |
| ObjectCreator[] parameters = new ObjectCreator[parameterCount]; |
| |
| for (int i = 0; i < parameterCount; i++) |
| { |
| final Class type = parameterTypes[i]; |
| final Type genericType = genericTypes[i]; |
| final Annotation[] annotations = parameterAnnotations[i]; |
| |
| String description = String.format("Determining injection value for parameter #%d (%s)", i + 1, |
| PlasticUtils.toTypeName(type)); |
| |
| final Invokable<ObjectCreator> operation = new Invokable<ObjectCreator>() |
| { |
| @Override |
| public ObjectCreator invoke() |
| { |
| return calculateInjection(type, genericType, annotations, locator, resources); |
| } |
| }; |
| |
| parameters[i] = tracker.invoke(description, operation); |
| } |
| |
| return parameters; |
| } |
| |
| /** |
| * Injects into the fields (of all visibilities) when the {@link org.apache.tapestry5.ioc.annotations.Inject} or |
| * {@link org.apache.tapestry5.ioc.annotations.InjectService} annotations are present. |
| * |
| * @param object |
| * to be initialized |
| * @param locator |
| * used to resolve external dependencies |
| * @param resources |
| * provides injection resources for fields |
| * @param tracker |
| * track operations |
| */ |
| public static void injectIntoFields(final Object object, final ObjectLocator locator, |
| final InjectionResources resources, OperationTracker tracker) |
| { |
| Class clazz = object.getClass(); |
| |
| while (clazz != Object.class) |
| { |
| Field[] fields = clazz.getDeclaredFields(); |
| |
| for (final Field f : fields) |
| { |
| // Ignore all static and final fields. |
| |
| int fieldModifiers = f.getModifiers(); |
| |
| if (Modifier.isStatic(fieldModifiers) || Modifier.isFinal(fieldModifiers)) |
| continue; |
| |
| final AnnotationProvider ap = new AnnotationProvider() |
| { |
| @Override |
| public <T extends Annotation> T getAnnotation(Class<T> annotationClass) |
| { |
| return f.getAnnotation(annotationClass); |
| } |
| }; |
| |
| String description = String.format("Calculating possible injection value for field %s.%s (%s)", |
| clazz.getName(), f.getName(), |
| PlasticUtils.toTypeName(f.getType())); |
| |
| tracker.run(description, new Runnable() |
| { |
| @Override |
| public void run() |
| { |
| final Class<?> fieldType = f.getType(); |
| |
| InjectService is = ap.getAnnotation(InjectService.class); |
| if (is != null) |
| { |
| inject(object, f, locator.getService(is.value(), fieldType)); |
| return; |
| } |
| |
| if (ap.getAnnotation(Inject.class) != null || ap.getAnnotation(InjectResource.class) != null) |
| { |
| Object value = resources.findResource(fieldType, f.getGenericType()); |
| |
| if (value != null) |
| { |
| inject(object, f, value); |
| return; |
| } |
| |
| inject(object, f, locator.getObject(fieldType, ap)); |
| return; |
| } |
| |
| if (ap.getAnnotation(javax.inject.Inject.class) != null) |
| { |
| Named named = ap.getAnnotation(Named.class); |
| |
| if (named == null) |
| { |
| Object value = resources.findResource(fieldType, f.getGenericType()); |
| |
| if (value != null) |
| { |
| inject(object, f, value); |
| return; |
| } |
| |
| inject(object, f, locator.getObject(fieldType, ap)); |
| } else |
| { |
| inject(object, f, locator.getService(named.value(), fieldType)); |
| } |
| |
| return; |
| } |
| |
| // Ignore fields that do not have the necessary annotation. |
| |
| } |
| }); |
| } |
| |
| clazz = clazz.getSuperclass(); |
| } |
| } |
| |
| private synchronized static void inject(Object target, Field field, Object value) |
| { |
| try |
| { |
| if (!field.isAccessible()) |
| field.setAccessible(true); |
| |
| field.set(target, value); |
| |
| // Is there a need to setAccessible back to false? |
| } catch (Exception ex) |
| { |
| throw new RuntimeException(String.format("Unable to set field '%s' of %s to %s: %s", field.getName(), |
| target, value, ExceptionUtils.toMessage(ex))); |
| } |
| } |
| |
| /** |
| * Joins together some number of elements to form a comma separated list. |
| */ |
| public static String join(List elements) |
| { |
| return join(elements, ", "); |
| } |
| |
| /** |
| * Joins together some number of elements. If a value in the list is the empty string, it is replaced with the |
| * string "(blank)". |
| * |
| * @param elements |
| * objects to be joined together |
| * @param separator |
| * used between elements when joining |
| */ |
| public static String join(List elements, String separator) |
| { |
| switch (elements.size()) |
| { |
| case 0: |
| return ""; |
| |
| case 1: |
| return elements.get(0).toString(); |
| |
| default: |
| |
| StringBuilder buffer = new StringBuilder(); |
| boolean first = true; |
| |
| for (Object o : elements) |
| { |
| if (!first) |
| buffer.append(separator); |
| |
| String string = String.valueOf(o); |
| |
| if (string.equals("")) |
| string = "(blank)"; |
| |
| buffer.append(string); |
| |
| first = false; |
| } |
| |
| return buffer.toString(); |
| } |
| } |
| |
| /** |
| * Creates a sorted copy of the provided elements, then turns that into a comma separated list. |
| * |
| * @return the elements converted to strings, sorted, joined with comma ... or "(none)" if the elements are null or |
| * empty |
| */ |
| public static String joinSorted(Collection elements) |
| { |
| if (elements == null || elements.isEmpty()) |
| return "(none)"; |
| |
| List<String> list = CollectionFactory.newList(); |
| |
| for (Object o : elements) |
| list.add(String.valueOf(o)); |
| |
| Collections.sort(list); |
| |
| return join(list); |
| } |
| |
| /** |
| * Returns true if the input is null, or is a zero length string (excluding leading/trailing whitespace). |
| */ |
| |
| public static boolean isBlank(String input) |
| { |
| return input == null || input.length() == 0 || input.trim().length() == 0; |
| } |
| |
| /** |
| * Returns true if the input is an empty collection. |
| */ |
| |
| public static boolean isEmptyCollection(Object input) |
| { |
| if (input instanceof Collection) |
| { |
| return ((Collection) input).isEmpty(); |
| } |
| |
| return false; |
| } |
| |
| public static boolean isNonBlank(String input) |
| { |
| return !isBlank(input); |
| } |
| |
| /** |
| * Capitalizes a string, converting the first character to uppercase. |
| */ |
| public static String capitalize(String input) |
| { |
| if (input.length() == 0) |
| return input; |
| |
| return input.substring(0, 1).toUpperCase() + input.substring(1); |
| } |
| |
| /** |
| * Sniffs the object to see if it is a {@link Location} or {@link Locatable}. Returns null if null or not |
| * convertable to a location. |
| */ |
| |
| public static Location locationOf(Object location) |
| { |
| if (location == null) |
| return null; |
| |
| if (location instanceof Location) |
| return (Location) location; |
| |
| if (location instanceof Locatable) |
| return ((Locatable) location).getLocation(); |
| |
| return null; |
| } |
| |
| /** |
| * Extracts the string keys from a map and returns them in sorted order. The keys are converted to strings. |
| * |
| * @param map |
| * the map to extract keys from (may be null) |
| * @return the sorted keys, or the empty set if map is null |
| */ |
| |
| public static List<String> sortedKeys(Map map) |
| { |
| if (map == null) |
| return Collections.emptyList(); |
| |
| List<String> keys = CollectionFactory.newList(); |
| |
| for (Object o : map.keySet()) |
| keys.add(String.valueOf(o)); |
| |
| Collections.sort(keys); |
| |
| return keys; |
| } |
| |
| public static <K, V> Set<K> keys(Map<K, V> map) |
| { |
| if (map == null) |
| return Collections.emptySet(); |
| |
| return map.keySet(); |
| } |
| |
| /** |
| * Gets a value from a map (which may be null). |
| * |
| * @param <K> |
| * @param <V> |
| * @param map |
| * the map to extract from (may be null) |
| * @param key |
| * @return the value from the map, or null if the map is null |
| */ |
| |
| public static <K, V> V get(Map<K, V> map, K key) |
| { |
| if (map == null) |
| return null; |
| |
| return map.get(key); |
| } |
| |
| /** |
| * Returns true if the method provided is a static method. |
| */ |
| public static boolean isStatic(Method method) |
| { |
| return Modifier.isStatic(method.getModifiers()); |
| } |
| |
| public static <T> Iterator<T> reverseIterator(final List<T> list) |
| { |
| final ListIterator<T> normal = list.listIterator(list.size()); |
| |
| return new Iterator<T>() |
| { |
| @Override |
| public boolean hasNext() |
| { |
| return normal.hasPrevious(); |
| } |
| |
| @Override |
| public T next() |
| { |
| return normal.previous(); |
| } |
| |
| @Override |
| public void remove() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| |
| /** |
| * Return true if the input string contains the marker for symbols that must be expanded. |
| */ |
| public static boolean containsSymbols(String input) |
| { |
| return input.contains("${"); |
| } |
| |
| /** |
| * Searches the string for the final period ('.') character and returns everything after that. The input string is |
| * generally a fully qualified class name, though tapestry-core also uses this method for the occasional property |
| * expression (which is also dot separated). Returns the input string unchanged if it does not contain a period |
| * character. |
| */ |
| public static String lastTerm(String input) |
| { |
| assert InternalUtils.isNonBlank(input); |
| int dotx = input.lastIndexOf('.'); |
| |
| if (dotx < 0) |
| return input; |
| |
| return input.substring(dotx + 1); |
| } |
| |
| /** |
| * Searches a class for the "best" constructor, the public constructor with the most parameters. Returns null if |
| * there are no public constructors. If there is more than one constructor with the maximum number of parameters, it |
| * is not determined which will be returned (don't build a class like that!). In addition, if a constructor is |
| * annotated with {@link org.apache.tapestry5.ioc.annotations.Inject}, it will be used (no check for multiple such |
| * constructors is made, only at most a single constructor should have the annotation). |
| * |
| * @param clazz |
| * to search for a constructor for |
| * @return the constructor to be used to instantiate the class, or null if no appropriate constructor was found |
| */ |
| public static Constructor findAutobuildConstructor(Class clazz) |
| { |
| Constructor[] constructors = clazz.getConstructors(); |
| |
| switch (constructors.length) |
| { |
| case 1: |
| |
| return constructors[0]; |
| |
| case 0: |
| |
| return null; |
| |
| default: |
| break; |
| } |
| |
| Constructor standardConstructor = findConstructorByAnnotation(constructors, Inject.class); |
| Constructor javaxConstructor = findConstructorByAnnotation(constructors, javax.inject.Inject.class); |
| |
| if (standardConstructor != null && javaxConstructor != null) |
| throw new IllegalArgumentException( |
| String.format( |
| "Too many autobuild constructors found: use either @%s or @%s annotation to mark a single constructor for autobuilding.", |
| Inject.class.getName(), javax.inject.Inject.class.getName())); |
| |
| if (standardConstructor != null) |
| { |
| return standardConstructor; |
| } |
| |
| if (javaxConstructor != null) |
| { |
| return javaxConstructor; |
| } |
| |
| // Choose a constructor with the most parameters. |
| |
| Comparator<Constructor> comparator = new Comparator<Constructor>() |
| { |
| @Override |
| public int compare(Constructor o1, Constructor o2) |
| { |
| return o2.getParameterTypes().length - o1.getParameterTypes().length; |
| } |
| }; |
| |
| Arrays.sort(constructors, comparator); |
| |
| return constructors[0]; |
| } |
| |
| private static <T extends Annotation> Constructor findConstructorByAnnotation(Constructor[] constructors, |
| Class<T> annotationClass) |
| { |
| for (Constructor c : constructors) |
| { |
| if (c.getAnnotation(annotationClass) != null) |
| return c; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Adds a value to a specially organized map where the values are lists of objects. This somewhat simulates a map |
| * that allows multiple values for the same key. |
| * |
| * @param map |
| * to store value into |
| * @param key |
| * for which a value is added |
| * @param value |
| * to add |
| * @param <K> |
| * the type of key |
| * @param <V> |
| * the type of the list |
| */ |
| public static <K, V> void addToMapList(Map<K, List<V>> map, K key, V value) |
| { |
| List<V> list = map.get(key); |
| |
| if (list == null) |
| { |
| list = CollectionFactory.newList(); |
| map.put(key, list); |
| } |
| |
| list.add(value); |
| } |
| |
| /** |
| * Validates that the marker annotation class had a retention policy of runtime. |
| * |
| * @param markerClass |
| * the marker annotation class |
| */ |
| public static void validateMarkerAnnotation(Class markerClass) |
| { |
| Retention policy = (Retention) markerClass.getAnnotation(Retention.class); |
| |
| if (policy != null && policy.value() == RetentionPolicy.RUNTIME) |
| return; |
| |
| throw new IllegalArgumentException(UtilMessages.badMarkerAnnotation(markerClass)); |
| } |
| |
| public static void validateMarkerAnnotations(Class[] markerClasses) |
| { |
| for (Class markerClass : markerClasses) |
| validateMarkerAnnotation(markerClass); |
| } |
| |
| public static void close(Closeable stream) |
| { |
| if (stream != null) |
| try |
| { |
| stream.close(); |
| } catch (IOException ex) |
| { |
| // Ignore. |
| } |
| } |
| |
| /** |
| * Extracts the message from an exception. If the exception's message is null, returns the exceptions class name. |
| * |
| * @param exception |
| * to extract message from |
| * @return message or class name |
| * @deprecated Deprecated in 5.4; use {@link ExceptionUtils#toMessage(Throwable)} instead. |
| */ |
| // Cause it gets used a lot outside of Tapestry proper even though it is internal. |
| public static String toMessage(Throwable exception) |
| { |
| return ExceptionUtils.toMessage(exception); |
| } |
| |
| public static void validateConstructorForAutobuild(Constructor constructor) |
| { |
| Class clazz = constructor.getDeclaringClass(); |
| |
| if (!Modifier.isPublic(clazz.getModifiers())) |
| throw new IllegalArgumentException(String.format( |
| "Class %s is not a public class and may not be autobuilt.", clazz.getName())); |
| |
| if (!Modifier.isPublic(constructor.getModifiers())) |
| throw new IllegalArgumentException( |
| String.format( |
| "Constructor %s is not public and may not be used for autobuilding an instance of the class. " |
| + "You should make the constructor public, or mark an alternate public constructor with the @Inject annotation.", |
| constructor)); |
| } |
| |
| /** |
| * @since 5.3 |
| */ |
| public static final Mapper<Class, AnnotationProvider> CLASS_TO_AP_MAPPER = new Mapper<Class, AnnotationProvider>() |
| { |
| @Override |
| public AnnotationProvider map(final Class element) |
| { |
| return toAnnotationProvider(element); |
| } |
| |
| }; |
| |
| /** |
| * @since 5.3 |
| */ |
| public static AnnotationProvider toAnnotationProvider(final Class element) |
| { |
| return new AnnotationProvider() |
| { |
| @Override |
| public <T extends Annotation> T getAnnotation(Class<T> annotationClass) |
| { |
| return annotationClass.cast(element.getAnnotation(annotationClass)); |
| } |
| }; |
| } |
| |
| ; |
| |
| /** |
| * @since 5.3 |
| */ |
| public static final Mapper<Method, AnnotationProvider> METHOD_TO_AP_MAPPER = new Mapper<Method, AnnotationProvider>() |
| { |
| @Override |
| public AnnotationProvider map(final Method element) |
| { |
| return toAnnotationProvider(element); |
| } |
| }; |
| |
| public static final Method findMethod(Class containingClass, String methodName, Class... parameterTypes) |
| { |
| if (containingClass == null) |
| return null; |
| |
| try |
| { |
| return containingClass.getMethod(methodName, parameterTypes); |
| } catch (SecurityException ex) |
| { |
| throw new RuntimeException(ex); |
| } catch (NoSuchMethodException ex) |
| { |
| return null; |
| } |
| } |
| |
| /** |
| * @since 5.3 |
| */ |
| public static ServiceDef3 toServiceDef3(ServiceDef sd) |
| { |
| if (sd instanceof ServiceDef3) |
| return (ServiceDef3) sd; |
| |
| final ServiceDef2 sd2 = toServiceDef2(sd); |
| |
| return new ServiceDef3() |
| { |
| // ServiceDef3 methods: |
| |
| @Override |
| public AnnotationProvider getClassAnnotationProvider() |
| { |
| return toAnnotationProvider(getServiceInterface()); |
| } |
| |
| @Override |
| public AnnotationProvider getMethodAnnotationProvider(final String methodName, final Class... argumentTypes) |
| { |
| return toAnnotationProvider(findMethod(getServiceInterface(), methodName, argumentTypes)); |
| } |
| |
| @Override |
| public Class getServiceImplementation() |
| { |
| return null; |
| } |
| |
| // ServiceDef2 methods: |
| |
| @Override |
| public boolean isPreventDecoration() |
| { |
| return sd2.isPreventDecoration(); |
| } |
| |
| @Override |
| public ObjectCreator createServiceCreator(ServiceBuilderResources resources) |
| { |
| return sd2.createServiceCreator(resources); |
| } |
| |
| @Override |
| public String getServiceId() |
| { |
| return sd2.getServiceId(); |
| } |
| |
| @Override |
| public Set<Class> getMarkers() |
| { |
| return sd2.getMarkers(); |
| } |
| |
| @Override |
| public Class getServiceInterface() |
| { |
| return sd2.getServiceInterface(); |
| } |
| |
| @Override |
| public String getServiceScope() |
| { |
| return sd2.getServiceScope(); |
| } |
| |
| @Override |
| public boolean isEagerLoad() |
| { |
| return sd2.isEagerLoad(); |
| } |
| |
| }; |
| } |
| |
| public static ServiceDef2 toServiceDef2(final ServiceDef sd) |
| { |
| if (sd instanceof ServiceDef2) |
| return (ServiceDef2) sd; |
| |
| return new ServiceDef2() |
| { |
| // ServiceDef2 methods: |
| |
| @Override |
| public boolean isPreventDecoration() |
| { |
| return false; |
| } |
| |
| // ServiceDef methods: |
| |
| @Override |
| public ObjectCreator createServiceCreator(ServiceBuilderResources resources) |
| { |
| return sd.createServiceCreator(resources); |
| } |
| |
| @Override |
| public String getServiceId() |
| { |
| return sd.getServiceId(); |
| } |
| |
| @Override |
| public Set<Class> getMarkers() |
| { |
| return sd.getMarkers(); |
| } |
| |
| @Override |
| public Class getServiceInterface() |
| { |
| return sd.getServiceInterface(); |
| } |
| |
| @Override |
| public String getServiceScope() |
| { |
| return sd.getServiceScope(); |
| } |
| |
| @Override |
| public boolean isEagerLoad() |
| { |
| return sd.isEagerLoad(); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return sd.toString(); |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + ((getServiceId() == null) ? 0 : getServiceId().hashCode()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) |
| { |
| if (this == obj) { return true; } |
| if (obj == null) { return false; } |
| if (!(obj instanceof ServiceDefImpl)) { return false; } |
| ServiceDef other = (ServiceDef) obj; |
| if (getServiceId() == null) |
| { |
| if (other.getServiceId() != null) { return false; } |
| } |
| else if (!getServiceId().equals(other.getServiceId())) { return false; } |
| return true; |
| } |
| |
| }; |
| } |
| |
| public static ModuleDef2 toModuleDef2(final ModuleDef md) |
| { |
| if (md instanceof ModuleDef2) |
| return (ModuleDef2) md; |
| |
| return new ModuleDef2() |
| { |
| @Override |
| public Set<AdvisorDef> getAdvisorDefs() |
| { |
| return Collections.emptySet(); |
| } |
| |
| @Override |
| public Class getBuilderClass() |
| { |
| return md.getBuilderClass(); |
| } |
| |
| @Override |
| public Set<ContributionDef> getContributionDefs() |
| { |
| return md.getContributionDefs(); |
| } |
| |
| @Override |
| public Set<DecoratorDef> getDecoratorDefs() |
| { |
| return md.getDecoratorDefs(); |
| } |
| |
| @Override |
| public String getLoggerName() |
| { |
| return md.getLoggerName(); |
| } |
| |
| @Override |
| public ServiceDef getServiceDef(String serviceId) |
| { |
| return md.getServiceDef(serviceId); |
| } |
| |
| @Override |
| public Set<String> getServiceIds() |
| { |
| return md.getServiceIds(); |
| } |
| |
| @Override |
| public Set<StartupDef> getStartups() |
| { |
| return Collections.emptySet(); |
| } |
| }; |
| } |
| |
| /** |
| * @since 5.1.0.2 |
| */ |
| public static ServiceLifecycle2 toServiceLifecycle2(final ServiceLifecycle lifecycle) |
| { |
| if (lifecycle instanceof ServiceLifecycle2) |
| return (ServiceLifecycle2) lifecycle; |
| |
| return new ServiceLifecycle2() |
| { |
| @Override |
| public boolean requiresProxy() |
| { |
| return true; |
| } |
| |
| @Override |
| public Object createService(ServiceResources resources, ObjectCreator creator) |
| { |
| return lifecycle.createService(resources, creator); |
| } |
| |
| @Override |
| public boolean isSingleton() |
| { |
| return lifecycle.isSingleton(); |
| } |
| }; |
| } |
| |
| /** |
| * @since 5.2.0 |
| */ |
| public static <T extends Comparable<T>> List<T> matchAndSort(Collection<? extends T> collection, |
| Predicate<T> predicate) |
| { |
| assert predicate != null; |
| |
| List<T> result = CollectionFactory.newList(); |
| |
| for (T object : collection) |
| { |
| if (predicate.accept(object)) |
| result.add(object); |
| } |
| |
| Collections.sort(result); |
| |
| return result; |
| } |
| |
| /** |
| * @since 5.2.0 |
| */ |
| public static ContributionDef2 toContributionDef2(final ContributionDef contribution) |
| { |
| if (contribution instanceof ContributionDef2) |
| return (ContributionDef2) contribution; |
| |
| return new ContributionDef2() |
| { |
| |
| @Override |
| public Set<Class> getMarkers() |
| { |
| return Collections.emptySet(); |
| } |
| |
| @Override |
| public Class getServiceInterface() |
| { |
| return null; |
| } |
| |
| @Override |
| public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, |
| Configuration configuration) |
| { |
| contribution.contribute(moduleSource, resources, configuration); |
| } |
| |
| @Override |
| public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, |
| OrderedConfiguration configuration) |
| { |
| contribution.contribute(moduleSource, resources, configuration); |
| } |
| |
| @Override |
| public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, |
| MappedConfiguration configuration) |
| { |
| contribution.contribute(moduleSource, resources, configuration); |
| } |
| |
| @Override |
| public String getServiceId() |
| { |
| return contribution.getServiceId(); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return contribution.toString(); |
| } |
| }; |
| } |
| |
| public static ContributionDef3 toContributionDef3(ContributionDef contribution) |
| { |
| |
| if (contribution instanceof ContributionDef2) |
| { |
| return (ContributionDef3) contribution; |
| } |
| |
| final ContributionDef2 cd2 = toContributionDef2(contribution); |
| |
| return new ContributionDef3() |
| { |
| @Override |
| public boolean isOptional() |
| { |
| return false; |
| } |
| |
| @Override |
| public String getServiceId() |
| { |
| return cd2.getServiceId(); |
| } |
| |
| @Override |
| public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, Configuration configuration) |
| { |
| cd2.contribute(moduleSource, resources, configuration); |
| } |
| |
| @Override |
| public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, OrderedConfiguration configuration) |
| { |
| cd2.contribute(moduleSource, resources, configuration); |
| } |
| |
| @Override |
| public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, MappedConfiguration configuration) |
| { |
| cd2.contribute(moduleSource, resources, configuration); |
| } |
| |
| @Override |
| public Set<Class> getMarkers() |
| { |
| return cd2.getMarkers(); |
| } |
| |
| @Override |
| public Class getServiceInterface() |
| { |
| return cd2.getServiceInterface(); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return cd2.toString(); |
| } |
| }; |
| } |
| |
| /** |
| * @since 5.2.2 |
| */ |
| public static AdvisorDef2 toAdvisorDef2(final AdvisorDef advisor) |
| { |
| if (advisor instanceof AdvisorDef2) |
| return (AdvisorDef2) advisor; |
| |
| return new AdvisorDef2() |
| { |
| |
| @Override |
| public ServiceAdvisor createAdvisor(ModuleBuilderSource moduleSource, ServiceResources resources) |
| { |
| return advisor.createAdvisor(moduleSource, resources); |
| } |
| |
| @Override |
| public String getAdvisorId() |
| { |
| return advisor.getAdvisorId(); |
| } |
| |
| @Override |
| public String[] getConstraints() |
| { |
| return advisor.getConstraints(); |
| } |
| |
| @Override |
| public boolean matches(ServiceDef serviceDef) |
| { |
| return advisor.matches(serviceDef); |
| } |
| |
| @Override |
| public Set<Class> getMarkers() |
| { |
| return Collections.emptySet(); |
| } |
| |
| @Override |
| public Class getServiceInterface() |
| { |
| return null; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return advisor.toString(); |
| } |
| }; |
| } |
| |
| /** |
| * @since 5.2.2 |
| */ |
| public static DecoratorDef2 toDecoratorDef2(final DecoratorDef decorator) |
| { |
| if (decorator instanceof DecoratorDef2) |
| return (DecoratorDef2) decorator; |
| |
| return new DecoratorDef2() |
| { |
| |
| @Override |
| public ServiceDecorator createDecorator(ModuleBuilderSource moduleSource, ServiceResources resources) |
| { |
| return decorator.createDecorator(moduleSource, resources); |
| } |
| |
| @Override |
| public String[] getConstraints() |
| { |
| return decorator.getConstraints(); |
| } |
| |
| @Override |
| public String getDecoratorId() |
| { |
| return decorator.getDecoratorId(); |
| } |
| |
| @Override |
| public boolean matches(ServiceDef serviceDef) |
| { |
| return decorator.matches(serviceDef); |
| } |
| |
| @Override |
| public Set<Class> getMarkers() |
| { |
| return Collections.emptySet(); |
| } |
| |
| @Override |
| public Class getServiceInterface() |
| { |
| return null; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return decorator.toString(); |
| } |
| }; |
| } |
| |
| /** |
| * Determines if the indicated class is stored as a locally accessible file |
| * (and not, typically, as a file inside a JAR). This is related to automatic |
| * reloading of services. |
| * |
| * @since 5.2.0 |
| */ |
| public static boolean isLocalFile(Class clazz) |
| { |
| String path = PlasticInternalUtils.toClassPath(clazz.getName()); |
| |
| ClassLoader loader = clazz.getClassLoader(); |
| |
| // System classes have no visible class loader, and are not local files. |
| |
| if (loader == null) |
| return false; |
| |
| URL classFileURL = loader.getResource(path); |
| |
| return classFileURL != null && classFileURL.getProtocol().equals("file"); |
| } |
| |
| /** |
| * Wraps a {@link Coercion} as a {@link Mapper}. |
| * |
| * @since 5.2.0 |
| */ |
| public static <S, T> Mapper<S, T> toMapper(final Coercion<S, T> coercion) |
| { |
| assert coercion != null; |
| |
| return new Mapper<S, T>() |
| { |
| @Override |
| public T map(S value) |
| { |
| return coercion.coerce(value); |
| } |
| }; |
| } |
| |
| private static final AtomicLong uuidGenerator = new AtomicLong(System.nanoTime()); |
| |
| /** |
| * Generates a unique value for the current execution of the application. This initial UUID value |
| * is not easily predictable; subsequent UUIDs are allocated in ascending series. |
| * |
| * @since 5.2.0 |
| */ |
| public static long nextUUID() |
| { |
| return uuidGenerator.incrementAndGet(); |
| } |
| |
| /** |
| * Extracts the service id from the passed annotated element. First the {@link ServiceId} annotation is checked. |
| * If present, its value is returned. Otherwise {@link Named} annotation is checked. If present, its value is |
| * returned. |
| * If neither of the annotations is present, <code>null</code> value is returned |
| * |
| * @param annotated |
| * annotated element to get annotations from |
| * @since 5.3 |
| */ |
| public static String getServiceId(AnnotatedElement annotated) |
| { |
| ServiceId serviceIdAnnotation = annotated.getAnnotation(ServiceId.class); |
| |
| if (serviceIdAnnotation != null) |
| { |
| return serviceIdAnnotation.value(); |
| } |
| |
| Named namedAnnotation = annotated.getAnnotation(Named.class); |
| |
| if (namedAnnotation != null) |
| { |
| String value = namedAnnotation.value(); |
| |
| if (InternalUtils.isNonBlank(value)) |
| { |
| return value; |
| } |
| } |
| |
| return null; |
| } |
| |
| |
| public static AnnotationProvider toAnnotationProvider(final Method element) |
| { |
| if (element == null) |
| return NULL_ANNOTATION_PROVIDER; |
| |
| return new AnnotationProvider() |
| { |
| @Override |
| public <T extends Annotation> T getAnnotation(Class<T> annotationClass) |
| { |
| return element.getAnnotation(annotationClass); |
| } |
| }; |
| } |
| |
| public static <T> ObjectCreator<T> createConstructorConstructionPlan(final OperationTracker tracker, final ObjectLocator locator, |
| final InjectionResources resources, |
| final Logger logger, |
| final String description, |
| final Constructor<T> constructor) |
| { |
| return tracker.invoke(String.format("Creating plan to instantiate %s via %s", |
| constructor.getDeclaringClass().getName(), |
| constructor), new Invokable<ObjectCreator<T>>() |
| { |
| @Override |
| public ObjectCreator<T> invoke() |
| { |
| validateConstructorForAutobuild(constructor); |
| |
| ObjectCreator[] constructorParameters = calculateParameters(locator, resources, constructor.getParameterTypes(), constructor.getGenericParameterTypes(), constructor.getParameterAnnotations(), tracker); |
| |
| Invokable<T> core = new ConstructorInvoker<T>(constructor, constructorParameters); |
| |
| Invokable<T> wrapped = logger == null ? core : new LoggingInvokableWrapper<T>(logger, description, core); |
| |
| ConstructionPlan<T> plan = new ConstructionPlan(tracker, description, wrapped); |
| |
| extendPlanForInjectedFields(plan, tracker, locator, resources, constructor.getDeclaringClass()); |
| |
| extendPlanForPostInjectionMethods(plan, tracker, locator, resources, constructor.getDeclaringClass()); |
| |
| return plan; |
| } |
| }); |
| } |
| |
| private static <T> void extendPlanForInjectedFields(final ConstructionPlan<T> plan, OperationTracker tracker, final ObjectLocator locator, final InjectionResources resources, Class<T> instantiatedClass) |
| { |
| Class clazz = instantiatedClass; |
| |
| while (clazz != Object.class) |
| { |
| Field[] fields = clazz.getDeclaredFields(); |
| |
| for (final Field f : fields) |
| { |
| // Ignore all static and final fields. |
| |
| int fieldModifiers = f.getModifiers(); |
| |
| if (Modifier.isStatic(fieldModifiers) || Modifier.isFinal(fieldModifiers)) |
| continue; |
| |
| final AnnotationProvider ap = new AnnotationProvider() |
| { |
| @Override |
| public <T extends Annotation> T getAnnotation(Class<T> annotationClass) |
| { |
| return f.getAnnotation(annotationClass); |
| } |
| }; |
| |
| String description = String.format("Calculating possible injection value for field %s.%s (%s)", |
| clazz.getName(), f.getName(), |
| PlasticUtils.toTypeName(f.getType())); |
| |
| tracker.run(description, new Runnable() |
| { |
| @Override |
| public void run() |
| { |
| final Class<?> fieldType = f.getType(); |
| |
| InjectService is = ap.getAnnotation(InjectService.class); |
| if (is != null) |
| { |
| addInjectPlan(plan, f, locator.getService(is.value(), fieldType)); |
| return; |
| } |
| |
| if (ap.getAnnotation(Inject.class) != null || ap.getAnnotation(InjectResource.class) != null) |
| { |
| Object value = resources.findResource(fieldType, f.getGenericType()); |
| |
| if (value != null) |
| { |
| addInjectPlan(plan, f, value); |
| return; |
| } |
| |
| addInjectPlan(plan, f, locator.getObject(fieldType, ap)); |
| return; |
| } |
| |
| if (ap.getAnnotation(javax.inject.Inject.class) != null) |
| { |
| Named named = ap.getAnnotation(Named.class); |
| |
| if (named == null) |
| { |
| addInjectPlan(plan, f, locator.getObject(fieldType, ap)); |
| } else |
| { |
| addInjectPlan(plan, f, locator.getService(named.value(), fieldType)); |
| } |
| |
| return; |
| } |
| |
| // Ignore fields that do not have the necessary annotation. |
| |
| } |
| }); |
| } |
| |
| clazz = clazz.getSuperclass(); |
| } |
| } |
| |
| private static <T> void addInjectPlan(ConstructionPlan<T> plan, final Field field, final Object injectedValue) |
| { |
| plan.add(new InitializationPlan<T>() |
| { |
| @Override |
| public String getDescription() |
| { |
| return String.format("Injecting %s into field %s of class %s.", |
| injectedValue, |
| field.getName(), |
| field.getDeclaringClass().getName()); |
| } |
| |
| @Override |
| public void initialize(T instance) |
| { |
| inject(instance, field, injectedValue); |
| } |
| }); |
| } |
| |
| private static boolean hasAnnotation(AccessibleObject member, Class<? extends Annotation> annotationType) |
| { |
| return member.getAnnotation(annotationType) != null; |
| } |
| |
| private static <T> void extendPlanForPostInjectionMethods(ConstructionPlan<T> plan, OperationTracker tracker, ObjectLocator locator, InjectionResources resources, Class<T> instantiatedClass) |
| { |
| for (Method m : instantiatedClass.getMethods()) |
| { |
| if (hasAnnotation(m, PostInjection.class) || hasAnnotation(m, PostConstruct.class)) |
| { |
| extendPlanForPostInjectionMethod(plan, tracker, locator, resources, m); |
| } |
| } |
| } |
| |
| private static void extendPlanForPostInjectionMethod(final ConstructionPlan<?> plan, final OperationTracker tracker, final ObjectLocator locator, final InjectionResources resources, final Method method) |
| { |
| tracker.run("Computing parameters for post-injection method " + method, |
| new Runnable() |
| { |
| @Override |
| public void run() |
| { |
| final ObjectCreator[] parameters = InternalUtils.calculateParametersForMethod(method, locator, |
| resources, tracker); |
| |
| plan.add(new InitializationPlan<Object>() |
| { |
| @Override |
| public String getDescription() |
| { |
| return "Invoking " + method; |
| } |
| |
| @Override |
| public void initialize(Object instance) |
| { |
| Throwable fail = null; |
| |
| Object[] realized = realizeObjects(parameters); |
| |
| try |
| { |
| method.invoke(instance, realized); |
| } catch (InvocationTargetException ex) |
| { |
| fail = ex.getTargetException(); |
| } catch (Exception ex) |
| { |
| fail = ex; |
| } |
| |
| if (fail != null) |
| { |
| throw new RuntimeException(String |
| .format("Exception invoking method %s: %s", method, ExceptionUtils.toMessage(fail)), fail); |
| } |
| } |
| }); |
| } |
| }); |
| } |
| |
| |
| public static <T> ObjectCreator<T> createMethodInvocationPlan(final OperationTracker tracker, final ObjectLocator locator, |
| final InjectionResources resources, |
| final Logger logger, |
| final String description, |
| final Object instance, |
| final Method method) |
| { |
| |
| return tracker.invoke("Creating plan to invoke " + method, new Invokable<ObjectCreator<T>>() |
| { |
| @Override |
| public ObjectCreator<T> invoke() |
| { |
| ObjectCreator[] methodParameters = calculateParametersForMethod(method, locator, resources, tracker); |
| |
| Invokable<T> core = new MethodInvoker<T>(instance, method, methodParameters); |
| |
| Invokable<T> wrapped = logger == null ? core : new LoggingInvokableWrapper<T>(logger, description, core); |
| |
| return new ConstructionPlan(tracker, description, wrapped); |
| } |
| }); |
| } |
| |
| /** |
| * @since 5.3.1, 5.4 |
| */ |
| public static Mapper<ObjectCreator, Object> CREATE_OBJECT = new Mapper<ObjectCreator, Object>() |
| { |
| @Override |
| public Object map(ObjectCreator element) |
| { |
| return element.createObject(); |
| } |
| }; |
| |
| /** |
| * @since 5.3.1, 5.4 |
| */ |
| public static Object[] realizeObjects(ObjectCreator[] creators) |
| { |
| return F.flow(creators).map(CREATE_OBJECT).toArray(Object.class); |
| } |
| } |