You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by ma...@apache.org on 2020/03/07 20:35:55 UTC

[logging-log4j2] 03/03: Refactor Collection into own class

This is an automated email from the ASF dual-hosted git repository.

mattsicker pushed a commit to branch mean-bean-machine
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit a827c7726955ddbfdeb7b9819d38f73980010bda
Author: Matt Sicker <bo...@gmail.com>
AuthorDate: Sat Mar 7 13:35:46 2020 -0600

    Refactor Collection<Qualifier> into own class
    
    This simplifies the model representation of qualifiers a bit.
    
    Signed-off-by: Matt Sicker <bo...@gmail.com>
---
 .../apache/logging/log4j/plugins/api/Default.java  |  25 -----
 .../log4j/plugins/defaults/bean/AbstractBean.java  |   4 +-
 .../plugins/defaults/bean/DefaultBeanManager.java  |  19 ++--
 .../log4j/plugins/defaults/bean/SystemBean.java    |   4 +-
 .../defaults/model/DefaultElementManager.java      |  20 ++--
 .../defaults/model/DefaultInjectionPoint.java      |  11 +-
 .../plugins/defaults/model/DefaultVariable.java    |  10 +-
 .../log4j/plugins/spi/model/ElementManager.java    |   2 +-
 .../log4j/plugins/spi/model/InjectionPoint.java    |   3 +-
 .../spi/model/{Qualifier.java => Qualifiers.java}  | 111 ++++++++++++---------
 .../logging/log4j/plugins/spi/model/Variable.java  |   2 +-
 .../log4j/plugins/spi/model/QualifiersTest.java    |  79 +++++++++++++++
 12 files changed, 175 insertions(+), 115 deletions(-)

diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Default.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Default.java
index 4ec2398..6578f65 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Default.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/api/Default.java
@@ -17,7 +17,6 @@
 
 package org.apache.logging.log4j.plugins.api;
 
-import java.lang.annotation.Annotation;
 import java.lang.annotation.Documented;
 import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
@@ -29,28 +28,4 @@ import java.lang.annotation.RetentionPolicy;
 @Inherited
 // default qualifier unless qualifier besides @Named is present
 public @interface Default {
-    @SuppressWarnings("ClassExplicitlyAnnotation")
-    class Literal implements Default, Annotation {
-        @Override
-        public Class<? extends Annotation> annotationType() {
-            return Default.class;
-        }
-
-        @Override
-        public boolean equals(final Object o) {
-            return this == o || o instanceof Default;
-        }
-
-        @Override
-        public int hashCode() {
-            return Default.class.hashCode();
-        }
-
-        @Override
-        public String toString() {
-            return "@Default";
-        }
-    }
-
-    Default INSTANCE = new Literal();
 }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/AbstractBean.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/AbstractBean.java
index aeaf77b..524e849 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/AbstractBean.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/AbstractBean.java
@@ -19,7 +19,7 @@ package org.apache.logging.log4j.plugins.defaults.bean;
 
 import org.apache.logging.log4j.plugins.spi.bean.Bean;
 import org.apache.logging.log4j.plugins.spi.model.MetaClass;
-import org.apache.logging.log4j.plugins.spi.model.Qualifier;
+import org.apache.logging.log4j.plugins.spi.model.Qualifiers;
 import org.apache.logging.log4j.plugins.spi.model.Variable;
 
 import java.lang.annotation.Annotation;
@@ -42,7 +42,7 @@ abstract class AbstractBean<D, T> implements Bean<T> {
     }
 
     @Override
