/*
* Quasar: lightweight threads and actors for the JVM.
* Copyright (c) 2013-2014, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.actors;
import co.paralleluniverse.common.reflection.ReflectionUtil;
import com.google.common.reflect.TypeToken;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
/**
* A specification of how to construct an actor
*
* @author pron
*/
public class ActorSpec<T extends Actor<Message, V>, Message, V> implements ActorBuilder<Message, V> {
/**
* Creates an {@code ActorSpec}
*
* @param type the actor's type
* @param params the actor's constructor parameters.
*/
public static <Message, V, T extends Actor<Message, V>> ActorSpec<T, Message, V> of(Class<T> type, Object... params) {
return new ActorSpec<>(type, params);
}
/**
* Creates an {@code ActorSpec}
*
* @param type the actor's type
* @param params the actor's constructor parameters.
*/
public static <Message, V, T extends Actor<Message, V>> ActorSpec<T, Message, V> of(TypeToken<T> type, Object... params) {
return new ActorSpec<T, Message, V>((Class<T>) type.getRawType(), params);
}
private final AtomicReference<Class<T>> classRef;
private final String className;
private final Object[] params;
private Class<?>[] ctorParamTypes;
private Constructor<T> ctor;
/**
* Specifies an actor of a given type and given constructor parameters.
*
* @param className the name of the actor class
* @param params the parameters to pass to the actor's constructors
*/
public ActorSpec(String className, Object[] params) {
this((Constructor<T>) matchingConstructor(currentClassFor(className), params), params, false);
}
/**
* Specifies an actor of a given type and given constructor parameters.
*
* @param type the type of the actor
* @param params the parameters to pass to the actor's constructors
*/
public ActorSpec(Class<T> type, Object[] params) {
this(matchingConstructor(type, params), params, false);
}
/**
* Specifies an actor with given constructor and given constructor parameters.
*
* @param ctor the actor's constructor
* @param params the parameters to pass to the actor's constructors
*/
public ActorSpec(Constructor<T> ctor, Object[] params) {
this(ctor, params, false);
}
private ActorSpec(Constructor<T> ctor, Object[] params, boolean ignore) {
this.className = ctor.getDeclaringClass().getName();
this.classRef = (AtomicReference<Class<T>>) (Object) ActorLoader.getClassRef(className);
this.params = Arrays.copyOf(params, params.length);
this.ctor = ctor;
ctor.setAccessible(true);
}
private void updateConstructor() {
try {
this.ctor = ActorLoader.currentClassFor(ctor.getDeclaringClass()).getConstructor(ctor.getParameterTypes());
ctor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
private static <T> Constructor<T> matchingConstructor(Class<T> type, Object[] params) {
final Constructor<T> ctor = ReflectionUtil.getMatchingConstructor(type, ReflectionUtil.getTypes(params));
if (ctor == null)
throw new IllegalArgumentException("No constructor for type " + type.getName() + " was found to match parameters " + Arrays.toString(params));
return ctor;
}
@Override
public T build() {
if (classRef.get() != ctor.getDeclaringClass())
updateConstructor();
try {
T instance = ctor.newInstance(params);
instance.setSpec(this);
return instance;
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
final String ps = Arrays.toString(params);
return "ActorSpec{" + ctor.getName() + '(' + ps.substring(1, ps.length() - 1) + ")}";
}
private static Class<?> currentClassFor(String className) {
try {
return ActorLoader.currentClassFor(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}