Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#
# Created with help from www.gitignore.io
#


#######################################################################
# Intellij
# Created by https://www.gitignore.io/api/intellij
#
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

## File-based project format:
*.iws

## Plugin-specific files:

# IntelliJ
/out/
/.idea/
/target/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

### Intellij Patch ###
*.iml
*.ipr


#######################################################################
# Maven
# Created by https://www.gitignore.io/api/maven
#
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties


#######################################################################
# OSX
# Created by https://www.gitignore.io/api/osx
#
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

17 changes: 17 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
language: java
install: true

script:
- mvn verify
- mvn compile
- mvn test
- mvn validate
- mvn jar:jar
- mvn jar:test-jar
- mvn source:jar
- mvn source:test-jar
- mvn surefire:test
- mvn clean package
- mvn clean install
jdk:
- oraclejdk8
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

#### About Feather

[Feather](http://zsoltherpai.github.io/feather) is an ultra-lightweight dependency injection ([JSR-330](https://jcp.org/en/jsr/detail?id=330 "JSR-330"))
library for Java and Android. Dependency injection frameworks are often perceived as "magical" and complex.
Feather - with just a few hundred lines of code - is probably the easiest, tiniest, most obvious one,
Expand Down
122 changes: 110 additions & 12 deletions feather/src/main/java/org/codejargon/feather/Feather.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
package org.codejargon.feather;

import javax.inject.*;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.inject.Inject;
import javax.inject.Qualifier;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.*;
import java.util.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
*
* @author edwin.njeru
*
*/
public class Feather {
private final Map<Key, Provider<?>> providers = new ConcurrentHashMap<>();
private final Map<Key, Object> singletons = new ConcurrentHashMap<>();
private final Map<Class, Object[][]> injectFields = new ConcurrentHashMap<>(0);
//private final Map<Class, Object[][]> injectFields = new ConcurrentHashMap<>(0);

/**
* Constructs Feather with configuration modules
*
* @param modules varargs of modules classes
*
* @return an instance of Feather
*/
public static Feather with(Object... modules) {
return new Feather(Arrays.asList(modules));
}

/**
* Constructs Feather with configuration modules
*
* @param modules {@code Iterable<?>} collection of configuration modules
* @return
*/
public static Feather with(Iterable<?> modules) {
return new Feather(modules);
Expand Down Expand Up @@ -73,12 +96,15 @@ public <T> Provider<T> provider(Key<T> key) {

/**
* Injects fields to the target object
*
* @param target Object
*/
public void injectFields(Object target) {
if (!injectFields.containsKey(target.getClass())) {
/*if (!injectFields.containsKey(target.getClass())) {
injectFields.put(target.getClass(), injectFields(target.getClass()));
}
for (Object[] f: injectFields.get(target.getClass())) {
}*/
//for (Object[] f: injectFields.get(target.getClass())) {
for (Object[] f: injectFields(target.getClass())) {
Field field = (Field) f[0];
Key key = (Key) f[2];
try {
Expand All @@ -89,10 +115,11 @@ public void injectFields(Object target) {
}
}

@SuppressWarnings("unchecked")
@SuppressWarnings({ "unchecked", "rawtypes" })
private <T> Provider<T> provider(final Key<T> key, Set<Key> chain) {
if (!providers.containsKey(key)) {
final Constructor constructor = constructor(key);
@SuppressWarnings("rawtypes")
final Constructor constructor = constructor(key);
final Provider<?>[] paramProviders = paramProviders(key, constructor.getParameterTypes(), constructor.getGenericParameterTypes(), constructor.getParameterAnnotations(), chain);
providers.put(key, singletonProvider(key, key.type.getAnnotation(Singleton.class), new Provider() {
@Override
Expand All @@ -109,7 +136,8 @@ public Object get() {
return (Provider<T>) providers.get(key);
}

private void providerMethod(final Object module, final Method m) {
@SuppressWarnings("unchecked")
private void providerMethod(final Object module, final Method m) {
final Key key = Key.of(m.getReturnType(), qualifier(m.getAnnotations()));
if (providers.containsKey(key)) {
throw new FeatherException(String.format("%s has multiple providers, module %s", key.toString(), module.getClass()));
Expand Down Expand Up @@ -192,6 +220,10 @@ public Object get() {
return providers;
}

/**
* @return an array of fully-constructed and injected instances fetched from an
* array of @param paramProviders, which are then used as parameters
*/
private static Object[] params(Provider<?>[] paramProviders) {
Object[] params = new Object[paramProviders.length];
for (int i = 0; i < paramProviders.length; ++i) {
Expand All @@ -200,7 +232,16 @@ private static Object[] params(Provider<?>[] paramProviders) {
return params;
}

private static Set<Key> append(Set<Key> set, Key newKey) {
/**
* Appends a @{code Key} @param newKey to an existing {@code Set} @param set of
* keys, and returns a {@code Set} of key containing the new {@code Key}.
* If the {@code Set} of {@code Key} is null, a serializable immutable set containing only
* the @param newKey is returned
*
* @return Set<Key> append {@code Key} {@code Set}
*/
@SuppressWarnings("rawtypes")
private static Set<Key> append(Set<Key> set, Key newKey) {
if (set != null && !set.isEmpty()) {
Set<Key> appended = new LinkedHashSet<>(set);
appended.add(newKey);
Expand All @@ -210,6 +251,14 @@ private static Set<Key> append(Set<Key> set, Key newKey) {
}
}

/**
* Creates a map represented as a two-dimensional array of {@code Inject} annotated
* fields with their corresponding {@code Provider} types, or Parameterised types or
* type arguments.
*
* @param target Class<?> target
* @return two-dimensional Object[][] array
*/
private static Object[][] injectFields(Class<?> target) {
Set<Field> fields = fields(target);
Object[][] fs = new Object[fields.size()][];
Expand All @@ -227,6 +276,13 @@ private static Object[][] injectFields(Class<?> target) {
return fs;
}

/**
* Returns accessible set of fields ({@code Field}) in a given class type
* which are annotated with the {@code Inject} {@code Annotation}
*
* @param type Class<?> type
* @return Set<Field> fields
*/
private static Set<Field> fields(Class<?> type) {
Class<?> current = type;
Set<Field> fields = new HashSet<>();
Expand All @@ -242,15 +298,34 @@ private static Set<Field> fields(Class<?> type) {
return fields;
}

private static String chain(Set<Key> chain, Key lastKey) {
/**
* Returns a String representing a concatenation of Keys from a {@code Set}
* of {@code Key} and appends the last {@code Key}
*
* @param chain Set<Key> chain
* @param {@code Key} lastKey
* @return "->" concatenated string of {@code Key} names
*/
@SuppressWarnings("rawtypes")
private static String chain(Set<Key> chain, Key lastKey) {
StringBuilder chainString = new StringBuilder();
for (Key key : chain) {
chainString.append(key.toString()).append(" -> ");
}
return chainString.append(lastKey.toString()).toString();
}

private static Constructor constructor(Key key) {
/**
* Fetches a constructor which is annotated with {@code Inject} from a {@code Key}.
* If such a constructor does not exist, the noargs constructor is used instead.
* If both do not exist and a module provider does not also exist then a
* {@code FeatherException} is thrown
*
* @param {@code Key}
* @return {@code Constructor}
*/
@SuppressWarnings("rawtypes")
private static Constructor constructor(Key key) {
Constructor inject = null;
Constructor noarg = null;
for (Constructor c : key.type.getDeclaredConstructors()) {
Expand All @@ -273,6 +348,13 @@ private static Constructor constructor(Key key) {
}
}

/**
* Creates a hashSet of methods from a given type which are annotated with the
* {@code Provides} annotation
*
* @param type Class<?> type
* @return Set<Method> containing {@code Provides} annotation
*/
private static Set<Method> providers(Class<?> type) {
Class<?> current = type;
Set<Method> providers = new HashSet<>();
Expand All @@ -288,6 +370,14 @@ private static Set<Method> providers(Class<?> type) {
return providers;
}

/**
* Fetches a {@code Qualifier} {@code Annotation} from an array of annotations.
* If a {@code Qualifier} {@code Annotation} is not found in the array, a null pointer
* is returned
*
* @param annotations
* @return {@code Qualifier} {@code Annotation}
*/
private static Annotation qualifier(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation.annotationType().isAnnotationPresent(Qualifier.class)) {
Expand All @@ -297,6 +387,14 @@ private static Annotation qualifier(Annotation[] annotations) {
return null;
}

/**
* Checks is a Provider exists in a subclass of the current class we are currently
* looping through
*
* @param {@code Method}
* @param discoveredMethods Set<Method> of discoveredMethods
* @return boolean whether or not a provider exists in a subclass
*/
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())) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package org.codejargon.feather;

public class FeatherException extends RuntimeException {
FeatherException(String message) {
/**
*
*/
private static final long serialVersionUID = -1606897572808444593L;

FeatherException(String message) {
super(message);
}

Expand Down
6 changes: 5 additions & 1 deletion feather/src/main/java/org/codejargon/feather/Provides.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package org.codejargon.feather;

import java.lang.annotation.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
Expand Down
Loading