Skip to content

Commit

Permalink
Performance optimizations (most notably injectfields).
Browse files Browse the repository at this point in the history
Number of iterations increased in startup comparison test
  • Loading branch information
zsoltconax committed Oct 6, 2015
1 parent 06c7143 commit 1eb2ccc
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 246 deletions.
261 changes: 175 additions & 86 deletions feather/src/main/java/org/codejargon/feather/Feather.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class Feather {
private final Map<Key, Provider<?>> providers = new ConcurrentHashMap<>();
private final Map<Key, Object> singletons = new ConcurrentHashMap<>();
private final Map<Key, Provider<?>[]> paramProviders = new ConcurrentHashMap<>();
private final Map<Class, InjectField[]> injectFields = new ConcurrentHashMap<>(0);

/**
* Constructs Feather with the provided configuration modules
Expand All @@ -35,9 +36,9 @@ public Object get() {
);
for (final Object module : modules) {
if (module instanceof Class) {
throw new FeatherException(String.format("Class %s provided as module instead of an instance.", ((Class) module).getName()));
throw new FeatherException(String.format("%s provided as class instead of an instance.", ((Class) module).getName()));
}
for (Method providerMethod : Inspection.providers(module.getClass())) {
for (Method providerMethod : providers(module.getClass())) {
providerMethod(module, providerMethod);
}
}
Expand Down Expand Up @@ -75,71 +76,58 @@ public <T> Provider<T> provider(Key<T> key) {
* Injects fields to the target object (non-transitive)
*/
public void injectFields(Object target) {
for (Field f : Inspection.injectFields(target.getClass())) {
Class<?> providerType = f.getType().equals(Provider.class) ?
(Class<?>) ((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0] :
null;
Key key = Key.of(providerType != null ? providerType : f.getType(), Inspection.qualifier(f.getAnnotations()));
if (!injectFields.containsKey(target.getClass())) {
injectFields.put(target.getClass(), injectFields(target.getClass()));
}
for (InjectField f : injectFields.get(target.getClass())) {
try {
f.set(target, providerType != null ? provider(key) : instance(key));
f.field.set(target, f.providerType ? provider(f.key) : instance(f.key));
} catch (IllegalAccessException e) {
throw new FeatherException(String.format("Can't inject field %s to an instance of %s", f.getName(), target.getClass().getName()));
throw new FeatherException(String.format("Can't inject to field %s in %s", f.field.getName(), target.getClass().getName()));
}
}
}

@SuppressWarnings("unchecked")
private <T> Provider<T> provider(final Key<T> key, Set<Key> depChain) {
if (!providers.containsKey(key)) {
synchronized (providers) {
if (!providers.containsKey(key)) {
final Constructor constructor = Inspection.constructor(key);
final Provider<?>[] paramProviders = providersForParams(key, constructor.getParameterTypes(), constructor.getGenericParameterTypes(), constructor.getParameterAnnotations(), depChain);
providers.put(key, singletonProvider(key, key.type.getAnnotation(Singleton.class), new Provider() {
@Override
public Object get() {
try {
return constructor.newInstance(arguments(paramProviders));
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new FeatherException(String.format("Failed to instantiate dependency %s", key.toString()), e);
}
}
})
);
}
}
final Constructor constructor = constructor(key);
final Provider<?>[] paramProviders = providersForParams(key, constructor.getParameterTypes(), constructor.getGenericParameterTypes(), constructor.getParameterAnnotations(), depChain);
providers.put(key, singletonProvider(key, key.type.getAnnotation(Singleton.class), new Provider() {
@Override
public Object get() {
try {
return constructor.newInstance(arguments(paramProviders));
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new FeatherException(String.format("Can't instantiate dependency %s", key.toString()), e);
}
}
})
);
}
return (Provider<T>) providers.get(key);
}

private Object[] arguments(Provider<?>[] paramProviders) {
Object[] args = new Object[paramProviders.length];
for (int i = 0; i < paramProviders.length; ++i) {
args[i] = paramProviders[i].get();
}
return args;
}

private void circularCheck(Key key, Set<Key> depChain) {
if (depChain != null && depChain.contains(key)) {
throw new FeatherException(String.format("Circular dependency: %s", chainString(depChain, key)));
}
}

private void providerMethod(final Object module, final Method m) {
final Key key = Key.of(m.getReturnType(), Inspection.qualifier(m.getAnnotations()));
final Key key = Key.of(m.getReturnType(), qualifier(m.getAnnotations()));
if (providers.containsKey(key)) {
throw new FeatherException(String.format("Multiple providers for dependency %s defined in module %s", key.toString(), module.getClass()));
throw new FeatherException(String.format("Multiple providers for dependency %s, in module %s", key.toString(), module.getClass()));
}
Singleton singleton = m.getAnnotation(Singleton.class) != null ? m.getAnnotation(Singleton.class) : m.getReturnType().getAnnotation(Singleton.class);
final Provider<?>[] paramProviders = providersForParams(key, m.getParameterTypes(), m.getGenericParameterTypes(), m.getParameterAnnotations(), Collections.singleton(key));
final Provider<?>[] paramProviders = providersForParams(
key,
m.getParameterTypes(),
m.getGenericParameterTypes(),
m.getParameterAnnotations(),
Collections.singleton(key)
);
providers.put(key, singletonProvider(key, singleton, new Provider() {
@Override
public Object get() {
try {
return m.invoke(module, arguments(paramProviders));
} catch (IllegalAccessException | InvocationTargetException e) {
throw new FeatherException(String.format("Failed to instantiate %s with provider", key.toString()), e);
throw new FeatherException(String.format("Can't instantiate %s with provider", key.toString()), e);
}
}
}
Expand All @@ -164,47 +152,63 @@ public T get() {
} : provider;
}

private Provider<?>[] providersForParams(final Key key, Class<?>[] parameterClasses, Type[] parameterTypes, Annotation[][] parameterAnnotations, final Set<Key> depChain) {
private Provider<?>[] providersForParams(
final Key key,
Class<?>[] parameterClasses,
Type[] parameterTypes,
Annotation[][] parameterAnnotations,
final Set<Key> depChain
) {
if (!paramProviders.containsKey(key)) {
synchronized (paramProviders) {
if (!paramProviders.containsKey(key)) {
Provider<?>[] providers = new Provider<?>[parameterTypes.length];
for(int i = 0; i < parameterTypes.length; ++i) {
Type parameterType = parameterTypes[i];
Class<?> parameterClass = parameterClasses[i];
Annotation qualifier = Inspection.qualifier(parameterAnnotations[i]);
Class<?> providerType = Provider.class.equals(parameterClass) ?
(Class<?>)((ParameterizedType) parameterType).getActualTypeArguments()[0] :
null;
if(providerType == null) {
final Key newKey = Key.of(parameterClass, qualifier);
final Set<Key> dependencyChain = append(depChain, key);
circularCheck(newKey, dependencyChain);
providers[i] = new Provider() {
@Override
public Object get() {
return provider(newKey, dependencyChain).get();
}
};
} else {
final Key newKey = Key.of(providerType, qualifier);
providers[i] = new Provider() {
@Override
public Object get() {
return provider(newKey, null);
}
};
Provider<?>[] providers = new Provider<?>[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; ++i) {
Type parameterType = parameterTypes[i];
Class<?> parameterClass = parameterClasses[i];
Annotation qualifier = qualifier(parameterAnnotations[i]);
Class<?> providerType = Provider.class.equals(parameterClass) ?
(Class<?>) ((ParameterizedType) parameterType).getActualTypeArguments()[0] :
null;
if (providerType == null) {
final Key newKey = Key.of(parameterClass, qualifier);
final Set<Key> dependencyChain = append(depChain, key);
circularCheck(newKey, dependencyChain);
providers[i] = new Provider() {
@Override
public Object get() {
return provider(newKey, dependencyChain).get();
}
}
paramProviders.put(key, providers);
};
} else {
final Key newKey = Key.of(providerType, qualifier);
providers[i] = new Provider() {
@Override
public Object get() {
return provider(newKey, null);
}
};
}
}
paramProviders.put(key, providers);
}
return paramProviders.get(key);
}

private Set<Key> append(Set<Key> set, Key newItem) {
if(set != null && !set.isEmpty()) {
private static Object[] arguments(Provider<?>[] paramProviders) {
Object[] args = new Object[paramProviders.length];
for (int i = 0; i < paramProviders.length; ++i) {
args[i] = paramProviders[i].get();
}
return args;
}

private static void circularCheck(Key key, Set<Key> depChain) {
if (depChain != null && depChain.contains(key)) {
throw new FeatherException(String.format("Circular dependency: %s", chainString(depChain, key)));
}
}

private static Set<Key> append(Set<Key> set, Key newItem) {
if (set != null && !set.isEmpty()) {
Set<Key> appended = new LinkedHashSet<>(set);
appended.add(newItem);
return appended;
Expand All @@ -213,19 +217,104 @@ private Set<Key> append(Set<Key> set, Key newItem) {
}
}

static String chainString(Set<Key> chain, Key lastKey) {
private static InjectField[] injectFields(Class<?> target) {
Set<Field> fields = fields(target);
InjectField[] injectFields = new InjectField[fields.size()];
int i = 0;
for(Field f : fields) {
Class<?> providerType = f.getType().equals(Provider.class) ?
(Class<?>) ((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0] :
null;
injectFields[i++] = new InjectField(
f,
providerType != null,
Key.of(providerType != null ? providerType : f.getType(), qualifier(f.getAnnotations()))
);
}
return injectFields;
}

private static Set<Field> fields(Class<?> target) {
Class<?> currentClass = target;
Set<Field> fields = new HashSet<>();
while (!currentClass.equals(Object.class)) {
for (Field field : currentClass.getDeclaredFields()) {
if (field.isAnnotationPresent(Inject.class)) {
field.setAccessible(true);
fields.add(field);
}
}
currentClass = currentClass.getSuperclass();
}
return fields;
}

private static String chainString(Set<Key> chain, Key lastKey) {
List<Key> keys = new ArrayList<>(chain);
keys.add(lastKey);
StringBuilder stringBuilder = new StringBuilder();
boolean first = true;
StringBuilder chainString = new StringBuilder();
for (Key key : keys) {
if (first) {
stringBuilder.append(key.toString());
first = false;
} else {
stringBuilder.append(" -> ").append(key.toString());
chainString.append(" -> ").append(key.toString());
}
return chainString.toString();
}

private static Constructor constructor(Key key) {
Constructor inject = null;
Constructor noarg = null;
for (Constructor c : key.type.getDeclaredConstructors()) {
if (c.isAnnotationPresent(Inject.class)) {
if (inject != null) {
throw new FeatherException(String.format("Dependency %s has more than one @Inject constructor", key.type));
} else {
c.setAccessible(true);
inject = c;

}
} else if (c.getParameters().length == 0) {
c.setAccessible(true);
noarg = c;
}
}
Constructor constructor = inject != null ? inject : noarg;
if (constructor != null) {
constructor.setAccessible(true);
return constructor;
} else {
throw new FeatherException(String.format("Dependency %s must have an @Inject constructor or a no-arg constructor or configured with @Provides in a module", key.type.getName()));
}
}

private static Set<Method> providers(Class<?> clazz) {
Class<?> currentClass = clazz;
Set<Method> providerMethods = new HashSet<>();
while (!currentClass.equals(Object.class)) {
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Provides.class) && !providerInSubClass(method, providerMethods)) {
method.setAccessible(true);
providerMethods.add(method);
}
}
currentClass = currentClass.getSuperclass();
}
return providerMethods;
}

private static Annotation qualifier(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation.annotationType().isAnnotationPresent(Qualifier.class)) {
return annotation;
}
}
return null;
}

private static boolean providerInSubClass(Method method, Set<Method> discoveredMethods) {
for (Method discovered : discoveredMethods) {
if (discovered.getName().equals(method.getName()) && Arrays.equals(method.getParameterTypes(), discovered.getParameterTypes())) {
return true;
}
}
return stringBuilder.toString();
return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package org.codejargon.feather;

public class FeatherException extends RuntimeException {
public FeatherException(String message) {
FeatherException(String message) {
super(message);
}

public FeatherException(String message, Throwable cause) {
FeatherException(String message, Throwable cause) {
super(message, cause);
}
}
15 changes: 15 additions & 0 deletions feather/src/main/java/org/codejargon/feather/InjectField.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.codejargon.feather;

import java.lang.reflect.Field;

class InjectField {
final Field field;
final boolean providerType;
final Key key;

InjectField(Field field, boolean providerType, Key key) {
this.field = field;
this.providerType = providerType;
this.key = key;
}
}
Loading

0 comments on commit 1eb2ccc

Please sign in to comment.