/**
* Copyright (C) 2016 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 java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.Validate;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaMethod;
import org.jboss.errai.codegen.meta.MetaParameterizedType;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.Dependency;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.DependencyType;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.InjectableType;
import org.jboss.errai.ioc.rebind.ioc.graph.api.HasInjectableHandle;
import org.jboss.errai.ioc.rebind.ioc.graph.api.Injectable;
/**
*
* @author Max Barkley <mbarkley@redhat.com>
*/
final class GraphUtil {
private GraphUtil() {}
static void throwDuplicateConcreteInjectableException(final String name, final Injectable first,
final Injectable second) {
final String message = "Two concrete injectables exist with the same name (" + name + "):\n"
+ "\t" + first + "\n"
+ "\t" + second;
throw new RuntimeException(message);
}
static ProducerInstanceDependencyImpl findProducerInstanceDep(final InjectableImpl injectable) {
for (final BaseDependency dep : injectable.dependencies) {
if (dep.dependencyType.equals(DependencyType.ProducerMember)) {
return (ProducerInstanceDependencyImpl) dep;
}
}
throw new RuntimeException("Could not find producer member.");
}
static MetaMethod getOverridenMethod(final MetaMethod specializingMethod) {
final MetaClass[] producerParams = GraphUtil.getParameterTypes(specializingMethod);
MetaClass enclosingType = specializingMethod.getDeclaringClass();
MetaMethod specializedMethod = null;
while (specializedMethod == null && enclosingType.getSuperClass() != null) {
enclosingType = enclosingType.getSuperClass();
specializedMethod = enclosingType.getDeclaredMethod(specializingMethod.getName(), producerParams);
}
return specializedMethod;
}
static MetaClass[] getParameterTypes(final MetaMethod producerMethod) {
final MetaClass[] paramTypes = new MetaClass[producerMethod.getParameters().length];
for (int i = 0; i < paramTypes.length; i++) {
paramTypes[i] = producerMethod.getParameters()[i].getType();
}
return paramTypes;
}
/**
* Required so that subtypes get all the qualifiers of supertypes when there
* are multiple @Specializes in the hierarchy.
*/
static void sortSuperTypesBeforeSubtypes(final List<InjectableImpl> specializations) {
Collections.sort(specializations, new Comparator<InjectableImpl>() {
@Override
public int compare(final InjectableImpl c1, final InjectableImpl c2) {
return getScore(c1) - getScore(c2);
}
private int getScore(final InjectableImpl c) {
if (c.injectableType.equals(InjectableType.Producer)) {
return getDistanceFromObject(findProducerInstanceDep(c).producingMember.getDeclaringClass());
} else {
return getDistanceFromObject(c.type);
}
}
private int getDistanceFromObject(MetaClass type) {
int distance = 0;
for (; type.getSuperClass() != null; type = type.getSuperClass()) {
distance++;
}
return distance;
}
});
}
static String combineProblemMessages(final Collection<String> problems) {
final StringBuilder builder = new StringBuilder("The following problems were found:\n\n");
for (final String problem : problems) {
builder.append(problem)
.append("\n");
}
return builder.toString();
}
static Injectable getResolvedDependency(final Dependency dep, final Injectable depOwner) {
return Validate.notNull(dep.getInjectable(), "The dependency %s in %s should have already been resolved.", dep, depOwner);
}
static String buildMessageFromProblems(final List<String> dependencyProblems) {
final StringBuilder builder = new StringBuilder();
builder.append("The following dependency problems were found:\n");
for (final String problem : dependencyProblems) {
builder.append('\t')
.append(problem)
.append('\n');
}
return builder.toString();
}
static InjectableReference copyInjectableReference(final InjectableReference injectable) {
final InjectableReference copy = new InjectableReference(injectable.type, injectable.qualifier);
copy.linked.addAll(injectable.linked);
return copy;
}
static String unsatisfiedDependencyMessage(final BaseDependency dep, final Injectable concrete,
final Collection<Injectable> resolvedDisabledBeans) {
final StringBuilder message = new StringBuilder()
.append("Unsatisfied ")
.append(dep.dependencyType.toString().toLowerCase())
.append(" dependency ")
.append(dep.injectable)
.append(" for ")
.append(concrete)
.append('.');
if (!resolvedDisabledBeans.isEmpty()) {
message.append(" Some beans were found that satisfied this dependency, but must be enabled:\n");
resolvedDisabledBeans.stream().forEach(inj -> message
.append(inj.getInjectedType().getFullyQualifiedName()).append('\n'));
}
return message.toString();
}
static String ambiguousDependencyMessage(final BaseDependency dep, final Injectable concrete, final List<InjectableImpl> resolved) {
final StringBuilder messageBuilder = new StringBuilder();
messageBuilder.append("Ambiguous resolution for ")
.append(dep.dependencyType.toString().toLowerCase())
.append(" ")
.append(dep.injectable)
.append(" in ")
.append(concrete)
.append(".\n")
.append("Resolved types:\n")
.append(resolved.get(0));
for (int i = 1; i < resolved.size(); i++) {
messageBuilder.append(", ")
.append(resolved.get(i));
}
return messageBuilder.toString();
}
static boolean candidateSatisfiesInjectable(final InjectableReference injectableReference,
final HasInjectableHandle candidate, final boolean considerTypeParameters) {
return qualifiersMatch(injectableReference, candidate)
&& (!considerTypeParameters || typeParametersMatch(injectableReference, candidate))
&& notSameReference(injectableReference, candidate);
}
static boolean candidateSatisfiesInjectable(final InjectableReference injectableReference,
final HasInjectableHandle candidate) {
return candidateSatisfiesInjectable(injectableReference, candidate, true);
}
private static boolean notSameReference(final InjectableReference injectableReference, final HasInjectableHandle candidate) {
return !candidate.equals(injectableReference);
}
private static boolean typeParametersMatch(final InjectableReference injectableReference, final HasInjectableHandle candidate) {
return GraphUtil.hasAssignableTypeParameters(candidate.getInjectedType(), injectableReference.type);
}
private static boolean qualifiersMatch(final InjectableReference injectableReference, final HasInjectableHandle candidate) {
return injectableReference.qualifier.isSatisfiedBy(candidate.getQualifier());
}
static boolean hasAssignableTypeParameters(final MetaClass fromType, final MetaClass toType) {
final MetaParameterizedType toParamType = toType.getParameterizedType();
final Optional<MetaParameterizedType> fromParamType = GraphUtil.getFromTypeParams(fromType, toType);
return toParamType == null || fromParamType.map(type -> toParamType.isAssignableFrom(type)).orElse(true);
}
static Optional<MetaParameterizedType> getFromTypeParams(final MetaClass fromType, final MetaClass toType) {
MetaClass parameterContainingType = null;
if (toType.isInterface()) {
if (fromType.getFullyQualifiedName().equals(toType.getFullyQualifiedName())) {
parameterContainingType = fromType;
}
else for (final MetaClass type : fromType.getAllSuperTypesAndInterfaces()) {
if (type.isInterface() && type.getFullyQualifiedName().equals(toType.getFullyQualifiedName())) {
parameterContainingType = type;
break;
}
}
} else {
MetaClass clazz = fromType;
do {
if (clazz.getFullyQualifiedName().equals(toType.getFullyQualifiedName())) {
parameterContainingType = clazz;
break;
}
clazz = clazz.getSuperClass();
} while (!clazz.getFullyQualifiedName().equals("java.lang.Object"));
}
if (parameterContainingType == null) {
final String classOrIface = (toType.isInterface() ? "interface" : "class");
throw new RuntimeException("Could not find " + classOrIface + " " + toType.getFullyQualifiedName()
+ " through type " + fromType.getFullyQualifiedName());
}
else if (parameterContainingType.getParameterizedType() != null) {
return Optional.of(parameterContainingType.getParameterizedType());
}
else {
return Optional.empty();
}
}
}