diff --git a/ChangeLog.md b/ChangeLog.md index e442bd3..66bbd8e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,8 @@ +Version: 1.2.0 +* MvcGraph.inject throws runtime exception - MvcGraphException now. So no need to catch poke exceptions any more. +* MvcGraph.get method to get an instance. It provides cached instance if there is already an existing one, otherwise the newly created instance. +* Improve exception handling in library poke. + Version: 1.1.9 Fix bug * When a recovering fragment in view pager its onViewReady is not called when its holding activity resume by popping out from another above activity. diff --git a/README.md b/README.md index c0a706a..2112f02 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,13 @@ The library is currently release to jCenter and MavenCentral com.shipdream android-mvc - 1.1.9 + 1.2.0 ``` **Gradle:** ```groovy -compile "com.shipdream:android-mvc:1.1.9" +compile "com.shipdream:android-mvc:1.2.0" ``` ## Dependency injection with reference count diff --git a/build.gradle b/build.gradle index a831776..6c01d1d 100644 --- a/build.gradle +++ b/build.gradle @@ -74,8 +74,8 @@ ext { gitUrl = 'https://github.com/kejunxia/AndroidMvc.git' // Git repository URL version = [ major: 1, - minor: 1, - patch : 9 + minor: 2, + patch : 0 ] libGroup = 'com.shipdream' libVersion = "${version.major}.${version.minor}.${version.patch}" diff --git a/documents/sites/Site-MarkDown.md b/documents/sites/Site-MarkDown.md index 38ac817..82a6c88 100644 --- a/documents/sites/Site-MarkDown.md +++ b/documents/sites/Site-MarkDown.md @@ -40,13 +40,13 @@ The library is currently release to jCenter and MavenCentral com.shipdream android-mvc - 1.1.9 + 1.2.0 ``` **Gradle:** ```groovy -compile "com.shipdream:android-mvc:1.1.9" +compile "com.shipdream:android-mvc:1.2.0" ``` ## Samples diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java index 5a53a5f..114b8cf 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraph.java @@ -27,7 +27,7 @@ import com.shipdream.lib.poke.Graph; import com.shipdream.lib.poke.ImplClassLocator; import com.shipdream.lib.poke.ImplClassLocatorByPattern; -import com.shipdream.lib.poke.LocateClassException; +import com.shipdream.lib.poke.ImplClassNotFoundException; import com.shipdream.lib.poke.Provider; import com.shipdream.lib.poke.ProviderByClassType; import com.shipdream.lib.poke.ProviderFinderByRegistry; @@ -171,15 +171,45 @@ public void clearOnProviderFreedListeners() { graph.clearOnProviderFreedListeners(); } + /** + * Get an instance matching the type and qualifier. If there is an instance cached, the cached + * instance will be returned otherwise a new instance will be created. + * + *

