blob: e9e7806f312c57c1610ae7791840986b7e81a5a6 [file] [log] [blame]
// 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);
}
}