Skip to content

Commit

Permalink
Merge branch '1.0.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
alanlit committed Oct 1, 2024
2 parents eaec43a + 9ed705c commit 61ef8b5
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 35 deletions.
9 changes: 5 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

group 'com.mentalresonance'
version '1.0.1'
version '1.0.2'

repositories {
mavenLocal()
Expand Down Expand Up @@ -43,7 +43,7 @@ publishing {
}

def testArgs = [
'-Xmx16g',
'-Xmx20g',
'--add-opens=java.base/java.lang=ALL-UNNAMED',
'--add-opens=java.base/java.math=ALL-UNNAMED',
'--add-opens=java.base/java.util=ALL-UNNAMED',
Expand All @@ -65,8 +65,9 @@ java {
}

tasks.withType(JavaCompile) {
options.debug = false
options.compilerArgs << '-g:none' // Ensures no debug information is included
options.debug = true
options.debugOptions.debugLevel = 'source,lines,vars'
// options.compilerArgs << '-g:none' // Ensures no debug information is included
}

javadoc.options {
Expand Down
78 changes: 75 additions & 3 deletions src/main/java/com/mentalresonance/dust/core/actors/Actor.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import static com.mentalresonance.dust.core.actors.SupervisionStrategy.*;
Expand Down Expand Up @@ -699,8 +700,7 @@ protected ActorRef actorOf(Props props, String name) throws ActorInstantiationEx
log.warn("Child %s of %s already exists !!".formatted(name, self.path));
return exists;
}
var cons = context.actorConstructors.get(props.actorClass);
Actor actor = (Actor)cons.newInstance(props.actorArgs);
Actor actor = createInstanceWithParameters(props.actorClass, props.actorArgs);
ActorRef ref = new ActorRef(self.path, name, context);

ref.props = props;
Expand All @@ -717,11 +717,83 @@ protected ActorRef actorOf(Props props, String name) throws ActorInstantiationEx
return actor.self;
}
catch (Exception e) {
e.printStackTrace();
log.error("Could not create Actor {} error: {}", props.actorClass, e.getMessage());
throw new ActorInstantiationException(e.getMessage());
}
}

Actor createInstanceWithParameters(Class<?> actorClass, Object[] args) throws Exception {
ArrayList<Class<?>> classList = new ArrayList<>();
classList.add(actorClass);
for (Object obj : args) {
if (obj != null) {
Class clazz = obj.getClass();
if (clazz.isPrimitive()) {
if (clazz == int.class) {
classList.add(Integer.class);
} else if (clazz == boolean.class) {
classList.add(Boolean.class);
} else if (clazz == byte.class) {
classList.add(Byte.class);
} else if (clazz == char.class) {
classList.add(Character.class);
} else if (clazz == double.class) {
classList.add(Double.class);
} else if (clazz == float.class) {
classList.add(Float.class);
} else if (clazz == long.class) {
classList.add(Long.class);
} else if (clazz == short.class) {
classList.add(Short.class);
} else if (clazz == void.class) {
classList.add(Void.class);
}
} else {
classList.add(clazz);
}
} else {
classList.add(null);
}
}
Constructor cons = context.actorConstructors.get(classList);
return (Actor) cons.newInstance(args);
}

private List<Class<?>> autoboxPrimitiveTypes(List<Class<?>> classList) {
List<Class<?>> autoboxedList = new ArrayList<>();

for (Class<?> clazz : classList) {
// Check if the class is a primitive type, and if so, add the corresponding wrapper class
if (clazz.isPrimitive()) {
if (clazz == int.class) {
autoboxedList.add(Integer.class);
} else if (clazz == boolean.class) {
autoboxedList.add(Boolean.class);
} else if (clazz == byte.class) {
autoboxedList.add(Byte.class);
} else if (clazz == char.class) {
autoboxedList.add(Character.class);
} else if (clazz == double.class) {
autoboxedList.add(Double.class);
} else if (clazz == float.class) {
autoboxedList.add(Float.class);
} else if (clazz == long.class) {
autoboxedList.add(Long.class);
} else if (clazz == short.class) {
autoboxedList.add(Short.class);
} else if (clazz == void.class) {
autoboxedList.add(Void.class);
}
} else {
// If it's not a primitive, just add the class as-is
autoboxedList.add(clazz);
}
}

return autoboxedList;
}