Note that, not like {@link #inject(Object)} (Object)} this method will NOT increment + * reference count for the injectable object with the same type and qualifier.

+ * @param type the type of the object + * @param qualifier the qualifier of the injected object. Null is allowed if no qualifier is specified + * @return The cached object or a new instance matching the type and qualifier + * @throws MvcGraphException throw if exception occurs during getting the instance + */ + public T get(Class type, Annotation qualifier) { + try { + return graph.get(type, qualifier, Inject.class); + } catch (ProviderMissingException e) { + throw new MvcGraphException(e.getMessage(), e); + } catch (ProvideException e) { + throw new MvcGraphException(e.getMessage(), e); + } catch (CircularDependenciesException e) { + throw new MvcGraphException(e.getMessage(), e); + } + } + /** * Inject all fields annotated by {@link Inject}. References of controllers will be * incremented. * * @param target The target object whose fields annotated by {@link Inject} will be injected. - * @throws ProvideException */ - public void inject(Object target) throws ProvideException, CircularDependenciesException, ProviderMissingException { - graph.inject(target, Inject.class); + public void inject(Object target) { + try { + graph.inject(target, Inject.class); + } catch (ProvideException e) { + throw new MvcGraphException(e.getMessage(), e); + } catch (ProviderMissingException e) { + throw new MvcGraphException(e.getMessage(), e); + } catch (CircularDependenciesException e) { + throw new MvcGraphException(e.getMessage(), e); + } } /** @@ -190,7 +220,11 @@ public void inject(Object target) throws ProvideException, CircularDependenciesE * @param target of which the object fields will be released. */ public void release(Object target) { - graph.release(target, Inject.class); + try { + graph.release(target, Inject.class); + } catch (ProviderMissingException e) { + throw new MvcGraphException(e.getMessage(), e); + } } /** @@ -326,7 +360,7 @@ private DefaultProviderFinder(MvcGraph mvcGraph) { @SuppressWarnings("unchecked") @Override - public Provider findProvider(Class type, Annotation qualifier) { + public Provider findProvider(Class type, Annotation qualifier) throws ProviderMissingException { Provider provider = super.findProvider(type, qualifier); if (provider == null) { provider = providers.get(type); @@ -336,8 +370,8 @@ public Provider findProvider(Class type, Annotation qualifier) { provider = new MvcProvider<>(mvcGraph.stateManagedObjects, type, impClass); provider.setScopeCache(defaultImplClassLocator.getScopeCache()); providers.put(type, provider); - } catch (LocateClassException e) { - throw new RuntimeException(new ProviderMissingException(e.getMessage(), e)); + } catch (ImplClassNotFoundException e) { + throw new ProviderMissingException(type, qualifier, e); } } } diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraphException.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraphException.java new file mode 100644 index 0000000..37e1289 --- /dev/null +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/MvcGraphException.java @@ -0,0 +1,11 @@ +package com.shipdream.lib.android.mvc; + +public class MvcGraphException extends RuntimeException { + public MvcGraphException(String message) { + super(message); + } + + public MvcGraphException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/BaseEvent.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/BaseEvent.java index e2d6582..013025e 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/BaseEvent.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/BaseEvent.java @@ -17,7 +17,7 @@ package com.shipdream.lib.android.mvc.event; public class BaseEvent { - private Object sender; + private final Object sender; public BaseEvent(Object sender) { this.sender = sender; diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/ValueChangeEventC2C.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/ValueChangeEventC2C.java index 9602031..850706e 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/ValueChangeEventC2C.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/ValueChangeEventC2C.java @@ -17,8 +17,8 @@ package com.shipdream.lib.android.mvc.event; public class ValueChangeEventC2C extends BaseEventC2C{ - private T lastValue; - private T currentValue; + private final T lastValue; + private final T currentValue; public ValueChangeEventC2C(Object sender, T lastValue, T currentValue) { super(sender); diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/ValueChangeEventC2V.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/ValueChangeEventC2V.java index 576c5ca..edc250c 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/ValueChangeEventC2V.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/ValueChangeEventC2V.java @@ -17,8 +17,8 @@ package com.shipdream.lib.android.mvc.event; public class ValueChangeEventC2V extends BaseEventC2V{ - private T lastValue; - private T currentValue; + private final T lastValue; + private final T currentValue; public ValueChangeEventC2V(Object sender, T lastValue, T currentValue) { super(sender); diff --git a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/ValueChangeEventV2V.java b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/ValueChangeEventV2V.java index 638507c..baa51c9 100644 --- a/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/ValueChangeEventV2V.java +++ b/library/android-mvc-controller/src/main/java/com/shipdream/lib/android/mvc/event/ValueChangeEventV2V.java @@ -17,8 +17,8 @@ package com.shipdream.lib.android.mvc.event; public class ValueChangeEventV2V extends BaseEventV2V{ - private T lastValue; - private T currentValue; + private final T lastValue; + private final T currentValue; public ValueChangeEventV2V(Object sender, T lastValue, T currentValue) { super(sender); diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcDialogFragment.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcDialogFragment.java index ff28993..dd102dc 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcDialogFragment.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcDialogFragment.java @@ -21,7 +21,6 @@ import android.support.v4.app.DialogFragment; import com.shipdream.lib.android.mvc.event.BaseEventV2V; -import com.shipdream.lib.poke.exception.PokeException; /** * This dialog fragment can either use {@link AlertDialog.Builder} to build a alert dialog or use @@ -41,11 +40,8 @@ public void onCreate(Bundle savedInstanceState) { setRetainInstance(true); } - try { - AndroidMvc.graph().inject(this); - } catch (PokeException e) { - throw new RuntimeException(e); - } + AndroidMvc.graph().inject(this); + eventRegister = new EventRegister(this); eventRegister.registerEventBuses(); } diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcFragment.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcFragment.java index 6f7bcce..df82fd6 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcFragment.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcFragment.java @@ -23,7 +23,6 @@ import android.view.ViewGroup; import com.shipdream.lib.android.mvc.event.BaseEventV2V; -import com.shipdream.lib.poke.exception.PokeException; import java.util.concurrent.CopyOnWriteArrayList; @@ -108,12 +107,8 @@ protected int getCurrentOrientation() { void injectDependencies() { if (!dependenciesInjected) { - try { - AndroidMvc.graph().inject(this); - dependenciesInjected = true; - } catch (PokeException e) { - throw new RuntimeException(e); - } + AndroidMvc.graph().inject(this); + dependenciesInjected = true; } } diff --git a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcService.java b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcService.java index 6423189..2a25485 100644 --- a/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcService.java +++ b/library/android-mvc/src/main/java/com/shipdream/lib/android/mvc/view/MvcService.java @@ -19,7 +19,6 @@ import android.app.Service; import com.shipdream.lib.android.mvc.event.BaseEventV2V; -import com.shipdream.lib.poke.exception.PokeException; /** * Android service can be thought as a kind of view sitting on top and driven by controller which @@ -34,11 +33,9 @@ public abstract class MvcService extends Service{ @Override public void onCreate() { super.onCreate(); - try { - AndroidMvc.graph().inject(this); - } catch (PokeException e) { - throw new RuntimeException(e); - } + + AndroidMvc.graph().inject(this); + eventRegister = new EventRegister(this); eventRegister.registerEventBuses(); } diff --git a/library/poke/src/main/java/com/shipdream/lib/poke/Graph.java b/library/poke/src/main/java/com/shipdream/lib/poke/Graph.java index 11cdc53..8301617 100644 --- a/library/poke/src/main/java/com/shipdream/lib/poke/Graph.java +++ b/library/poke/src/main/java/com/shipdream/lib/poke/Graph.java @@ -16,11 +16,11 @@ package com.shipdream.lib.poke; +import com.shipdream.lib.poke.Provider.OnFreedListener; import com.shipdream.lib.poke.exception.CircularDependenciesException; import com.shipdream.lib.poke.exception.ProvideException; import com.shipdream.lib.poke.exception.ProviderMissingException; import com.shipdream.lib.poke.util.ReflectUtils; -import com.shipdream.lib.poke.Provider.OnFreedListener; import java.lang.annotation.Annotation; import java.lang.reflect.Field; @@ -121,65 +121,73 @@ public void clearMonitors() { } /** - * Inject all fields annotated by the given injectAnnotation + * Add {@link ProviderFinder} to the graph directly. Eg. if manual provider registration + * is needed, a {@link com.shipdream.lib.poke.ProviderFinderByRegistry} can be added. + *

+ *

Note that, when there are multiple {@link ProviderFinder}s able to inject an instance the + * later merged graph wins.

* - * @param target Whose fields will be injected - * @param injectAnnotation Annotated which a field will be recognize - * @throws ProvideException + * @param providerFinders The {@link ProviderFinder}s to add */ - public void inject(Object target, Class injectAnnotation) throws ProvideException, ProviderMissingException, CircularDependenciesException { - if (monitors != null) { - int size = monitors.size(); - for (int i = 0; i < size; i++) { - monitors.get(i).onInject(target); - } + protected void addProviderFinders(ProviderFinder... providerFinders) { + if (this.providerFinders == null) { + this.providerFinders = new ArrayList<>(); } - doInject(target, null, null, injectAnnotation); - visitedInjectNodes.clear(); - revisitedNode = null; - visitedFields.clear(); + this.providerFinders.addAll(Arrays.asList(providerFinders)); } /** - * Release cached instances held by fields of target object. References of cache of the - * instances will be decremented. Once the reference count of a controller reaches 0, it will - * be removed from the cache and raise {@link OnFreedListener}. + * Inject all fields annotated by the given injectAnnotation * * @param target Whose fields will be injected * @param injectAnnotation Annotated which a field will be recognize + * @throws ProvideException */ - public void release(Object target, Class injectAnnotation) { + public void inject(Object target, Class injectAnnotation) + throws ProvideException, ProviderMissingException, CircularDependenciesException { if (monitors != null) { int size = monitors.size(); for (int i = 0; i < size; i++) { - monitors.get(i).onRelease(target); + monitors.get(i).onInject(target); } } - doRelease(target, null, null, injectAnnotation); + doInject(target, null, null, injectAnnotation, true); visitedInjectNodes.clear(); revisitedNode = null; visitedFields.clear(); } /** - * Add {@link ProviderFinder} to the graph directly. Eg. if manual provider registration - * is needed, a {@link com.shipdream.lib.poke.ProviderFinderByRegistry} can be added. - *

- *

Note that, when there are multiple {@link ProviderFinder}s able to inject an instance the - * later merged graph wins.

+ * Get an instance matching the type and qualifier. If there is an instance cached, the cached + * instance will be returned otherwise a new instance will be created. * - * @param providerFinders The {@link ProviderFinder}s to add + *

Note that, not like {@link #inject(Object, Class)} this method will NOT increment + * reference count for the injectable object with the same type and qualifier.

+ * @param type the type of the object + * @param qualifier the qualifier of the injected object. Null is allowed if no qualifier is specified + * @return The cached object or a new instance matching the type and qualifier + * @throws ProviderMissingException throw if the provider matching the requiredType and qualifier is not found + * @throws ProvideException throw when failed to create a new instance + * @throws CircularDependenciesException throw when circular dependency found during injecting the newly created instance */ - protected void addProviderFinders(ProviderFinder... providerFinders) { - if (this.providerFinders == null) { - this.providerFinders = new ArrayList<>(); + public T get(Class type, Annotation qualifier, Class injectAnnotation) + throws ProviderMissingException, ProvideException, CircularDependenciesException { + Provider provider = getProvider(type, qualifier); + T cachedInstance = provider.findCachedInstance(); + if (cachedInstance != null) { + return cachedInstance; + } else { + T newInstance = provider.createInstance(); + + doInject(newInstance, null, null, injectAnnotation, false); + + return newInstance; } - this.providerFinders.addAll(Arrays.asList(providerFinders)); } @SuppressWarnings("unchecked") private void doInject(Object target, Class targetType, Annotation targetQualifier, - Class injectAnnotation) + Class injectAnnotation, boolean retainReference) throws ProvideException, ProviderMissingException, CircularDependenciesException { boolean circularDetected = false; Provider targetProvider; @@ -203,7 +211,7 @@ private void doInject(Object target, Class targetType, Annotation targetQualifie } } - if (!circularDetected) { + if (!circularDetected && target != null) { Class clazz = target.getClass(); while (clazz != null) { Field[] fields = clazz.getDeclaredFields(); @@ -212,22 +220,27 @@ private void doInject(Object target, Class targetType, Annotation targetQualifie Class fieldType = field.getType(); Annotation fieldQualifier = ReflectUtils.findFirstQualifier(field); Provider provider = getProvider(fieldType, fieldQualifier); - if (provider != null) { - Object impl = provider.get(); - ReflectUtils.setField(target, field, impl); + + Object impl; + if (retainReference) { + impl = provider.get(); provider.retain(target, field); - boolean firstTimeInject = provider.totalReference() == 1; - if (!isFieldVisited(impl, field)) { - doInject(impl, fieldType, fieldQualifier, injectAnnotation); - } - if (firstTimeInject) { - provider.notifyInjected(impl); - } - recordVisitField(impl, field); } else { - throw new ProviderMissingException(String.format("Provider for %s is missing. Make " + - "sure the provider is registered in advance.", fieldType)); + impl = provider.createInstance(); } + + ReflectUtils.setField(target, field, impl); + + boolean firstTimeInject = provider.totalReference() == 1; + if (!isFieldVisited(impl, field)) { + doInject(impl, fieldType, fieldQualifier, injectAnnotation, retainReference); + } + + if (firstTimeInject) { + provider.notifyInjected(impl); + } + + recordVisitField(impl, field); } } clazz = clazz.getSuperclass(); @@ -239,8 +252,29 @@ private void doInject(Object target, Class targetType, Annotation targetQualifie } } + /** + * Release cached instances held by fields of target object. References of cache of the + * instances will be decremented. Once the reference count of a controller reaches 0, it will + * be removed from the cache and raise {@link OnFreedListener}. + * + * @param target Whose fields will be injected + * @param injectAnnotation Annotated which a field will be recognize + */ + public void release(Object target, Class injectAnnotation) throws ProviderMissingException { + if (monitors != null) { + int size = monitors.size(); + for (int i = 0; i < size; i++) { + monitors.get(i).onRelease(target); + } + } + doRelease(target, null, null, injectAnnotation); + visitedInjectNodes.clear(); + revisitedNode = null; + visitedFields.clear(); + } + private void doRelease(Object target, Class targetType, Annotation targetQualifier, - final Class injectAnnotation) { + final Class injectAnnotation) throws ProviderMissingException { Class clazz = target.getClass(); boolean circularDetected = false; @@ -258,14 +292,7 @@ private void doRelease(Object target, Class targetType, Annotation targetQualifi if(fieldValue != null) { final Class fieldType = field.getType(); Annotation fieldQualifier = ReflectUtils.findFirstQualifier(field); - Provider provider; - try { - provider = getProvider(fieldType, fieldQualifier); - } catch (ProviderMissingException e) { - throw new RuntimeException(String.format("Can't find provider " + - "for %s with qualifier %s", fieldType.getName(), "@" - + (targetQualifier == null ? "null" : targetQualifier.toString()))); - } + Provider provider = getProvider(fieldType, fieldQualifier); boolean stillReferenced = provider.getReferenceCount(target, field) > 0; if (!isFieldVisited(target, field) && stillReferenced) { @@ -281,6 +308,7 @@ private void doRelease(Object target, Class targetType, Annotation targetQualifi onProviderFreedListeners.get(k).onFreed(provider); } } + provider.freeCache(); } } @@ -348,6 +376,10 @@ Provider getProvider(Class type, Annotation qualifier) throws ProviderMissingExc provider = providerFinder.findProvider(type, qualifier); } + if (provider == null) { + throw new ProviderMissingException(type, qualifier); + } + return provider; } diff --git a/library/poke/src/main/java/com/shipdream/lib/poke/ImplClassLocator.java b/library/poke/src/main/java/com/shipdream/lib/poke/ImplClassLocator.java index b137682..f439fc3 100644 --- a/library/poke/src/main/java/com/shipdream/lib/poke/ImplClassLocator.java +++ b/library/poke/src/main/java/com/shipdream/lib/poke/ImplClassLocator.java @@ -27,7 +27,7 @@ public abstract class ImplClassLocator { * @param * @return */ - public abstract Class locateImpl(Class contract) throws LocateClassException; + public abstract Class locateImpl(Class contract) throws ImplClassNotFoundException; /** * Define the {@link ScopeCache} for the injectable contract located by this diff --git a/library/poke/src/main/java/com/shipdream/lib/poke/ImplClassLocatorByPattern.java b/library/poke/src/main/java/com/shipdream/lib/poke/ImplClassLocatorByPattern.java index 50000bb..dc9503c 100644 --- a/library/poke/src/main/java/com/shipdream/lib/poke/ImplClassLocatorByPattern.java +++ b/library/poke/src/main/java/com/shipdream/lib/poke/ImplClassLocatorByPattern.java @@ -36,13 +36,13 @@ public ImplClassLocatorByPattern(ScopeCache scopeCache) { } @Override - public Class locateImpl(Class contract) throws LocateClassException { + public Class locateImpl(Class contract) throws ImplClassNotFoundException { String pkg = contract.getPackage().getName(); String implClassName = pkg + ".internal." + contract.getSimpleName() + "Impl"; try { return (Class) Class.forName(implClassName); } catch (ClassNotFoundException e) { - throw new LocateClassException("Can't locate implementation class for " + contract.getName(), e); + throw new ImplClassNotFoundException("Can't find implementation class for " + contract.getName(), e); } } diff --git a/library/poke/src/main/java/com/shipdream/lib/poke/LocateClassException.java b/library/poke/src/main/java/com/shipdream/lib/poke/ImplClassNotFoundException.java similarity index 83% rename from library/poke/src/main/java/com/shipdream/lib/poke/LocateClassException.java rename to library/poke/src/main/java/com/shipdream/lib/poke/ImplClassNotFoundException.java index e67d70d..459556f 100644 --- a/library/poke/src/main/java/com/shipdream/lib/poke/LocateClassException.java +++ b/library/poke/src/main/java/com/shipdream/lib/poke/ImplClassNotFoundException.java @@ -16,8 +16,8 @@ package com.shipdream.lib.poke; -public class LocateClassException extends Exception { - public LocateClassException(String message, Throwable cause) { +public class ImplClassNotFoundException extends Exception { + public ImplClassNotFoundException(String message, Throwable cause) { super(message, cause); } } diff --git a/library/poke/src/main/java/com/shipdream/lib/poke/Provider.java b/library/poke/src/main/java/com/shipdream/lib/poke/Provider.java index 41b7dde..ab19571 100644 --- a/library/poke/src/main/java/com/shipdream/lib/poke/Provider.java +++ b/library/poke/src/main/java/com/shipdream/lib/poke/Provider.java @@ -76,7 +76,7 @@ int getReferenceCount(Object owner, Field field) { return 0; } - int retain(Object owner, Field field) { + void retain(Object owner, Field field) { totalRefCount++; Map fields = owners.get(owner); if (fields == null) { @@ -87,15 +87,13 @@ int retain(Object owner, Field field) { Integer count = fields.get(field.getName()); if (count == null) { fields.put(field.getName(), 1); - return 1; } else { count++; fields.put(field.getName(), count); - return count; } } - int release(Object owner, Field field) { + void release(Object owner, Field field) { Map fields = owners.get(owner); if(fields!= null) { totalRefCount--; @@ -103,16 +101,23 @@ int release(Object owner, Field field) { Integer count = fields.get(field.getName()); if(--count > 0) { fields.put(field.getName(), count); - return count; } else { fields.remove(field.getName()); } } + if(fields != null && fields.isEmpty()) { owners.remove(owner); } + } - return 0; + void freeCache() { + if (scopeCache != null) { + ScopeCache.CachedItem cachedItem = scopeCache.findCacheItem(type, qualifier); + if (cachedItem != null) { + scopeCache.removeCache(cachedItem.type, cachedItem.qualifier); + } + } } int totalReference() { @@ -181,15 +186,6 @@ public T findCachedInstance() { return null; } - void freeCache() { - if (scopeCache != null) { - ScopeCache.CachedItem cachedItem = scopeCache.findCacheItem(type, qualifier); - if (cachedItem != null) { - scopeCache.removeCache(cachedItem.type, cachedItem.qualifier); - } - } - } - /** * Register listener which will be called back when the instance is injected. It will called * until all injectable fields of the object are fully and recursively if needed injected. @@ -220,7 +216,7 @@ public void unregisterOnInjectedListener(OnInjectedListener listener) { * @throws CircularDependenciesException Exception thrown if nested injection has circular dependencies * @throws ProviderMissingException Exception thrown if nested injection misses dependencies */ - public final T get() throws ProvideException, CircularDependenciesException, ProviderMissingException { + final T get() throws ProvideException { if(scopeCache == null) { T impl = createInstance(); if(impl == null) { @@ -228,6 +224,7 @@ public final T get() throws ProvideException, CircularDependenciesException, Pro throw new ProvideException(String.format("Provider (type: %s, qualifier: " + "%s) should not provide NULL as instance", type.getName(), qualifierName)); } + return impl; } else { return scopeCache.get(this); diff --git a/library/poke/src/main/java/com/shipdream/lib/poke/ProviderFinderByRegistry.java b/library/poke/src/main/java/com/shipdream/lib/poke/ProviderFinderByRegistry.java index b5f24d3..87399a1 100644 --- a/library/poke/src/main/java/com/shipdream/lib/poke/ProviderFinderByRegistry.java +++ b/library/poke/src/main/java/com/shipdream/lib/poke/ProviderFinderByRegistry.java @@ -18,6 +18,7 @@ import com.shipdream.lib.poke.exception.ProvideException; import com.shipdream.lib.poke.exception.ProviderConflictException; +import com.shipdream.lib.poke.exception.ProviderMissingException; import com.shipdream.lib.poke.util.ReflectUtils; import java.lang.annotation.Annotation; @@ -42,14 +43,22 @@ private static class ProviderHolder { @SuppressWarnings("unchecked") @Override - public Provider findProvider(Class type, Annotation qualifier) { + public Provider findProvider(Class type, Annotation qualifier) throws ProviderMissingException { ProviderHolder providerHolder = providers.get(PokeHelper.makeProviderKey(type, qualifier)); if (providerHolder != null) { - return providerHolder.overrider == null ? - providerHolder.original : providerHolder.overrider; + if (providerHolder.overrider == null) { + if (providerHolder.original == null) { + throw new ProviderMissingException(type, qualifier); + } else { + return providerHolder.original; + } + } else { + return providerHolder.overrider; + } + } else { + return null; } - return null; } /** diff --git a/library/poke/src/main/java/com/shipdream/lib/poke/ScopeCache.java b/library/poke/src/main/java/com/shipdream/lib/poke/ScopeCache.java index 169dd35..cb4cb33 100644 --- a/library/poke/src/main/java/com/shipdream/lib/poke/ScopeCache.java +++ b/library/poke/src/main/java/com/shipdream/lib/poke/ScopeCache.java @@ -16,9 +16,7 @@ package com.shipdream.lib.poke; -import com.shipdream.lib.poke.exception.CircularDependenciesException; import com.shipdream.lib.poke.exception.ProvideException; -import com.shipdream.lib.poke.exception.ProviderMissingException; import java.lang.annotation.Annotation; import java.util.HashMap; @@ -38,8 +36,7 @@ public static class CachedItem { protected Map cache = new HashMap<>(); @SuppressWarnings("unchecked") - T get(Provider provider) throws ProvideException, ProviderMissingException, - CircularDependenciesException { + T get(Provider provider) throws ProvideException { String key = PokeHelper.makeProviderKey(provider.type(), provider.getQualifier()); CachedItem item = cache.get(key); if (item == null) { diff --git a/library/poke/src/main/java/com/shipdream/lib/poke/exception/ProviderMissingException.java b/library/poke/src/main/java/com/shipdream/lib/poke/exception/ProviderMissingException.java index b3ac1fd..a98daa9 100644 --- a/library/poke/src/main/java/com/shipdream/lib/poke/exception/ProviderMissingException.java +++ b/library/poke/src/main/java/com/shipdream/lib/poke/exception/ProviderMissingException.java @@ -18,15 +18,27 @@ import com.shipdream.lib.poke.Graph; +import java.lang.annotation.Annotation; + /** * Exception occurs when a {@link Graph} fails to find a provider by given injection type */ public class ProviderMissingException extends PokeException{ - public ProviderMissingException(String message) { - super(message); + public ProviderMissingException(Class instanceType, Annotation qualifier) { + super(makeMessage(instanceType, qualifier)); + } + + public ProviderMissingException(Class instanceType, Annotation qualifier, Throwable cause) { + super(makeMessage(instanceType, qualifier), cause); } - public ProviderMissingException(String message, Throwable cause) { - super(message, cause); + private static String makeMessage(Class instanceType, Annotation qualifier) { + if (qualifier == null) { + return String.format("Provider for type: %s cannot be found. Please if it's defined properly.", instanceType.getName()); + } else { + return String.format("Provider for type: %s, qualifier: %s cannot be found. Please if it's defined properly.", + instanceType.getName(), qualifier.toString()); + } + } } diff --git a/library/poke/src/test/java/com/shipdream/lib/poke/TestInjectionReferenceCount.java b/library/poke/src/test/java/com/shipdream/lib/poke/TestInjectionReferenceCount.java index c7ec3ee..2f857cd 100644 --- a/library/poke/src/test/java/com/shipdream/lib/poke/TestInjectionReferenceCount.java +++ b/library/poke/src/test/java/com/shipdream/lib/poke/TestInjectionReferenceCount.java @@ -96,6 +96,41 @@ public Container providesContainer() { } } + @Test + public void should_be_able_to_get_cached_instance() throws ProvideException, ProviderConflictException, + CircularDependenciesException, ProviderMissingException { + SimpleGraph graph = new SimpleGraph(); + Component component = new TestComp2(); + graph.register(component); + + Container container = graph.get(Container.class, null, MyInject.class); + Assert.assertNotNull(((Fridge) container).a); + Assert.assertNotNull(((Fridge) container).b); + + Assert.assertTrue(component.getScopeCache().cache.isEmpty()); + + House house = new House(); + graph.inject(house, MyInject.class); + + Assert.assertNotNull(house.container); + Assert.assertNotNull(((Fridge) house.container).a); + Assert.assertNotNull(((Fridge) house.container).b); + Assert.assertTrue(((Fridge) house.container).a == ((Fridge) house.container).b); + + Assert.assertTrue(((Fridge) container).a != house.container); + Assert.assertTrue(((Fridge) container).a != ((Fridge) house.container).a); + Assert.assertTrue(((Fridge) container).b != ((Fridge) house.container).b); + + Provider containerProvider = graph.getProvider(Container.class, null); + Provider fruitProvider = graph.getProvider(Fruit.class, null); + + Assert.assertTrue(containerProvider.totalReference() == 1); + Assert.assertTrue(fruitProvider.totalReference() == 2); + + graph.release(house, MyInject.class); + Assert.assertTrue(component.getScopeCache().cache.isEmpty()); + } + @Test public void referenceCountCanReduceCascadinglyFromRoot() throws ProvideException, ProviderConflictException, CircularDependenciesException, ProviderMissingException { @@ -190,8 +225,6 @@ public void releaseInjectedFieldsShouldSetThemNull() throws ProvideException, Pr Assert.assertEquals(2, fruitProvider.totalReference()); graph.release(kitchen.container, MyInject.class); -// Assert.assertNull(((Fridge) kitchen.container).a); -// Assert.assertNull(((Fridge) kitchen.container).b); Assert.assertNotNull(kitchen.aOnFloor); Assert.assertNotNull(kitchen.bOnFloor); @@ -200,8 +233,6 @@ public void releaseInjectedFieldsShouldSetThemNull() throws ProvideException, Pr Assert.assertTrue(fruitProvider.totalReference() == 2); graph.release(kitchen, MyInject.class); -// Assert.assertNull(kitchen.aOnFloor); -// Assert.assertNull(kitchen.bOnFloor); Assert.assertTrue(component.getScopeCache().cache.isEmpty()); } diff --git a/samples/benchmark/src/main/java/com/shipdream/lib/android/mvc/samples/benchmark/MainActivity.java b/samples/benchmark/src/main/java/com/shipdream/lib/android/mvc/samples/benchmark/MainActivity.java index ad4d614..8531472 100644 --- a/samples/benchmark/src/main/java/com/shipdream/lib/android/mvc/samples/benchmark/MainActivity.java +++ b/samples/benchmark/src/main/java/com/shipdream/lib/android/mvc/samples/benchmark/MainActivity.java @@ -65,7 +65,7 @@ import com.shipdream.lib.poke.Graph; import com.shipdream.lib.poke.ImplClassLocator; import com.shipdream.lib.poke.ImplClassLocatorByPattern; -import com.shipdream.lib.poke.LocateClassException; +import com.shipdream.lib.poke.ImplClassNotFoundException; import com.shipdream.lib.poke.Provider; import com.shipdream.lib.poke.ProviderByClassType; import com.shipdream.lib.poke.ProviderFinder; @@ -217,8 +217,8 @@ public Provider findProvider(Class type, Annotation qualifier) throws try { Class impl = implClassLocatorByPattern.locateImpl(type); return new ProviderByClassType(type, impl); - } catch (LocateClassException e) { - throw new RuntimeException(e); + } catch (ImplClassNotFoundException e) { + throw new ProviderMissingException(type, qualifier, e); } } }); @@ -252,8 +252,8 @@ public Provider findProvider(Class type, Annotation qualifier) throws try { Class impl = implClassLocatorByPattern.locateImpl(type); return new ProviderByClassType(type, impl); - } catch (LocateClassException e) { - throw new RuntimeException(e); + } catch (ImplClassNotFoundException e) { + throw new ProviderMissingException(type, qualifier, e); } } });