-    public Collection<Qualifier> getQualifiers() {
+    public Qualifiers getQualifiers() {
         return variable.getQualifiers();
     }
 
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultBeanManager.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultBeanManager.java
index f431939..ec4ba8b 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultBeanManager.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/DefaultBeanManager.java
@@ -44,7 +44,7 @@ import org.apache.logging.log4j.plugins.spi.model.MetaField;
 import org.apache.logging.log4j.plugins.spi.model.MetaMember;
 import org.apache.logging.log4j.plugins.spi.model.MetaMethod;
 import org.apache.logging.log4j.plugins.spi.model.MetaParameter;
-import org.apache.logging.log4j.plugins.spi.model.Qualifier;
+import org.apache.logging.log4j.plugins.spi.model.Qualifiers;
 import org.apache.logging.log4j.plugins.spi.model.Variable;
 import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
 import org.apache.logging.log4j.plugins.spi.scope.ScopeContext;
@@ -88,6 +88,7 @@ public class DefaultBeanManager implements BeanManager {
 
     public DefaultBeanManager(final ElementManager elementManager) {
         this.elementManager = elementManager;
+        // TODO: need a better way to register scope contexts
         scopes.put(PrototypeScoped.class, new PrototypeScopeContext());
         scopes.put(SingletonScoped.class, new DefaultScopeContext(SingletonScoped.class));
     }
@@ -276,7 +277,7 @@ public class DefaultBeanManager implements BeanManager {
             throw new InjectionException("Expected one type parameter argument for " + point + " but got " +
                     Arrays.toString(typeArguments));
         }
-        if (point.getQualifiers().contains(Qualifier.DEFAULT_QUALIFIER)) {
+        if (point.getQualifiers().hasDefaultQualifier()) {
             final Type typeArgument = typeArguments[0];
             if (!typeArgument.equals(expectedType)) {
                 throw new InjectionException("Expected type " + expectedType + " but got " + typeArgument + " in " + point);
@@ -288,7 +289,7 @@ public class DefaultBeanManager implements BeanManager {
         // TODO: this will need to allow for TypeConverter usage somehow
         // first, look for an existing bean
         final Type type = point.getType();
-        final Collection<Qualifier> qualifiers = point.getQualifiers();
+        final Qualifiers qualifiers = point.getQualifiers();
         final Optional<Bean<T>> existingBean = getExistingOrProvidedBean(type, qualifiers,
                 () -> elementManager.createVariable(point));
         if (existingBean.isPresent()) {
@@ -321,7 +322,7 @@ public class DefaultBeanManager implements BeanManager {
         return Optional.empty();
     }
 
-    private <T> Optional<Bean<T>> getExistingOrProvidedBean(final Type type, final Collection<Qualifier> qualifiers,
+    private <T> Optional<Bean<T>> getExistingOrProvidedBean(final Type type, final Qualifiers qualifiers,
                                                             final Supplier<Variable<T>> variableSupplier) {
         final Optional<Bean<T>> existingBean = getBean(type, qualifiers);
         if (existingBean.isPresent()) {
@@ -334,9 +335,9 @@ public class DefaultBeanManager implements BeanManager {
                 .map(this::addBean);
     }
 
-    private <T> Optional<Bean<T>> getBean(final Type type, final Collection<Qualifier> qualifiers) {
+    private <T> Optional<Bean<T>> getBean(final Type type, final Qualifiers qualifiers) {
         final Set<Bean<T>> beans = this.<T>streamBeansMatchingType(type)
-                .filter(bean -> areCollectionsIsomorphic(qualifiers, bean.getQualifiers()))
+                .filter(bean -> qualifiers.equals(bean.getQualifiers()))
                 .collect(Collectors.toSet());
         if (beans.size() > 1) {
             throw new AmbiguousBeanException(beans, "type " + type + " and qualifiers " + qualifiers);
@@ -440,11 +441,11 @@ public class DefaultBeanManager implements BeanManager {
 
     private static class DisposesMethod<D> {
         private final Collection<Type> types;
-        private final Collection<Qualifier> qualifiers;
+        private final Qualifiers qualifiers;
         private final Bean<D> declaringBean;
         private final MetaMethod<D, ?> disposesMethod;
 
-        private DisposesMethod(final Collection<Type> types, final Collection<Qualifier> qualifiers,
+        private DisposesMethod(final Collection<Type> types, final Qualifiers qualifiers,
                                final Bean<D> declaringBean, final MetaMethod<D, ?> disposesMethod) {
             this.types = types;
             this.qualifiers = qualifiers;
@@ -455,7 +456,7 @@ public class DefaultBeanManager implements BeanManager {
         boolean matches(final Variable<?> variable, final Bean<?> declaringBean) {
             return Objects.equals(declaringBean, this.declaringBean) &&
                     areCollectionsIsomorphic(types, variable.getTypes()) &&
-                    areCollectionsIsomorphic(qualifiers, variable.getQualifiers());
+                    qualifiers.equals(variable.getQualifiers());
         }
     }
 
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/SystemBean.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/SystemBean.java
index 91205b0..66d8230 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/SystemBean.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/bean/SystemBean.java
@@ -18,8 +18,8 @@
 package org.apache.logging.log4j.plugins.defaults.bean;
 
 import org.apache.logging.log4j.plugins.spi.model.MetaClass;
-import org.apache.logging.log4j.plugins.spi.model.Qualifier;
 import org.apache.logging.log4j.plugins.spi.bean.Bean;
+import org.apache.logging.log4j.plugins.spi.model.Qualifiers;
 import org.apache.logging.log4j.plugins.spi.model.Variable;
 import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
 import org.apache.logging.log4j.plugins.spi.scope.InitializationContext;
@@ -37,7 +37,7 @@ abstract class SystemBean<T> implements Bean<T> {
     }
 
     @Override
-    public Collection<Qualifier> getQualifiers() {
+    public Qualifiers getQualifiers() {
         return variable.getQualifiers();
     }
 
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultElementManager.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultElementManager.java
index ca038da..882c124 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultElementManager.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultElementManager.java
@@ -18,8 +18,6 @@
 package org.apache.logging.log4j.plugins.defaults.model;
 
 import org.apache.logging.log4j.plugins.api.AliasFor;
-import org.apache.logging.log4j.plugins.api.Default;
-import org.apache.logging.log4j.plugins.api.Named;
 import org.apache.logging.log4j.plugins.api.PrototypeScoped;
 import org.apache.logging.log4j.plugins.api.QualifierType;
 import org.apache.logging.log4j.plugins.api.ScopeType;
@@ -32,7 +30,7 @@ import org.apache.logging.log4j.plugins.spi.model.MetaElement;
 import org.apache.logging.log4j.plugins.spi.model.MetaExecutable;
 import org.apache.logging.log4j.plugins.spi.model.MetaField;
 import org.apache.logging.log4j.plugins.spi.model.MetaParameter;
-import org.apache.logging.log4j.plugins.spi.model.Qualifier;
+import org.apache.logging.log4j.plugins.spi.model.Qualifiers;
 import org.apache.logging.log4j.plugins.spi.model.Variable;
 import org.apache.logging.log4j.plugins.util.Cache;
 import org.apache.logging.log4j.plugins.util.WeakCache;
@@ -41,10 +39,8 @@ import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Objects;
-import java.util.stream.Collectors;
 
 public class DefaultElementManager implements ElementManager {
 
@@ -84,12 +80,8 @@ public class DefaultElementManager implements ElementManager {
     }
 
     @Override
-    public Collection<Qualifier> getQualifiers(final MetaElement<?> element) {
-        final Collection<Annotation> qualifiers = filterQualifiers(element.getAnnotations());
-        if (qualifiers.stream().noneMatch(annotation -> annotation.annotationType() != Named.class)) {
-            qualifiers.add(Default.INSTANCE);
-        }
-        return qualifiers.stream().map(Qualifier::fromAnnotation).collect(Collectors.toCollection(HashSet::new));
+    public Qualifiers getQualifiers(final MetaElement<?> element) {
+        return Qualifiers.fromQualifierAnnotations(filterQualifiers(element.getAnnotations()));
     }
 
     private Collection<Annotation> filterQualifiers(final Collection<Annotation> annotations) {
@@ -160,7 +152,7 @@ public class DefaultElementManager implements ElementManager {
     @Override
     public <D, T> InjectionPoint<T> createFieldInjectionPoint(final MetaField<D, T> field, final Bean<D> owner) {
         Objects.requireNonNull(field);
-        final Collection<Qualifier> qualifiers = getQualifiers(field);
+        final Qualifiers qualifiers = getQualifiers(field);
         return new DefaultInjectionPoint<>(field.getBaseType(), qualifiers, owner, field, field);
     }
 
@@ -170,7 +162,7 @@ public class DefaultElementManager implements ElementManager {
                                                                   final Bean<D> owner) {
         Objects.requireNonNull(executable);
         Objects.requireNonNull(parameter);
-        final Collection<Qualifier> qualifiers = getQualifiers(parameter);
+        final Qualifiers qualifiers = getQualifiers(parameter);
         return new DefaultInjectionPoint<>(parameter.getBaseType(), qualifiers, owner, executable, parameter);
     }
 
@@ -186,7 +178,7 @@ public class DefaultElementManager implements ElementManager {
         return createVariable(point.getElement(), point.getQualifiers());
     }
 
-    private <T> Variable<T> createVariable(final MetaElement<T> element, final Collection<Qualifier> qualifiers) {
+    private <T> Variable<T> createVariable(final MetaElement<T> element, final Qualifiers qualifiers) {
         final Collection<Type> types = element.getTypeClosure();
         final Class<? extends Annotation> scopeType = getScopeType(element);
         return DefaultVariable.newVariable(types, qualifiers, scopeType);
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultInjectionPoint.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultInjectionPoint.java
index 0ccccdf..80bc723 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultInjectionPoint.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultInjectionPoint.java
@@ -18,24 +18,23 @@
 package org.apache.logging.log4j.plugins.defaults.model;
 
 import org.apache.logging.log4j.plugins.spi.bean.Bean;
-import org.apache.logging.log4j.plugins.spi.model.MetaElement;
 import org.apache.logging.log4j.plugins.spi.model.InjectionPoint;
+import org.apache.logging.log4j.plugins.spi.model.MetaElement;
 import org.apache.logging.log4j.plugins.spi.model.MetaMember;
-import org.apache.logging.log4j.plugins.spi.model.Qualifier;
+import org.apache.logging.log4j.plugins.spi.model.Qualifiers;
 
 import java.lang.reflect.Type;
-import java.util.Collection;
 import java.util.Objects;
 import java.util.Optional;
 
 class DefaultInjectionPoint<T> implements InjectionPoint<T> {
     private final Type type;
-    private final Collection<Qualifier> qualifiers;
+    private final Qualifiers qualifiers;
     private final Bean<?> bean;
     private final MetaMember<?, ?> member;
     private final MetaElement<T> element;
 
-    DefaultInjectionPoint(final Type type, final Collection<Qualifier> qualifiers, final Bean<?> bean,
+    DefaultInjectionPoint(final Type type, final Qualifiers qualifiers, final Bean<?> bean,
                           final MetaMember<?, ?> member, final MetaElement<T> element) {
         this.type = type;
         this.qualifiers = qualifiers;
@@ -50,7 +49,7 @@ class DefaultInjectionPoint<T> implements InjectionPoint<T> {
     }
 
     @Override
-    public Collection<Qualifier> getQualifiers() {
+    public Qualifiers getQualifiers() {
         return qualifiers;
     }
 
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultVariable.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultVariable.java
index db5f6a5..f756908 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultVariable.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/defaults/model/DefaultVariable.java
@@ -17,7 +17,7 @@
 
 package org.apache.logging.log4j.plugins.defaults.model;
 
-import org.apache.logging.log4j.plugins.spi.model.Qualifier;
+import org.apache.logging.log4j.plugins.spi.model.Qualifiers;
 import org.apache.logging.log4j.plugins.spi.model.Variable;
 
 import java.lang.annotation.Annotation;
@@ -27,16 +27,16 @@ import java.util.Objects;
 
 public class DefaultVariable<T> implements Variable<T> {
 
-    public static <T> DefaultVariable<T> newVariable(final Collection<Type> types, final Collection<Qualifier> qualifiers,
+    public static <T> DefaultVariable<T> newVariable(final Collection<Type> types, final Qualifiers qualifiers,
                                                      final Class<? extends Annotation> scopeType) {
         return new DefaultVariable<>(types, qualifiers, scopeType);
     }
 
     private final Collection<Type> types;
-    private final Collection<Qualifier> qualifiers;
+    private final Qualifiers qualifiers;
     private final Class<? extends Annotation> scopeType;
 
-    private DefaultVariable(final Collection<Type> types, final Collection<Qualifier> qualifiers,
+    private DefaultVariable(final Collection<Type> types, final Qualifiers qualifiers,
                             final Class<? extends Annotation> scopeType) {
         this.types = Objects.requireNonNull(types);
         this.qualifiers = Objects.requireNonNull(qualifiers);
@@ -49,7 +49,7 @@ public class DefaultVariable<T> implements Variable<T> {
     }
 
     @Override
-    public Collection<Qualifier> getQualifiers() {
+    public Qualifiers getQualifiers() {
         return qualifiers;
     }
 
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/ElementManager.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/ElementManager.java
index cd9dea1..00aa67d 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/ElementManager.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/ElementManager.java
@@ -53,7 +53,7 @@ public interface ElementManager extends AutoCloseable {
      * @param element program element to extract qualifiers from
      * @return qualifiers present on the element
      */
-    Collection<Qualifier> getQualifiers(MetaElement<?> element);
+    Qualifiers getQualifiers(MetaElement<?> element);
 
     /**
      * Checks if a class has exactly one injectable constructor. A constructor is <i>injectable</i> if:
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/InjectionPoint.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/InjectionPoint.java
index f4e1de9..6e47521 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/InjectionPoint.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/InjectionPoint.java
@@ -20,7 +20,6 @@ package org.apache.logging.log4j.plugins.spi.model;
 import org.apache.logging.log4j.plugins.spi.bean.Bean;
 
 import java.lang.reflect.Type;
-import java.util.Collection;
 import java.util.Optional;
 
 /**
@@ -39,7 +38,7 @@ public interface InjectionPoint<T> {
      * Gets the qualifiers of this point. If no qualifiers other than {@link org.apache.logging.log4j.plugins.api.Named}
      * are present, then these qualifiers will also include {@link org.apache.logging.log4j.plugins.api.Default}.
      */
-    Collection<Qualifier> getQualifiers();
+    Qualifiers getQualifiers();
 
     /**
      * Gets the bean where this injection point is defined or empty for static methods and fields.
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Qualifier.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Qualifiers.java
similarity index 53%
rename from log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Qualifier.java
rename to log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Qualifiers.java
index fba378c..74c8d0c 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Qualifier.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Qualifiers.java
@@ -20,22 +20,80 @@ package org.apache.logging.log4j.plugins.spi.model;
 import org.apache.logging.log4j.plugins.api.AliasFor;
 import org.apache.logging.log4j.plugins.api.Default;
 import org.apache.logging.log4j.plugins.api.Ignore;
+import org.apache.logging.log4j.plugins.api.Named;
 import org.apache.logging.log4j.plugins.spi.InjectionException;
 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 
-// TODO: consider a composite class for qualifier collections or other potential ways to model this
-public final class Qualifier {
+/**
+ * Represents a normalized set of {@linkplain org.apache.logging.log4j.plugins.api.QualifierType qualifier annotations}.
+ */
+public final class Qualifiers {
+    private final Map<Class<? extends Annotation>, Map<String, Object>> qualifiers;
+
+    private Qualifiers(final Map<Class<? extends Annotation>, Map<String, Object>> qualifiers) {
+        this.qualifiers = qualifiers;
+    }
 
-    public static final Qualifier DEFAULT_QUALIFIER = new Qualifier(Default.class, Collections.emptyMap());
+    public boolean hasDefaultQualifier() {
+        return qualifiers.containsKey(Default.class);
+    }
 
-    public static Qualifier fromAnnotation(final Annotation annotation) {
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        final Qualifiers that = (Qualifiers) o;
+        return qualifiers.equals(that.qualifiers);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(qualifiers);
+    }
+
+    @Override
+    public String toString() {
+        return qualifiers.toString();
+    }
+
+    /**
+     * Creates a normalized Qualifiers instance from a collection of qualifier annotation instances.
+     */
+    public static Qualifiers fromQualifierAnnotations(final Collection<Annotation> annotations) {
+        final Map<Class<? extends Annotation>, Map<String, Object>> qualifiers = new HashMap<>(annotations.size());
+        for (final Annotation annotation : annotations) {
+            final Class<? extends Annotation> annotationType = annotation.annotationType();
+            final AliasFor alias = annotationType.getAnnotation(AliasFor.class);
+            final Class<? extends Annotation> qualifierType = alias != null ? alias.value() : annotationType;
+            qualifiers.put(qualifierType, getQualifierAttributes(annotation));
+        }
+        if (needsDefaultQualifier(qualifiers.keySet())) {
+            qualifiers.put(Default.class, Collections.emptyMap());
+        }
+        return new Qualifiers(Collections.unmodifiableMap(qualifiers));
+    }
+
+    private static boolean needsDefaultQualifier(final Collection<Class<? extends Annotation>> qualifierTypes) {
+        if (qualifierTypes.contains(Default.class)) {
+            return false;
+        }
+        for (final Class<? extends Annotation> qualifierType : qualifierTypes) {
+            if (qualifierType != Named.class) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static Map<String, Object> getQualifierAttributes(final Annotation annotation) {
         final Class<? extends Annotation> annotationType = annotation.annotationType();
         final Method[] elements = annotationType.getDeclaredMethods();
         final Map<String, Object> attributes = new HashMap<>(elements.length);
@@ -44,10 +102,7 @@ public final class Qualifier {
                 attributes.put(element.getName(), getAnnotationElement(annotation, element));
             }
         }
-        // FIXME: support default name for @Named when value is blank
-        final AliasFor alias = annotationType.getAnnotation(AliasFor.class);
-        final Class<? extends Annotation> qualifierType = alias != null ? alias.value() : annotationType;
-        return new Qualifier(qualifierType, Collections.unmodifiableMap(attributes));
+        return Collections.unmodifiableMap(attributes);
     }
 
     private static Object getAnnotationElement(final Annotation annotation, final Method element) {
@@ -60,44 +115,4 @@ public final class Qualifier {
                     e.getCause());
         }
     }
-
-    private final Class<? extends Annotation> qualifierType;
-    private final Map<String, Object> attributes;
-
-    private Qualifier(final Class<? extends Annotation> qualifierType, final Map<String, Object> attributes) {
-        this.qualifierType = qualifierType;
-        this.attributes = attributes;
-    }
-
-    public Class<? extends Annotation> getQualifierType() {
-        return qualifierType;
-    }
-
-    public Map<String, Object> getAttributes() {
-        return attributes;
-    }
-
-    @Override
-    public boolean equals(final Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        final Qualifier that = (Qualifier) o;
-        return qualifierType.equals(that.qualifierType) &&
-                attributes.equals(that.attributes);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(qualifierType, attributes);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append('@').append(qualifierType.getSimpleName());
-        if (!attributes.isEmpty()) {
-            sb.append(attributes);
-        }
-        return sb.toString();
-    }
 }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Variable.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Variable.java
index fa7e02e..bb8db73 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Variable.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/spi/model/Variable.java
@@ -35,7 +35,7 @@ public interface Variable<T> {
         return false;
     }
 
-    Collection<Qualifier> getQualifiers();
+    Qualifiers getQualifiers();
 
     Class<? extends Annotation> getScopeType();
 }
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/spi/model/QualifiersTest.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/spi/model/QualifiersTest.java
new file mode 100644
index 0000000..739ec6b
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/spi/model/QualifiersTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.spi.model;
+
+import org.apache.logging.log4j.plugins.api.AliasFor;
+import org.apache.logging.log4j.plugins.api.Named;
+import org.apache.logging.log4j.plugins.api.QualifierType;
+import org.junit.Test;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+
+public class QualifiersTest {
+    @Test
+    public void emptyQualifiersShouldContainDefaultQualifier() {
+        final Qualifiers qualifiers = Qualifiers.fromQualifierAnnotations(Collections.emptyList());
+        assertTrue(qualifiers.hasDefaultQualifier());
+    }
+
+    @Test
+    public void qualifiersWithNamedOnlyShouldContainDefaultQualifier() {
+        @Named
+        class Foo {
+        }
+        final Qualifiers qualifiers = Qualifiers.fromQualifierAnnotations(Arrays.asList(Foo.class.getAnnotations()));
+        assertTrue(qualifiers.hasDefaultQualifier());
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @QualifierType
+    public @interface Bar {
+    }
+
+    @Test
+    public void qualifiersWithNonDefaultQualifiersShouldNotContainDefaultQualifier() {
+        @Bar
+        class Foo {
+        }
+        final Qualifiers qualifiers = Qualifiers.fromQualifierAnnotations(Arrays.asList(Foo.class.getAnnotations()));
+        assertFalse(qualifiers.hasDefaultQualifier());
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @AliasFor(Bar.class)
+    public @interface BarAlias {
+    }
+
+    @Test
+    public void qualifiersShouldAccountForAliases() {
+        @Bar
+        class Foo {
+        }
+        @BarAlias
+        class FooAlias {
+        }
+        final Qualifiers foo = Qualifiers.fromQualifierAnnotations(Arrays.asList(Foo.class.getAnnotations()));
+        final Qualifiers fooAlias = Qualifiers.fromQualifierAnnotations(Arrays.asList(FooAlias.class.getAnnotations()));
+        assertEquals(foo, fooAlias);
+    }
+}
\ No newline at end of file