/**
* Convert a path into an ActorRef. Path can be of the form ..
* /.... in which case is absolute
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,100 @@ public ActorContext(ActorSystem system) {
this.system = system;
}

// Actor class -> Constructor for the Actor
/**
* Cache of Actor constructors. This is self loading and will get constructors on demand. Purely for
* speed.
* Key List<Class> is [ActorClass, Arg1Class, Arg2Class ..... ]
* Value is the constructor matching this signature
*/
public final LoadingCache<Class, Constructor> actorConstructors = Caffeine.newBuilder()
public final LoadingCache<List<Class<?>>, Constructor> actorConstructors = Caffeine.newBuilder()
.maximumSize(4096)
.build(k -> k.getConstructors()[0]);
.build(k -> constructorFor(k));

/**
* Appropriate constructor was not cached, so take list of [ActorClass, Arg1Class, Arg2Class..] and
* find the appropriate constructor.
* @param classAndArgs List of (already boxed) classes defining the call
* @return appropriate constructor
* @throws Exception if we cannot do this
*/
private Constructor<?> constructorFor(List<Class<?>> classAndArgs) throws Exception {
try {
List<Constructor<?>> constructors = Arrays.stream(classAndArgs.get(0).getConstructors()).toList();
List<Class<?>> argClasses = classAndArgs.size() > 1 ? classAndArgs.subList(1, classAndArgs.size()) : List.of();
List<Constructor> matchingConstructors = new ArrayList<>();

for (Constructor cons : constructors ) {
List<Class<?>> parameterTypes = autoboxPrimitiveTypes(cons.getParameterTypes());
if (areParametersMatching(parameterTypes, argClasses)) {
return cons;
}
}

throw new Exception("No constructor found for " + classAndArgs.get(0));
}
catch (Exception e) {
e.printStackTrace();
throw e;
}
}

/**
* Do parameter types in constructor match the classes of the arguments
* @param parameterTypes constructor types
* @param argsClasses argument types
* @return true or false
*/
private static boolean areParametersMatching(List<Class<?>> parameterTypes, List<Class<?>> argsClasses) {
if (parameterTypes.size() != argsClasses.size()) {
return false;
}
for (int i = 0; i < parameterTypes.size(); i++) {
Class<?> clazz = argsClasses.get(i);
if (clazz != null && !(parameterTypes.get(i).isAssignableFrom(clazz))) {
return false;
}
}
return true;
}

/**
* Primitive types (!) cannot be checked by isAssignableFrom, so we box them here if necessary
* @param classList array of types - possible some primitive
* @return list of types - boxed where necessary
*/
private List<Class<?>> autoboxPrimitiveTypes(Class<?>[] classList) {
List<Class<?>> autoboxedList = new ArrayList<>();

for (Class<?> clazz : classList) {
if (clazz.isPrimitive()) {
if (clazz == int.class) {
autoboxedList.add(Integer.class);
} else if (clazz == boolean.class) {
autoboxedList.add(Boolean.class);
} else if (clazz == byte.class) {
autoboxedList.add(Byte.class);
} else if (clazz == char.class) {
autoboxedList.add(Character.class);
} else if (clazz == double.class) {
autoboxedList.add(Double.class);
} else if (clazz == float.class) {
autoboxedList.add(Float.class);
} else if (clazz == long.class) {
autoboxedList.add(Long.class);
} else if (clazz == short.class) {
autoboxedList.add(Short.class);
} else if (clazz == void.class) {
autoboxedList.add(Void.class);
}
} else {
// If it's not a primitive, just add the class as-is
autoboxedList.add(clazz);
}
}

return autoboxedList;
}

