Skip to content

Commit

Permalink
Added smart lazy actor implementation discovery.
Browse files Browse the repository at this point in the history
No need to depend on code generation or application hints for fast load times.
  • Loading branch information
DanielSperry committed Apr 4, 2015
1 parent 089c740 commit 08eb390
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 120 deletions.
@@ -0,0 +1,9 @@
package com.ea.orbit.actors.providers;

import com.ea.orbit.actors.IActor;
import com.ea.orbit.actors.providers.IOrbitProvider;

public interface IActorClassFinder extends IOrbitProvider
{
<T extends IActor> Class<? extends T> findActorImplementation(Class<T> iActorInterface);
}
Expand Up @@ -3,6 +3,9 @@
import com.ea.orbit.concurrent.ConcurrentHashSet;
import com.ea.orbit.util.ClassPath;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

Expand All @@ -11,6 +14,7 @@
*/
public class ClassPathSearch
{
private static final Logger logger = LoggerFactory.getLogger(ClassPathSearch.class);
private final ConcurrentHashSet<String> unprocessed = new ConcurrentHashSet<>();

private final ConcurrentHashMap<Class<?>, Class<?>> concreteImplementations = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -43,47 +47,59 @@ public <T, R extends T> Class<R> findImplementation(Class<T> theInterface)
final String expectedName = theInterface.getName();
// cloning the list of unprocessed since it might be modified by the operations on the stream.

if (logger.isDebugEnabled())
{
logger.debug("Searching implementation class for: " + theInterface);
}
// searching
implementationClass = Stream.of(unprocessed.toArray())
.map(o -> (String) o)
.sorted((a, b) -> {
// order by closest name to the interface, closest beginning then closest ending.
int sa = commonStart(expectedName, a);
int sb = commonStart(expectedName, b);
return (sa != sb) ? (sb - sa) : (commonEnd(expectedName, b) - commonEnd(expectedName, a));
// order by closest name to the interface.
int sa = commonStart(expectedName, a) + commonEnd(expectedName, a);
int sb = commonStart(expectedName, b) + commonEnd(expectedName, b);
return (sa != sb) ? (sb - sa) : a.length() - b.length();
})
// use this for development: .peek(System.out::println)
.map(cn -> {
if (logger.isDebugEnabled())
{
logger.debug("Checking: " + cn);
}
// this returns non null if there is a match
// it also culls the list
try
{
Class clazz = Class.forName(cn);
if (!clazz.isInterface() && theInterface.isAssignableFrom(clazz))
if (!clazz.isInterface())
{
// when searching for IHello1, must avoid:
// IHello1 <- IHello2
// IHello1 <- HelloImpl1
// IHello2 <- HelloImpl2 (wrong return, lest strict.
if (theInterface.isAssignableFrom(clazz))
{
// when searching for IHello1, must avoid:
// IHello1 <- IHello2
// IHello1 <- HelloImpl1
// IHello2 <-s HelloImpl2 (wrong return, lest strict.

// However the application **should not** do this kind of class tree.
// However the application **should not** do this kind of class tree.

// Important: this makes ClassPathSearch non generic.
for (Class<?> i : clazz.getInterfaces())
{
if (i != theInterface && theInterface.isAssignableFrom(i))
// Important: this makes ClassPathSearch non generic.
for (Class<?> i : clazz.getInterfaces())
{
return null;
if (i != theInterface && theInterface.isAssignableFrom(i))
{
return null;
}
}
// found the best match!
return clazz;
}
// found the best match!
return clazz;
}
for (Class<?> base : classesOfInterest)
{
if (base.isAssignableFrom(clazz))
for (Class<?> base : classesOfInterest)
{
// keep the classes that are part of the classes of interest list
return null;
if (base.isAssignableFrom(clazz))
{
// keep the classes that are part of the classes of interest list
return null;
}
}
}
// culling the list for the next search
Expand Down
@@ -0,0 +1,116 @@
package com.ea.orbit.actors.runtime;

import com.ea.orbit.actors.IActor;
import com.ea.orbit.actors.providers.IActorClassFinder;
import com.ea.orbit.concurrent.Task;
import com.ea.orbit.exception.UncheckedException;
import com.ea.orbit.util.ClassPath;
import com.ea.orbit.util.IOUtils;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class ActorClassFinder implements IActorClassFinder
{
private static final ClassPathSearch search = new ClassPathSearch(IActor.class);

private ConcurrentHashMap<Class<?>, Class<?>> cache = new ConcurrentHashMap<>();
private Execution execution;

public ActorClassFinder(Execution execution)
{
this.execution = execution;
}

public ActorClassFinder()
{

}

@Override
public <T extends IActor> Class<? extends T> findActorImplementation(Class<T> iActorInterface)
{
Class<?> r = cache.get(iActorInterface);
return r != null ? (Class<? extends T>) r : search.findImplementation(iActorInterface);
}

@Override
public Task<?> start()
{
if (execution == null)
{
return Task.done();
}
try
{
if (execution.getAutoDiscovery())
{
final List<ClassPath.ResourceInfo> actorInterfacesRes = ClassPath.get().getAllResources().stream()
.filter(r -> r.getResourceName().startsWith("META-INF/orbit/actors/interfaces")).collect(Collectors.toList());
final List<ClassPath.ResourceInfo> actorClassesRes = ClassPath.get().getAllResources().stream()
.filter(r -> r.getResourceName().startsWith("META-INF/orbit/actors/classes")).collect(Collectors.toList());

for (ClassPath.ResourceInfo irs : actorInterfacesRes)
{
// pre register factories
String nameFactoryName = IOUtils.toString(irs.url().openStream());
ActorFactory<?> factory = (ActorFactory<?>) classForName(nameFactoryName).newInstance();
execution.registerFactory(factory);
}

for (ClassPath.ResourceInfo irs : actorClassesRes)
{
String className = irs.getResourceName().substring("META-INF/orbit/actors/classes".length() + 1);
Class<?> actorClass = classForName(className);
cacheBestInterface(actorClass);
}
}
List<Class<?>> actorClasses = execution.getActorClasses();
if (actorClasses != null && !actorClasses.isEmpty())
{
for (Class<?> actorClass : actorClasses)
{
cacheBestInterface(actorClass);
}
}
}

catch (Throwable e)
{
throw new UncheckedException(e);
}
return Task.done();
}

private void cacheBestInterface(Class<?> actorClass)
{
Class<?> bestInterface = null;
for (Class<?> interfaceClass : actorClass.getInterfaces())
{
if (IActor.class.isAssignableFrom(interfaceClass) && interfaceClass != IActor.class)
{
bestInterface = (bestInterface == null) ? interfaceClass
: bestInterface.isAssignableFrom(interfaceClass) ? interfaceClass
: bestInterface;
}
}
if (bestInterface != null)
{
cache.put(actorClass, bestInterface);
}
}

private Class<?> classForName(String className)
{
try
{
return Class.forName(className);
}
catch (ClassNotFoundException e)
{
throw new UncheckedException(e);
}
}

}

0 comments on commit 08eb390

Please sign in to comment.