org.glassfish.hk2.utilities.BuilderHelper Maven / Gradle / Ivy
/*
* Copyright (c) 2007, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.hk2.utilities;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.glassfish.hk2.api.ActiveDescriptor;
import org.glassfish.hk2.api.Descriptor;
import org.glassfish.hk2.api.DescriptorType;
import org.glassfish.hk2.api.DescriptorVisibility;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.Filter;
import org.glassfish.hk2.api.IndexedFilter;
import org.glassfish.hk2.api.Metadata;
import org.glassfish.hk2.api.MultiException;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.api.ProxyForSameScope;
import org.glassfish.hk2.api.Rank;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.UseProxy;
import org.glassfish.hk2.api.Visibility;
import org.glassfish.hk2.internal.ActiveDescriptorBuilderImpl;
import org.glassfish.hk2.internal.ConstantActiveDescriptor;
import org.glassfish.hk2.internal.DescriptorBuilderImpl;
import org.glassfish.hk2.internal.IndexedFilterImpl;
import org.glassfish.hk2.internal.SpecificFilterImpl;
import org.glassfish.hk2.internal.StarFilter;
import org.glassfish.hk2.utilities.reflection.ReflectionHelper;
import org.jvnet.hk2.annotations.Contract;
import org.jvnet.hk2.annotations.ContractsProvided;
import org.jvnet.hk2.annotations.Service;
/**
* This class is used to generate DescriptorBuilders to be used
* as a simple mechanism to create a Filter or Descriptor.
*/
public class BuilderHelper {
/**
* Returns an indexed filter that will return all descriptors that
* have contract as an advertised contract
*
* @param contract The advertised contract to look for
* @return The indexed filter that can be used to calls to ServiceLocator methods
*/
public static IndexedFilter createContractFilter(String contract) {
return new IndexedFilterImpl(contract, null);
}
/**
* Returns an indexed filter that will return all descriptors that
* have the given name
*
* @param name The name to look for
* @return The indexed filter that can be used to calls to ServiceLocator methods
*/
public static IndexedFilter createNameFilter(String name) {
return new IndexedFilterImpl(null, name);
}
/**
* Returns an indexed filter that will return all descriptors that
* have the given name and given contract
*
* @param contract The advertised contract to look for
* @param name The name to look for
* @return The indexed filter that can be used to calls to ServiceLocator methods
*/
public static IndexedFilter createNameAndContractFilter(String contract, String name) {
return new IndexedFilterImpl(contract, name);
}
/** The key for the name field of the tokenized string */
public final static String NAME_KEY = "name";
/** The key for the qualifier field of the tokenized string */
public final static String QUALIFIER_KEY = "qualifier";
/** The token separator */
public final static String TOKEN_SEPARATOR = ";";
/**
* Creates a filter from a token string as per the following rules.
* - The token delimiter is ;
* - The first token is the contract. If the tokenString starts with ; there is no contract
* - All other tokens are in key = value form (with = being the separator)
* - A valid key that can appear only once is "name"
* - A valid key that can appear any number of times is "qualifier"
*
*
* The following are examples of valid token strings:
* - com.acme.product.RocketShoes
* - com.acme.product.Sneakers;name=Nike
* - com.acme.product.Sneakers;name=Nike;qualifier=com.acme.color.Red;qualifier=com.acme.style.HighTop
* - ;name=Narcissus
*
*
* @param tokenString A non-null string following the rules stated above
* @return A filter that will match the given string
* @throws IllegalArgumentException If the token string is invalid in some way
*/
public static IndexedFilter createTokenizedFilter(String tokenString) throws IllegalArgumentException {
if (tokenString == null) throw new IllegalArgumentException("null passed to createTokenizedFilter");
StringTokenizer st = new StringTokenizer(tokenString, TOKEN_SEPARATOR);
String contract = null;
String name = null;
final Set qualifiers = new LinkedHashSet();
boolean firstToken = true;
if (tokenString.startsWith(TOKEN_SEPARATOR)) {
firstToken = false;
}
while (st.hasMoreTokens()) {
String token = st.nextToken();
if (firstToken) {
firstToken = false;
if (token.length() <= 0) continue;
contract = token;
}
else {
int index = token.indexOf('=');
if (index < 0) {
throw new IllegalArgumentException("No = character found in token " + token);
}
String leftHandSide = token.substring(0, index);
String rightHandSide = token.substring(index + 1);
if (rightHandSide.length() <= 0) {
throw new IllegalArgumentException("No value found in token " + token);
}
if (NAME_KEY.equals(leftHandSide)) {
name = rightHandSide;
}
else if (QUALIFIER_KEY.equals(leftHandSide)) {
qualifiers.add(rightHandSide);
}
else {
throw new IllegalArgumentException("Unknown key: " + leftHandSide);
}
}
}
final String fContract = contract;
final String fName = name;
return new IndexedFilter() {
@Override
public boolean matches(Descriptor d) {
if (qualifiers.isEmpty()) return true;
return d.getQualifiers().containsAll(qualifiers);
}
@Override
public String getAdvertisedContract() {
return fContract;
}
@Override
public String getName() {
return fName;
}
@Override
public String toString() {
String cField = (fContract == null) ? "" : fContract;
String nField = (fName == null) ? "" : ";name=" + fName;
StringBuffer sb = new StringBuffer();
for (String q : qualifiers) {
sb.append(";qualifier=" + q);
}
return "TokenizedFilter(" + cField + nField + sb.toString() + ")";
}
};
}
/**
* This method creates a filter that will match one and only one descriptor. The passed
* in descriptor must have both its serviceID and locatorId filled in, or else this
* method will throw an IllegalArgumentException
*
* @param descriptor The descriptor from which to create a filter
* @return A filter to use that will match this descriptor exactly
*/
public static IndexedFilter createSpecificDescriptorFilter(Descriptor descriptor) {
String contract = ServiceLocatorUtilities.getBestContract(descriptor);
String name = descriptor.getName();
if (descriptor.getServiceId() == null) {
throw new IllegalArgumentException("The descriptor must have a specific service ID");
}
if (descriptor.getLocatorId() == null) {
throw new IllegalArgumentException("The descriptor must have a specific locator ID");
}
return new SpecificFilterImpl(contract, name,
descriptor.getServiceId(),
descriptor.getLocatorId());
}
/**
* Returns a filter that will return true an IndexedFilter that will match
* the {@link DescriptorImpl#equals(Object)} return
*
* @param descriptorImpl A non-null Descriptor to compare against other Descriptors
* @param deepCopy If true then a copy will be made of the descriptorImpl to protect the filter
* from the case where the incoming descriptorImpl may change. If false then the user
* must ensure that the fields of the descriptorImpl cannot change after the filter is returned.
* @return An IndexedFilter that can be used to determine equality with the given filter
* based on the fields of the Descriptor
*/
public static IndexedFilter createDescriptorFilter(Descriptor descriptorImpl, boolean deepCopy) {
final Descriptor filterDescriptor = (deepCopy) ? new DescriptorImpl(descriptorImpl) : descriptorImpl ;
return new IndexedFilter() {
@Override
public boolean matches(Descriptor d) {
return DescriptorImpl.descriptorEquals(filterDescriptor, d);
}
@Override
public String getAdvertisedContract() {
Set contracts = filterDescriptor.getAdvertisedContracts();
if (contracts == null || contracts.isEmpty()) return null;
return contracts.iterator().next();
}
@Override
public String getName() {
return filterDescriptor.getName();
}
};
}
/**
* Returns a filter that will return true an IndexedFilter that will match
* the {@link DescriptorImpl#equals(Object)} return
*
* @param descriptorImpl A non-null Descriptor to compare against other Descriptors
* @return An IndexedFilter that can be used to determine equality with the given filter
* based on the fields of the Descriptor
*/
public static IndexedFilter createDescriptorFilter(Descriptor descriptorImpl) {
return createDescriptorFilter(descriptorImpl, true);
}
/**
* Returns a filter of type Descriptor that matches
* all descriptors
*
* @return A filter that matches all descriptors
*/
public static Filter allFilter() {
return StarFilter.getDescriptorFilter();
}
/**
* This method links an implementation class with a {@link DescriptorBuilder}, to
* be used to further build the {@link Descriptor}.
*
* @param implementationClass The fully qualified name of the implementation
* class to be associated with the DescriptorBuilder.
* @param addToContracts if true, this implementation class will be added to the
* list of contracts
*
* @return A {@link DescriptorBuilder} that can be used to further build up the
* {@link Descriptor}
* @throws IllegalArgumentException if implementationClass is null
*/
public static DescriptorBuilder link(String implementationClass, boolean addToContracts) throws IllegalArgumentException {
if (implementationClass == null) throw new IllegalArgumentException();
return new DescriptorBuilderImpl(implementationClass, addToContracts);
}
/**
* This method links an implementation class with a {@link DescriptorBuilder}, to
* be used to further build the {@link Descriptor}. This method will automatically
* put the implementationClass into the list of advertised contracts.
*
* @param implementationClass The fully qualified name of the implementation
* class to be associated with the PredicateBuilder.
*
* @return A {@link DescriptorBuilder} that can be used to further build up the
* {@link Descriptor}
* @throws IllegalArgumentException if implementationClass is null
*/
public static DescriptorBuilder link(String implementationClass) throws IllegalArgumentException {
return link(implementationClass, true);
}
/**
* This method links an implementation class with a {@link DescriptorBuilder}, to
* be used to further build the {@link Descriptor}
*
* @param implementationClass The implementation class to be associated
* with the {@link DescriptorBuilder}.
* @param addToContracts true if this impl class should be automatically added to
* the list of contracts
* @return A {@link DescriptorBuilder} that can be used to further build up the
* {@link Descriptor}
* @throws IllegalArgumentException if implementationClass is null
*/
public static DescriptorBuilder link(Class> implementationClass, boolean addToContracts) throws IllegalArgumentException {
if (implementationClass == null) throw new IllegalArgumentException();
DescriptorBuilder builder = link(implementationClass.getName(), addToContracts);
return builder;
}
/**
* This method links an implementation class with a {@link DescriptorBuilder}, to
* be used to further build the {@link Descriptor}.
*
* @param implementationClass The implementation class to be associated
* with the {@link DescriptorBuilder}.
* @return A {@link DescriptorBuilder} that can be used to further build up the
* {@link Descriptor}
* @throws IllegalArgumentException if implementationClass is null
*/
public static DescriptorBuilder link(Class> implementationClass) throws IllegalArgumentException {
if (implementationClass == null) throw new IllegalArgumentException();
boolean isFactory = (Factory.class.isAssignableFrom(implementationClass));
DescriptorBuilder db = link(implementationClass, !isFactory);
return db;
}
/**
* This method creates an {@link ActiveDescriptorBuilder}, whose job it
* is to create an unreified {@link ActiveDescriptor}. The implementation
* class given will NOT automatically be added to the set of contracts
* of the {@link ActiveDescriptor}.
*
* @param implementationClass The implementation class to be associated
* with the {@link ActiveDescriptorBuilder}.
* @return A {@link ActiveDescriptorBuilder} that can be used to further build up the
* {@link Descriptor}
* @throws IllegalArgumentException if implementationClass is null
*/
public static ActiveDescriptorBuilder activeLink(Class> implementationClass) throws IllegalArgumentException {
if (implementationClass == null) throw new IllegalArgumentException();
return new ActiveDescriptorBuilderImpl(implementationClass);
}
/**
* This creates a descriptor that will always return the given object. The
* set of types in the advertised contracts will contain the class of the
* constant along with:
* - Any superclass of the constant marked with {@link Contract}
* - Any interface of the constant marked with {@link Contract}
*
*
* @param constant The non-null constant that should always be returned from
* the create method of this ActiveDescriptor.
* @return The descriptor returned can be used in calls to
* DynamicConfiguration.addActiveDescriptor
* @throws IllegalArgumentException if constant is null
*/
public static AbstractActiveDescriptor createConstantDescriptor(T constant) {
if (constant == null) throw new IllegalArgumentException();
Set contracts;
Class> cClass = constant.getClass();
ContractsProvided provided = cClass.getAnnotation(ContractsProvided.class);
if (provided != null) {
contracts = new HashSet();
for (Class> specified : provided.value()) {
contracts.add(specified);
}
}
else {
contracts = ReflectionHelper.getAdvertisedTypesFromObject(constant, Contract.class);
}
return createConstantDescriptor(constant,
ReflectionHelper.getName(constant.getClass()),
contracts.toArray(new Type[contracts.size()]));
}
/**
* Gets the rank from the given class
*
* @param fromClass The class to get the rank from. Will also check all
* superclasses
* @return The rank this class should initially have, or 0 if there is
* no Rank annotation on this class or all its superclasses
*/
public static int getRank(Class> fromClass) {
while (fromClass != null && !Object.class.equals(fromClass)) {
Rank rank = fromClass.getAnnotation(Rank.class);
if (rank != null) {
return rank.value();
}
fromClass = fromClass.getSuperclass();
}
return 0;
}
/**
* This creates a descriptor that will always return the given object.
* The advertised contracts is given in the incoming parameter and the
* name on the descriptor also comes from the incoming parameter.
*
* @param constant The non-null constant that should always be returned from
* the create method of this ActiveDescriptor.
* @param name The possibly null name that should be associated with this constant descriptor
* @param contracts The possibly empty set of contracts that should be associated with this
* descriptor
* @return The descriptor returned can be used in calls to
* DynamicConfiguration.addActiveDescriptor
* @throws IllegalArgumentException if constant is null
*/
public static AbstractActiveDescriptor createConstantDescriptor(T constant, String name, Type... contracts) {
if (constant == null) throw new IllegalArgumentException();
Annotation scope =
ReflectionHelper.getScopeAnnotationFromObject(constant);
Class extends Annotation> scopeClass = (scope == null) ? PerLookup.class :
scope.annotationType();
Set qualifiers =
ReflectionHelper.getQualifiersFromObject(constant);
Map> metadata = new HashMap>();
if (scope != null) {
getMetadataValues(scope, metadata);
}
for (Annotation qualifier : qualifiers) {
getMetadataValues(qualifier, metadata);
}
Set contractsAsSet;
if (contracts.length <= 0) {
contractsAsSet = ReflectionHelper.getAdvertisedTypesFromObject(constant, Contract.class);
}
else {
contractsAsSet = new LinkedHashSet();
for (Type cType : contracts) {
contractsAsSet.add(cType);
}
}
Boolean proxy = null;
UseProxy up = constant.getClass().getAnnotation(UseProxy.class);
if (up != null) {
proxy = up.value();
}
Boolean proxyForSameScope = null;
ProxyForSameScope pfss = constant.getClass().getAnnotation(ProxyForSameScope.class);
if (pfss != null) {
proxyForSameScope = pfss.value();
}
DescriptorVisibility visibility = DescriptorVisibility.NORMAL;
Visibility vi = constant.getClass().getAnnotation(Visibility.class);
if (vi != null) {
visibility = vi.value();
}
String classAnalysisName = null;
Service service = constant.getClass().getAnnotation(Service.class);
if (service != null) {
classAnalysisName = service.analyzer();
}
int rank = getRank(constant.getClass());
return new ConstantActiveDescriptor(
constant,
contractsAsSet,
scopeClass,
name,
qualifiers,
visibility,
proxy,
proxyForSameScope,
classAnalysisName,
metadata,
rank);
}
/**
* This returns a DescriptorImpl based on the given class. The returned
* descriptor will include the class itself as an advertised contract and
* all implemented interfaces that are marked @Contract
*
* @param clazz The class to analyze
* @return The DescriptorImpl corresponding to this class
*/
public static DescriptorImpl createDescriptorFromClass(Class> clazz) {
if (clazz == null) return new DescriptorImpl();
Set contracts = ReflectionHelper.getContractsFromClass(clazz, Contract.class);
String name = ReflectionHelper.getName(clazz);
String scope = ReflectionHelper.getScopeFromClass(clazz, ServiceLocatorUtilities.getPerLookupAnnotation()).annotationType().getName();
Set qualifiers = ReflectionHelper.getQualifiersFromClass(clazz);
DescriptorType type = DescriptorType.CLASS;
if (Factory.class.isAssignableFrom(clazz)) {
type = DescriptorType.PROVIDE_METHOD;
}
Boolean proxy = null;
UseProxy up = clazz.getAnnotation(UseProxy.class);
if (up != null) {
proxy = Boolean.valueOf(up.value());
}
Boolean proxyForSameScope = null;
ProxyForSameScope pfss = clazz.getAnnotation(ProxyForSameScope.class);
if (pfss != null) {
proxyForSameScope = Boolean.valueOf(pfss.value());
}
DescriptorVisibility visibility = DescriptorVisibility.NORMAL;
Visibility vi = clazz.getAnnotation(Visibility.class);
if (vi != null) {
visibility = vi.value();
}
int rank = getRank(clazz);
// TODO: Can we get metadata from @Service?
return new DescriptorImpl(
contracts,
name,
scope,
clazz.getName(),
new HashMap>(),
qualifiers,
type,
visibility,
null,
rank,
proxy,
proxyForSameScope,
null,
null,
null);
}
/**
* Makes a deep copy of the incoming descriptor
*
* @param copyMe The descriptor to copy
* @return A new descriptor with all fields copied
*/
public static DescriptorImpl deepCopyDescriptor(Descriptor copyMe) {
return new DescriptorImpl(copyMe);
}
/**
* This is a helper method that gets the metadata values from the
* {@link Metadata} annotations found in an annotation.
*
* @param annotation The annotation to find {@link Metadata} values
* from. May not be null.
* @param metadata A non-null metadata map. The values found in the
* annotation will be added to this metadata map
* @throws IllegalArgumentException if annotation or metadata is null
* @throws MultiException if there was an error invoking the methods of the annotation
*/
public static void getMetadataValues(Annotation annotation, Map> metadata) {
if (annotation == null || metadata == null) {
throw new IllegalArgumentException();
}
final Class extends Annotation> annotationClass = annotation.annotationType();
Method annotationMethods[] = AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Method[] run() {
return annotationClass.getDeclaredMethods();
}
});
for (Method annotationMethod : annotationMethods) {
Metadata metadataAnno = annotationMethod.getAnnotation(Metadata.class);
if (metadataAnno == null) continue;
String key = metadataAnno.value();
Object addMe;
try {
addMe = ReflectionHelper.invoke(annotation, annotationMethod, new Object[0], false);
}
catch (Throwable th) {
throw new MultiException(th);
}
if (addMe == null) continue;
String addMeString;
if (addMe instanceof Class) {
addMeString = ((Class>) addMe).getName();
}
else if (addMe.getClass().isArray()) {
int length = Array.getLength(addMe);
for (int lcv = 0; lcv < length; lcv++) {
Object iValue = Array.get(addMe, lcv);
if (iValue == null) continue;
if (iValue instanceof Class) {
String cName = ((Class>) iValue).getName();
ReflectionHelper.addMetadata(metadata, key, cName);
}
else {
ReflectionHelper.addMetadata(metadata, key, iValue.toString());
}
}
addMeString = null;
}
else {
addMeString = addMe.toString();
}
if (addMeString != null) {
ReflectionHelper.addMetadata(metadata, key, addMeString);
}
}
}
/**
* Creates a ServiceHandle that will always return the given object from
* the {@link ServiceHandle#getService()} method. The {@link ServiceHandle#getActiveDescriptor()}
* will return null and the {@link ServiceHandle#destroy()} method will
* do nothing
*
* @param obj The object to be associated with this ServiceHandle. May be null
* @return A {@link ServiceHandle} that will always return the given value
*/
public static ServiceHandle createConstantServiceHandle(final T obj) {
return new ServiceHandle() {
private Object serviceData;
@Override
public T getService() {
return obj;
}
@Override
public ActiveDescriptor getActiveDescriptor() {
return null;
}
@Override
public boolean isActive() {
return true;
}
@Override
public void close() {
// Do nothing
}
@Override
public synchronized void setServiceData(Object serviceData) {
this.serviceData = serviceData;
}
@Override
public synchronized Object getServiceData() {
return serviceData;
}
@Override
public List> getSubHandles() {
return Collections.emptyList();
}
};
}
/**
* Determines if the given descriptor matches the given filter. A null
* filter matches every descriptor. Takes into account if the {@link Filter}
* implements {@link IndexedFilter}.
*
* @param baseDescriptor The non-null descriptor to match the filter against
* @param filter The filter to match. If null will return true
* @return true if baseDescriptor matches, false otherwise
*/
public static boolean filterMatches(final Descriptor baseDescriptor, final Filter filter) {
if (baseDescriptor == null) throw new IllegalArgumentException();
if (filter == null) return true;
if (filter instanceof IndexedFilter) {
IndexedFilter indexedFilter = (IndexedFilter) filter;
String indexContract = indexedFilter.getAdvertisedContract();
if (indexContract != null) {
if (!baseDescriptor.getAdvertisedContracts().contains(indexContract)) {
return false;
}
}
String indexName = indexedFilter.getName();
if (indexName != null) {
if (baseDescriptor.getName() == null) return false;
if (!indexName.equals(baseDescriptor.getName())) {
return false;
}
}
// After all that we can run the match method!
}
return filter.matches(baseDescriptor);
}
}