/**
* Resolve a path to an ActorRef. The path must be rooted at '/'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ public void waitForDeath() throws Exception {

@Override
public String toString() {
return (null == mailBox) ? path + " [Remote]" : (null != props ? props.actorClass.getSimpleName() : "NULL") + ": " + path + " @" + Integer.toHexString(this.hashCode()) +" [Local]";
return path;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public abstract class PersistentServiceManagerActor extends PersistentActor {
* @param maxWorkers - max number of workers
* @return Props
*/
public static Props props(Props serviceProps, int maxWorkers) {
public static Props props(Props serviceProps, Integer maxWorkers) {
return Props.create(PersistentServiceManagerActor.class, serviceProps, maxWorkers, 5000L);
}

Expand All @@ -66,7 +66,7 @@ public static Props props(Props serviceProps, int maxWorkers) {
* @param delayMS - interval in ms between snapshot saves
* @return Props
*/
public static Props props(Props serviceProps, int maxWorkers, Long delayMS) {
public static Props props(Props serviceProps, Integer maxWorkers, Long delayMS) {
return Props.create(PersistentServiceManagerActor.class, serviceProps, maxWorkers, delayMS);
}

Expand All @@ -76,7 +76,7 @@ public static Props props(Props serviceProps, int maxWorkers, Long delayMS) {
* @param maxWorkers - max number of workers
* @param delayMS - interval in ms between snapshot saves
*/
public PersistentServiceManagerActor(Props serviceProps, int maxWorkers, Long delayMS) {
public PersistentServiceManagerActor(Props serviceProps, Integer maxWorkers, Long delayMS) {
this.serviceProps = serviceProps;
this.maxWorkers = maxWorkers;
this.delayMS = delayMS;
Expand All @@ -89,7 +89,7 @@ public PersistentServiceManagerActor(Props serviceProps, int maxWorkers, Long de
* @param serviceProps - Props for the service workers
* @param maxWorkers - max number of workers
*/
public PersistentServiceManagerActor(Props serviceProps, int maxWorkers) {
public PersistentServiceManagerActor(Props serviceProps, Integer maxWorkers) {
this(serviceProps, maxWorkers, 5000L);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class ServiceManagerActor extends Actor {
* @param maxWorkers max slots available
* @return Props
*/
public static Props props(Props serviceProps, int maxWorkers) {
public static Props props(Props serviceProps, Integer maxWorkers) {
return Props.create(ServiceManagerActor.class, serviceProps, maxWorkers);
}

Expand All @@ -64,7 +64,7 @@ public static Props props(Props serviceProps, int maxWorkers) {
* @param serviceProps the ServiceActor props
* @param maxWorkers max slots available
*/
public ServiceManagerActor(Props serviceProps, int maxWorkers) {
public ServiceManagerActor(Props serviceProps, Integer maxWorkers) {
this.serviceProps = serviceProps;
this.maxWorkers = maxWorkers;
currentWorkers = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class CompletionServiceManagerActor extends ServiceManagerActor {
* Constructor
* @param maxWorkers max workers for this service
*/
public CompletionServiceManagerActor(int maxWorkers) {
public CompletionServiceManagerActor(Integer maxWorkers) {
super(CompletionServiceActor.props(), maxWorkers);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ public class DeadLetterActor extends PubSubActor {
* @param logDeadLetters if true log dead letters
* @return Props
*/
public static Props props(boolean logDeadLetters) {
public static Props props(Boolean logDeadLetters) {
return Props.create(DeadLetterActor.class, logDeadLetters);
}

/**
* Constructor
* @param logDeadLetters if true log dead letters
*/
public DeadLetterActor(boolean logDeadLetters) {
public DeadLetterActor(Boolean logDeadLetters) {
this.logDeadLetters = logDeadLetters;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ public class SystemActor extends Actor {
* @param logDeadLetters if true dead letters are to be logged
* @return Props
*/
public static Props props(boolean logDeadLetters) {
public static Props props(Boolean logDeadLetters) {
return Props.create(SystemActor.class, logDeadLetters);
}

/**
* Constructor
* @param logDeadLetters if true dead letters are to be logged
*/
public SystemActor(boolean logDeadLetters) {
public SystemActor(Boolean logDeadLetters) {
this.logDeadLetters = logDeadLetters;
}

Expand Down
16 changes: 14 additions & 2 deletions src/test/groovy/TraitTest.groovy
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import com.mentalresonance.dust.core.actors.Actor
import com.mentalresonance.dust.core.actors.ActorTrait
import com.mentalresonance.dust.core.actors.Props
import com.mentalresonance.dust.core.actors.ActorSystem
import groovy.util.logging.Slf4j
Expand All @@ -10,6 +11,8 @@ import spock.lang.Specification
@Slf4j
class TraitTest extends Specification {

static boolean called = false

@Slf4j
static class Supervisor extends Actor implements TestTrait {

Expand All @@ -23,13 +26,22 @@ class TraitTest extends Specification {
}
}

static interface TestTrait extends ActorTrait {

default void callMe() {
System.out.println(String.format("MyActorRef - %s - MyActor %s", getSelf(), this))
called = true
}
}

def "Trait Test"() {
when:
ActorSystem system = new ActorSystem("TraitTest")
system.context.actorOf( Supervisor.props(), "trait")
Thread.sleep(500)
system.stop()
then:
true
then:
called
}

}
11 changes: 0 additions & 11 deletions src/test/java/TestTrait.java

This file was deleted.

0 comments on commit 61ef8b5

Please sign in to comment.