-
Notifications
You must be signed in to change notification settings - Fork 173
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding ClassPathSearch to search for implementation classes.
Will allow lazy discovery of the actor set.
- Loading branch information
1 parent
e10d781
commit 089c740
Showing
2 changed files
with
196 additions
and
0 deletions.
There are no files selected for viewing
146 changes: 146 additions & 0 deletions
146
actors/core/src/main/java/com/ea/orbit/actors/runtime/ClassPathSearch.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
||
} |
50 changes: 50 additions & 0 deletions
50
actors/core/src/test/java/com/ea/orbit/actors/runtime/ClassPathSearchTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |