/* * Copyright (C) 2015 Red Hat, Inc. and/or its affiliates. * * 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.jboss.errai.ioc.rebind.ioc.graph.impl; import static org.jboss.errai.ioc.util.GeneratedNamesUtil.qualifiedClassNameToIdentifier; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.enterprise.inject.Any; import javax.enterprise.inject.Default; import javax.enterprise.inject.Stereotype; import javax.inject.Named; import org.jboss.errai.codegen.meta.HasAnnotations; import org.jboss.errai.codegen.meta.MetaClass; import org.jboss.errai.codegen.meta.MetaClassFactory; import org.jboss.errai.codegen.meta.MetaClassMember; import org.jboss.errai.codegen.meta.MetaParameter; import org.jboss.errai.codegen.util.CDIAnnotationUtils; import org.jboss.errai.ioc.rebind.ioc.graph.api.Qualifier; import org.jboss.errai.ioc.rebind.ioc.graph.api.QualifierFactory; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; /** * Default implementation of {@link QualifierFactory}. Adheres to the CDI spec * regarding what qualifiers injectables and injection points have. * * @author Max Barkley <mbarkley@redhat.com> */ public class DefaultQualifierFactory implements QualifierFactory { private static final Any ANY = new Any() { @Override public Class<? extends Annotation> annotationType() { return Any.class; } @Override public String toString() { return "@Any"; } @Override public boolean equals(final Object obj) { return obj instanceof Any; } }; private static final AnnotationWrapper ANY_WRAPPER = new AnnotationWrapper(ANY); private static final Default DEFAULT = new Default() { @Override public Class<? extends Annotation> annotationType() { return Default.class; } @Override public String toString() { return "@Default"; } @Override public boolean equals(final Object obj) { return obj instanceof Default; } }; private static final AnnotationWrapper DEFAULT_WRAPPER = new AnnotationWrapper(DEFAULT); private static final Qualifier UNIVERSAL = new Universal(); private final Map<SortedSet<AnnotationWrapper>, NormalQualifier> qualifiers = new HashMap<>(); @Override public Qualifier forSource(final HasAnnotations annotated) { final SortedSet<AnnotationWrapper> annos = getRawQualifiers(annotated); maybeAddDefaultForSource(annos); annos.add(ANY_WRAPPER); return getOrCreateQualifier(annos); } @Override public Qualifier forSink(final HasAnnotations annotated) { final SortedSet<AnnotationWrapper> annos = getRawQualifiers(annotated); maybeAddDefaultForSink(annos); return getOrCreateQualifier(annos); } private NormalQualifier getOrCreateQualifier(final SortedSet<AnnotationWrapper> annos) { NormalQualifier qualifier = qualifiers.get(annos); if (qualifier == null) { qualifier = new NormalQualifier(annos); qualifiers.put(annos, qualifier); } return qualifier; } private SortedSet<AnnotationWrapper> getRawQualifiers(final HasAnnotations annotated) { final SortedSet<AnnotationWrapper> annos = new TreeSet<>(); for (final Annotation anno : annotated.getAnnotations()) { if (anno.annotationType().isAnnotationPresent(javax.inject.Qualifier.class)) { if (anno.annotationType().equals(Named.class) && ((Named) anno).value().equals("")) { annos.add(createNamed(annotated)); } else { annos.add(new AnnotationWrapper(anno)); } } else if (anno.annotationType().isAnnotationPresent(Stereotype.class)) { annos.addAll(getRawQualifiers(MetaClassFactory.get(anno.annotationType()))); } } return annos; } private AnnotationWrapper createNamed(final HasAnnotations annotated) { final String rawName; if (annotated instanceof MetaClassMember) { rawName = ((MetaClassMember) annotated).getName(); } else if (annotated instanceof MetaClass) { rawName = ((MetaClass) annotated).getName(); } else if (annotated instanceof MetaParameter) { rawName = ((MetaParameter) annotated).getName(); } else { throw new RuntimeException("Unrecognized annotated type " + annotated.getClass().getName()); } return createNamed(CDIAnnotationUtils.formatDefaultElName(rawName)); } private AnnotationWrapper createNamed(final String defaultName) { return new AnnotationWrapper(new Named() { @Override public Class<? extends Annotation> annotationType() { return Named.class; } @Override public String value() { return defaultName; } @Override public String toString() { return "@javax.inject.Named(value=" + defaultName + ")"; } }); } private void maybeAddDefaultForSource(final Set<AnnotationWrapper> annos) { if (annos.isEmpty() || onlyContainsNamed(annos)) { annos.add(DEFAULT_WRAPPER); } } private void maybeAddDefaultForSink(final Set<AnnotationWrapper> annos) { if (annos.isEmpty()) { annos.add(DEFAULT_WRAPPER); } } private boolean onlyContainsNamed(final Set<AnnotationWrapper> annos) { return annos.size() == 1 && annos.iterator().next().anno.annotationType().equals(Named.class); } @Override public Qualifier forDefault() { return getOrCreateQualifier(new TreeSet<>(Arrays.asList(DEFAULT_WRAPPER, ANY_WRAPPER))); } @Override public Qualifier forUniversallyQualified() { return UNIVERSAL; } @Override public Qualifier combine(final Qualifier q1, final Qualifier q2) { if (q1 instanceof Universal || q2 instanceof Universal) { return UNIVERSAL; } else if (q1 instanceof NormalQualifier && q2 instanceof NormalQualifier) { return combineNormal((NormalQualifier) q1, (NormalQualifier) q2); } else { throw new RuntimeException("At least one unrecognized qualifier implementation: " + q1.getClass().getName() + " and " + q2.getClass().getName()); } } private Qualifier combineNormal(final NormalQualifier q1, final NormalQualifier q2) { final Multimap<Class<? extends Annotation>, AnnotationWrapper> allAnnosByType = HashMultimap.create(); for (final AnnotationWrapper wrapper : q1.annotations) { allAnnosByType.put(wrapper.anno.annotationType(), wrapper); } for (final AnnotationWrapper wrapper : q2.annotations) { allAnnosByType.put(wrapper.anno.annotationType(), wrapper); } for (final Class<? extends Annotation> annoType : allAnnosByType.keySet()) { if (allAnnosByType.get(annoType).size() == 2) { final Iterator<AnnotationWrapper> iter = allAnnosByType.get(annoType).iterator(); throw new RuntimeException("Found two annotations of same type but with different values:\n\t" + iter.next() + "\n\t" + iter.next()); } } return getOrCreateQualifier(new TreeSet<>(allAnnosByType.values())); } private static final class NormalQualifier implements Qualifier { private final Set<AnnotationWrapper> annotations; private String identifier; private NormalQualifier(final Set<AnnotationWrapper> set) { this.annotations = set; } @Override public boolean isDefaultQualifier() { return annotations.size() == 2 && annotations.contains(ANY_WRAPPER) && annotations.contains(DEFAULT_WRAPPER); } @Override public boolean isSatisfiedBy(final Qualifier other) { if (other instanceof Universal) { return true; } else if (other instanceof NormalQualifier) { return ((NormalQualifier) other).annotations.containsAll(annotations); } else { throw new RuntimeException("Unrecognized qualifier type: " + other.getClass().getName()); } } @Override public String getIdentifierSafeString() { if (identifier == null) { final StringBuilder builder = new StringBuilder(); for (final AnnotationWrapper wrapper : annotations) { builder.append(qualifiedClassNameToIdentifier(wrapper.anno.annotationType())) .append("__"); } // Remove last delimeter if (annotations.size() > 0) { builder.delete(builder.length()-2, builder.length()); } identifier = builder.toString(); } return identifier; } @Override public String getName() { for (final AnnotationWrapper wrapper : annotations) { if (wrapper.anno.annotationType().equals(Named.class)) { return ((Named) wrapper.anno).value(); } } return null; } @Override public String toString() { return annotations.stream().map(AnnotationWrapper::toString).reduce((s1, s2) -> s1 + " " + s2).orElse(""); } @Override public int hashCode() { return annotations.hashCode(); } @Override public boolean equals(final Object obj) { if (!(obj instanceof NormalQualifier)) { return false; } final NormalQualifier other = (NormalQualifier) obj; return annotations.equals(other.annotations); } @Override public Iterator<Annotation> iterator() { final Iterator<AnnotationWrapper> iter = annotations.iterator(); return new Iterator<Annotation>() { @Override public boolean hasNext() { return iter.hasNext(); } @Override public Annotation next() { return iter.next().anno; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } } private static final class Universal implements Qualifier { @Override public boolean isSatisfiedBy(final Qualifier other) { return other == this; } @Override public boolean isDefaultQualifier() { return false; } @Override public String getIdentifierSafeString() { return "Universal"; } @Override public String toString() { return "@" + getIdentifierSafeString(); } @Override public boolean equals(final Object obj) { return obj instanceof Universal; } @Override public String getName() { return null; } @Override public Iterator<Annotation> iterator() { return Collections.<Annotation>emptyList().iterator(); } } private static final class AnnotationWrapper implements Comparable<AnnotationWrapper> { private final Annotation anno; private AnnotationWrapper(final Annotation anno) { this.anno = anno; } @Override public int hashCode() { return anno.annotationType().hashCode(); } @Override public boolean equals(final Object obj) { if (!(obj instanceof AnnotationWrapper)) { return false; } final AnnotationWrapper other = (AnnotationWrapper) obj; return CDIAnnotationUtils.equals(anno, other.anno); } @Override public String toString() { return anno.toString(); } @Override public int compareTo(final AnnotationWrapper o) { final int compareTo = anno.annotationType().getName().compareTo(o.anno.annotationType().getName()); if (compareTo != 0) { return compareTo; } else if (equals(o)) { return 0; } else { // Arbitrary stable ordering for annotations of same type with different values. return anno.toString().compareTo(o.anno.toString()); } } } }