
org.apache.logging.log4j.plugins.di.Key Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you 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.logging.log4j.plugins.di;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Supplier;
import org.apache.logging.log4j.plugins.Ordered;
import org.apache.logging.log4j.plugins.QualifierType;
import org.apache.logging.log4j.plugins.util.AnnotationUtil;
import org.apache.logging.log4j.plugins.util.TypeUtil;
import org.apache.logging.log4j.util.Cast;
import org.apache.logging.log4j.util.StringBuilderFormattable;
import org.apache.logging.log4j.util.Strings;
/**
* Type with an optional {@link QualifierType} type, name, and namespace. Keys are used for binding to and looking up instance
* factories via {@link Injector}.
*
* @param type of key
*/
public class Key implements StringBuilderFormattable {
private final Type type;
private final Class rawType;
private final Class extends Annotation> qualifierType;
private final String name;
private final String namespace;
private final int order;
private final int hashCode;
private String toString;
/**
* Anonymous subclasses override this constructor to instantiate this Key based on the type given.
* For example, to represent the equivalent of an annotated field {@code @Named("abc") Map> field},
* this constructor would be used like so:
*
* {@code
* Key
*/
protected Key() {
type = TypeUtil.getSuperclassTypeParameter(getClass());
rawType = Cast.cast(TypeUtil.getRawType(type));
final AnnotatedType superclass = getClass().getAnnotatedSuperclass();
final Annotation qualifier = AnnotationUtil.getMetaAnnotation(superclass, QualifierType.class);
qualifierType = qualifier != null ? qualifier.annotationType() : null;
name = Keys.getName(superclass);
namespace = Keys.getNamespace(superclass);
order = getOrder(superclass);
hashCode = Objects.hash(type, qualifierType, name.toLowerCase(Locale.ROOT), namespace.toLowerCase(Locale.ROOT));
}
private Key(
final Type type, final Class rawType, final Class extends Annotation> qualifierType, final String name,
final String namespace, final int order) {
this.type = type;
this.rawType = rawType;
this.qualifierType = qualifierType;
this.name = name;
this.namespace = namespace;
this.order = order;
hashCode = Objects.hash(type, qualifierType, name.toLowerCase(Locale.ROOT), namespace.toLowerCase(Locale.ROOT));
}
/**
* Returns the generic type of this key.
*/
public final Type getType() {
return type;
}
/**
* Returns the raw type of this key corresponding to its generic type.
*/
public final Class getRawType() {
return rawType;
}
/**
* Returns the name of this key. Names are case-insensitive. If this key has no defined name, then this returns
* an empty string.
*/
public final String getName() {
return name;
}
/**
* Returns the namespace of this key. If this key has no defined namespace, then this returns an empty string.
*/
public final String getNamespace() {
return namespace;
}
/**
* Returns the qualifier type of this key. If this key has no qualifier type defined, then this returns
* {@code null}.
*/
public final Class extends Annotation> getQualifierType() {
return qualifierType;
}
/**
* Returns the ordinal value of this key. Keys that are otherwise equal can be compared by this
* ordinal using the natural integer comparator where ties should default to keeping an existing binding intact.
*/
public final int getOrder() {
return order;
}
/**
* Returns a new key using the provided name and the same type and qualifier type as this instance.
*/
public final Key withName(final String name) {
return new Key<>(type, rawType, qualifierType, name, namespace, order);
}
/**
* Returns a new key using the provided namespace otherwise populated with the same values as this instance.
*/
public final Key withNamespace(final String namespace) {
return new Key<>(type, rawType, qualifierType, name, namespace, order);
}
/**
* Returns a new key using the provided qualifier type and the same type and name as this instance.
*/
public final Key withQualifierType(final Class extends Annotation> qualifierType) {
return new Key<>(type, rawType, qualifierType, name, namespace, order);
}
/**
* If this key's type {@code T} is a subtype of {@code Supplier} for some supplied type {@code P}, then this
* returns a new key with that type argument along with the same name and qualifier type as this key.
*/
public final
Key
getSuppliedType() {
if (type instanceof ParameterizedType && Supplier.class.isAssignableFrom(rawType)) {
final Type typeArgument = ((ParameterizedType) type).getActualTypeArguments()[0];
final Class
rawType = Cast.cast(TypeUtil.getRawType(typeArgument));
return new Key<>(typeArgument, rawType, qualifierType, name, namespace, order);
}
return null;
}
/**
* If this key's type {@code T} is a parameterized type, then this returns a new key corresponding to the
* requested parameterized type argument along with the same remaining values as this key.
*/
public final
Key
getParameterizedTypeArgument(final int arg) {
if (type instanceof ParameterizedType) {
final Type typeArgument = ((ParameterizedType) type).getActualTypeArguments()[arg];
final Class
rawType = Cast.cast(TypeUtil.getRawType(typeArgument));
return new Key<>(typeArgument, rawType, qualifierType, name, namespace, order);
}
return null;
}
@Override
public final boolean equals(final Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Key>)) {
return false;
}
final Key> that = (Key>) o;
return TypeUtil.isEqual(this.type, that.type) &&
this.name.equalsIgnoreCase(that.name) &&
this.namespace.equalsIgnoreCase(that.namespace) &&
Objects.equals(this.qualifierType, that.qualifierType);
}
@Override
public final int hashCode() {
return hashCode;
}
@Override
public final String toString() {
String string = toString;
if (string == null) {
final StringBuilder sb = new StringBuilder(32);
formatTo(sb);
toString = string = sb.toString();
}
return string;
}
@Override
public void formatTo(final StringBuilder buffer) {
buffer.append(TO_STRING_PREFIX).append(type.getTypeName());
if (!namespace.isEmpty()) {
buffer.append(NAMESPACE).append(namespace);
}
if (!name.isEmpty()) {
buffer.append(NAME).append(name);
}
if (qualifierType != null) {
buffer.append(QUALIFIER_TYPE).append(qualifierType.getSimpleName());
}
buffer.append(']');
}
private static final String TO_STRING_PREFIX = "Key[type: ";
private static final String NAMESPACE = "; namespace: ";
private static final String NAME = "; name: ";
private static final String QUALIFIER_TYPE = "; qualifierType: ";
/**
* Creates a Key for the class.
*/
public static Key forClass(final Class clazz) {
return Key.builder(clazz)
.setQualifierType(getQualifierType(clazz))
.setName(Keys.getName(clazz))
.setNamespace(Keys.getNamespace(clazz))
.setOrder(getOrder(clazz))
.get();
}
/**
* Creates a Key for the return type of the method.
*/
public static Key forMethod(final Method method) {
return Key.builder(method.getGenericReturnType())
.setQualifierType(getQualifierType(method))
.setName(Keys.getName(method))
.setNamespace(Keys.getNamespace(method))
.setOrder(getOrder(method))
.get();
}
/**
* Creates a Key for the parameter.
*/
public static Key forParameter(final Parameter parameter) {
return Key.builder(parameter.getParameterizedType())
.setQualifierType(getQualifierType(parameter))
.setName(Keys.getName(parameter))
.setNamespace(Keys.getNamespace(parameter))
.get();
}
/**
* Creates a Key for the field.
*/
public static Key forField(final Field field) {
return Key.builder(field.getGenericType())
.setQualifierType(getQualifierType(field))
.setName(Keys.getName(field))
.setNamespace(Keys.getNamespace(field))
.get();
}
/**
* Creates a new key builder for the given generic type.
*/
public static Builder builder(final Type type) {
return new Builder<>(type);
}
/**
* Creates a new key builder for the given class.
*/
public static Builder builder(final Class type) {
return new Builder<>(type);
}
/**
* Creates a new key builder from an existing key's properties.
*/
public static Builder builder(final Key original) {
return new Builder<>(original);
}
private static Class extends Annotation> getQualifierType(final AnnotatedElement element) {
final Annotation qualifierAnnotation = AnnotationUtil.getMetaAnnotation(element, QualifierType.class);
return qualifierAnnotation != null ? qualifierAnnotation.annotationType() : null;
}
private static int getOrder(final AnnotatedElement element) {
final Ordered annotation = element.getAnnotation(Ordered.class);
return annotation != null ? annotation.value() : 0;
}
/**
* Builder class for configuring a new {@link Key} instance.
*
* @param type of key
*/
public static final class Builder implements Supplier> {
private final Type type;
private final Class rawType;
private Class extends Annotation> qualifierType;
private String name;
private String namespace;
private Integer order;
private Builder(final Type type) {
this.type = type;
rawType = Cast.cast(TypeUtil.getRawType(type));
}
private Builder(final Class type) {
this.type = type;
rawType = type;
}
private Builder(final Key original) {
type = original.type;
rawType = original.rawType;
qualifierType = original.qualifierType;
name = original.name;
namespace = original.namespace;
order = original.order;
}
/**
* Specifies a qualifier annotation type. Qualifiers are optional and are used for an additional comparison
* property for keys.
*/
public Builder setQualifierType(final Class extends Annotation> qualifierType) {
this.qualifierType = qualifierType;
return this;
}
/**
* Specifies the name of this key. The default name for keys is the empty string.
*/
public Builder setName(final String name) {
this.name = name;
return this;
}
/**
* Specifies the namespace of this key. The default namespace for keys is the empty string.
*/
public Builder setNamespace(final String namespace) {
this.namespace = namespace;
return this;
}
/**
* Specifies the order of this key for disambiguation. This overrides any value discovered from
* the {@link Ordered} annotation on the type of the key.
*/
public Builder setOrder(final int order) {
this.order = order;
return this;
}
/**
* Creates a new {@link Key} from this builder's properties.
*/
@Override
public Key get() {
if (name == null) {
name = Strings.EMPTY;
}
if (namespace == null) {
namespace = Strings.EMPTY;
}
final int order = this.order != null ? this.order : getOrder(rawType);
return new Key<>(type, rawType, qualifierType, name, namespace, order);
}
}
}