Skip to content

Commit

Permalink
Adding ClassPathSearch to search for implementation classes.
Browse files Browse the repository at this point in the history
Will allow lazy discovery of the actor set.
  • Loading branch information
DanielSperry committed Apr 4, 2015
1 parent e10d781 commit 089c740
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.ea.orbit.actors.runtime;

import com.ea.orbit.concurrent.ConcurrentHashSet;
import com.ea.orbit.util.ClassPath;

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

/**
* Internal class to locate interface implementations.
*/
public class ClassPathSearch
{
private final ConcurrentHashSet<String> unprocessed = new ConcurrentHashSet<>();

private final ConcurrentHashMap<Class<?>, Class<?>> concreteImplementations = new ConcurrentHashMap<>();
private Class<?>[] classesOfInterest;

public ClassPathSearch(Class<?>... classesOfInterest)
{
this.classesOfInterest = classesOfInterest;
// get all class names from class path
ClassPath.get().getAllResources().stream()
.map(r -> r.getResourceName())
.filter(rn -> rn.endsWith(".class"))
.map(rn -> rn.substring(0, rn.length() - 6).replace('/', '.'))
.forEach(cn -> unprocessed.add(cn));

}

public <T, R extends T> Class<R> findImplementation(Class<T> theInterface)
{
Class<?> implementationClass = concreteImplementations.get(theInterface);
if (implementationClass != null)
{
return (Class<R>) implementationClass;
}
if (unprocessed.size() == 0)
{
return null;
}

final String expectedName = theInterface.getName();
// cloning the list of unprocessed since it might be modified by the operations on the stream.

// 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));
})
.map(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))
{
// when searching for IHello1, must avoid:
// IHello1 <- IHello2
// IHello1 <- HelloImpl1
// IHello2 <- HelloImpl2 (wrong return, lest strict.

// 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))
{
return null;
}
}
// found the best match!
return clazz;
}
for (Class<?> base : classesOfInterest)
{
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
unprocessed.remove(cn);
return null;
}
catch (Throwable e)
{
// there is some problem with this class
// culling the list for the next search
unprocessed.remove(cn);
return null;
}
})
.filter(c -> c != null)
.findFirst()
.orElse(null);

if (implementationClass != null)
{
// cache the findings.
concreteImplementations.put(theInterface, implementationClass);
}
return (Class<R>) implementationClass;
}


/**
* Returns the size of the common start the two strings
* <p/>
* Example: {@code commonStart("ssssBBB", "ssCCC") == 2 }
*/
static int commonStart(String a, String b)
{
int i = 0, l = Math.min(a.length(), b.length());
for (; i < l && a.charAt(i) == b.charAt(i); i++)
{
}
return i;
}

/**
* Returns the size of the common end the two strings
* <p/>
* Example: {@code commonEnd("AAAAeeee", "BBBee") == 2 }
*/
static int commonEnd(String a, String b)
{
int c = 0;
int ia = a.length();
int ib = b.length();

for (; --ia >= 0 && --ib >= 0 && a.charAt(ia) == b.charAt(ib); )
{
c++;
}
return c;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.ea.orbit.actors.runtime;

import org.junit.Test;

import static com.ea.orbit.actors.runtime.ClassPathSearch.commonEnd;
import static com.ea.orbit.actors.runtime.ClassPathSearch.commonStart;
import static org.junit.Assert.assertEquals;

public class ClassPathSearchTest
{
@Test
public void testUtilityFunctions()
{
assertEquals(0, commonStart("BBB", "CCC"));
assertEquals(0, commonEnd("AAAA", "BBB"));

assertEquals(0, commonStart("BBB", ""));
assertEquals(0, commonEnd("AAAA", ""));
assertEquals(0, commonStart("", "A"));
assertEquals(0, commonEnd("", "A"));

assertEquals(1, commonStart("A", "A"));

assertEquals(1, commonStart("ssssBBB", "sCCC"));
assertEquals(2, commonStart("ssssBBB", "ssCCC"));
assertEquals(3, commonStart("ssssBBB", "sssCCC"));

assertEquals(1, commonStart("sBBB", "sssCCC"));
assertEquals(2, commonStart("ssBBB", "sssCCC"));
assertEquals(3, commonStart("sssBBB", "sssCCC"));

assertEquals(1, commonEnd("AAAAe", "BBBee"));
assertEquals(2, commonEnd("AAAAeeee", "BBBee"));
assertEquals(3, commonEnd("AAAAeeee", "BBBeee"));
}

public interface IHi
{
}

public class Hi implements IHi
{
}

@Test
public void testFind()
{
assertEquals(Hi.class, new ClassPathSearch().findImplementation(IHi.class));
}
}

0 comments on commit 089c740

Please sign in to comment.