/**
*
* Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved.
*
* 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 com.speedment.common.injector.internal;
import com.speedment.common.injector.Injector;
import com.speedment.common.injector.InjectorBuilder;
import com.speedment.common.injector.State;
import com.speedment.common.injector.annotation.Inject;
import com.speedment.common.injector.annotation.WithState;
import com.speedment.common.injector.dependency.DependencyGraph;
import com.speedment.common.injector.dependency.DependencyNode;
import com.speedment.common.injector.exception.NotInjectableException;
import com.speedment.common.injector.execution.Execution;
import com.speedment.common.injector.execution.Execution.ClassMapper;
import com.speedment.common.injector.internal.util.InjectorUtil;
import static com.speedment.common.injector.internal.util.InjectorUtil.findIn;
import static com.speedment.common.injector.internal.util.PrintUtil.horizontalLine;
import static com.speedment.common.injector.internal.util.PrintUtil.limit;
import static com.speedment.common.injector.internal.util.PropertiesUtil.configureParams;
import static com.speedment.common.injector.internal.util.ReflectionUtil.traverseFields;
import com.speedment.common.logger.Level;
import com.speedment.common.logger.Logger;
import com.speedment.common.logger.LoggerManager;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import static java.util.Objects.requireNonNull;
import java.util.concurrent.atomic.AtomicBoolean;
import static java.util.stream.Collectors.toSet;
import java.util.stream.Stream;
/**
* The default implementation of the {@link Injector} interface.
*
* @author Emil Forslund
* @since 3.0.0
*/
public final class InjectorImpl implements Injector {
/**
* Create a new {@link InjectorBuilder} using the default implementation and
* default {@code ClassLoader}.
*
* @return the injector builder
*/
public static InjectorBuilder builder() {
return new InjectorBuilderImpl();
}
/**
* Create a new {@link InjectorBuilder} using the default implementation but
* with a specific {@code ClassLoader}.
*
* @param classLoader the class loader to use
* @return the injector builder
*/
public static InjectorBuilder builder(ClassLoader classLoader) {
return new InjectorBuilderImpl(classLoader);
}
public final static Logger LOGGER =
LoggerManager.getLogger(InjectorImpl.class);
private final Set<Class<?>> injectables;
private final List<Object> instances;
private final Properties properties;
private final ClassLoader classLoader;
private final DependencyGraph graph;
private final InjectorBuilder builder;
InjectorImpl(
Set<Class<?>> injectables,
List<Object> instances,
Properties properties,
ClassLoader classLoader,
DependencyGraph graph,
InjectorBuilder builder) {
this.injectables = requireNonNull(injectables);
this.instances = requireNonNull(instances);
this.properties = requireNonNull(properties);
this.classLoader = requireNonNull(classLoader);
this.graph = requireNonNull(graph);
this.builder = requireNonNull(builder);
}
@Override
public <T> Stream<T> stream(Class<T> type) {
return findAll(type);
}
@Override
public <T> T getOrThrow(Class<T> type) throws IllegalArgumentException {
return find(type, true);
}
@Override
public <T> Optional<T> get(Class<T> type) {
return Optional.ofNullable(find(type, false));
}
@Override
public Stream<Class<?>> injectables() {
return injectables.stream();
}
@Override
public <T> T inject(T instance) {
injectFields(instance);
configureParams(instance, properties);
return instance;
}
@Override
public ClassLoader classLoader() {
return classLoader;
}
@Override
public void stop() {
final AtomicBoolean hasAnythingChanged = new AtomicBoolean();
// Create ClassMapper
final ClassMapper classMapper = new ClassMapper() {
@Override
public <T> T apply(Class<T> type) throws NotInjectableException {
return find(type, true);
}
};
// Loop until all nodes have been started.
Set<DependencyNode> unfinished;
while (!(unfinished = graph.nodes()
.filter(n -> n.getCurrentState() != State.STOPPED)
.collect(toSet())).isEmpty()) {
hasAnythingChanged.set(false);
unfinished.forEach(n -> {
// Check if all its dependencies have been satisfied.
// TODO: Dependencies should be resolved in the opposite order
// when stopping.
if (n.canBe(State.STOPPED)) {
LOGGER.debug(horizontalLine());
// Retreive the instance for that node
final Object inst = find(n.getRepresentedType(), true);
// Execute all the executions for the next step.
n.getExecutions().stream()
.filter(e -> e.getState() == State.STOPPED)
.map(exec -> {
@SuppressWarnings("unchecked")
final Execution<Object> casted =
(Execution<Object>) exec;
return casted;
})
.forEach(exec -> {
// We might want to log exactly which steps we
// have completed.
if (LOGGER.getLevel()
.isEqualOrLowerThan(Level.DEBUG)) {
LOGGER.debug(
"| -> %-76s |",
limit(exec.toString(), 76)
);
}
try {
exec.invoke(inst, classMapper);
} catch (final IllegalAccessException
| IllegalArgumentException
| InvocationTargetException ex) {
throw new RuntimeException(ex);
}
});
// Update its state to the new state.
n.setState(State.STOPPED);
hasAnythingChanged.set(true);
LOGGER.debug(
"| %-66s %12s |",
n.getRepresentedType().getSimpleName(),
State.STOPPED.name()
);
}
});
if (!hasAnythingChanged.get()) {
throw new IllegalStateException(
"Injector appears to be stuck in an infinite loop. The " +
"following componenets have not been stopped: " +
unfinished.stream()
.map(DependencyNode::getRepresentedType)
.map(Class::getSimpleName)
.collect(toSet())
);
}
}
LOGGER.debug(horizontalLine());
LOGGER.debug(
"| %-79s |",
"All " + instances.size() + " components have been stopped!"
);
LOGGER.debug(horizontalLine());
}
@Override
public InjectorBuilder newBuilder() {
return builder;
}
private <T> Stream<T> findAll(Class<T> type) {
return InjectorUtil.findAll(type, this, instances);
}
private <T> T find(Class<T> type, boolean required) {
return findIn(type, this, instances, required);
}
private <T> void injectFields(T instance) {
requireNonNull(instance);
traverseFields(instance.getClass())
.filter(f -> f.isAnnotationPresent(Inject.class))
.distinct()
.forEachOrdered(field -> {
final Object value;
if (Injector.class.isAssignableFrom(field.getType())) {
value = this;
} else {
value = find(
field.getType(),
field.getAnnotation(WithState.class) != null
);
}
field.setAccessible(true);
try {
field.set(instance, value);
} catch (final IllegalAccessException ex) {
final String err = "Could not access field '" +
field.getName() +
"' in class '" + value.getClass().getName() +
"' of type '" + field.getType() + "'.";
LOGGER.error(ex, err);
throw new RuntimeException(err, ex);
}
});
}
}