package br.com.caelum.iogi.reflection;
import br.com.caelum.iogi.DependenciesInjector;
import br.com.caelum.iogi.Instantiator;
import br.com.caelum.iogi.parameters.Parameters;
import br.com.caelum.iogi.spi.ParameterNamesProvider;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import net.vidageek.mirror.dsl.Mirror;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.util.*;
import java.util.Map.Entry;
import static br.com.caelum.iogi.util.IogiCollections.zip;
public class ClassConstructor {
private final Set<String> names;
private final Constructor<?> constructor;
public ClassConstructor(final Constructor<?> constructor, final ParameterNamesProvider parameterNamesProvider) {
this(constructor, Sets.newLinkedHashSet(parameterNamesProvider.lookupParameterNames(constructor)));
}
private ClassConstructor(final Constructor<?> constructor, final Set<String> names) {
this.constructor = constructor;
this.names = names;
}
public Set<String> getNames() {
return names;
}
public int size() {
return names.size();
}
public NewObject instantiate(final Instantiator<?> argumentsInstantiator, final Parameters parameters, DependenciesInjector dependenciesInjector) {
final List<Object> argumentValues = Lists.newArrayListWithCapacity(size());
final Collection<Target<?>> needDependency = notFulfilledBy(parameters);
for (final Target<?> target : argumentTargets()) {
Object value;
if (needDependency.contains(target))
value = dependenciesInjector.provide(target);
else
value = argumentsInstantiator.instantiate(target, parameters);
argumentValues.add(value);
}
Object newObjectValue = new Mirror().on(declaringClass()).invoke().constructor(constructor).withArgs(argumentValues.toArray());
return new NewObject(argumentsInstantiator, parameters.notUsedBy(this), newObjectValue);
}
public Collection<Target<?>> notFulfilledBy(final Parameters parameters) {
final ArrayList<Target<?>> unfulfilledParameterTargets = new ArrayList<Target<?>>();
for (final Target<?> parameterTarget : argumentTargets()) {
if (!parameters.hasRelatedTo(parameterTarget))
unfulfilledParameterTargets.add(parameterTarget);
}
return Collections.unmodifiableList(unfulfilledParameterTargets);
}
public List<Target<?>> argumentTargets() {
final Iterable<Type> types = Arrays.asList(constructor.getGenericParameterTypes());
final ArrayList<Target<?>> targets = Lists.newArrayList();
for (Entry<Type, String> parameter : zip(types, names)) {
targets.add(new Target<Object>(parameter.getKey(), parameter.getValue()));
}
return Collections.unmodifiableList(targets);
}
public Class<?> declaringClass() {
return constructor.getDeclaringClass();
}
@Override
public String toString() {
return "(" + Joiner.on(", ").join(names) + ")";
}
public boolean canInstantiateOrInject(final Parameters relevantParameters, DependenciesInjector dependenciesInjector) {
final Collection<Target<?>> uninstantiableByParameters = notFulfilledBy(relevantParameters);
final boolean canObtainDependencies = dependenciesInjector.canObtainDependenciesFor(uninstantiableByParameters);
return canObtainDependencies;
}
public Object construct(List<Object> argumentValues) {
return new Mirror().on(declaringClass()).invoke().constructor(constructor).withArgs(argumentValues.toArray());
}
}