/*
* Copyright 2016 Kejun Xia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.shipdream.lib.poke;
import com.shipdream.lib.poke.exception.ProvideException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Provider controls the injection type mapping as well as the scope by associated
* {@link ScopeCache}.
*/
public abstract class Provider<T> {
/**
* Listener monitoring when the provider is instantiating a new instance.
*/
public interface CreationListener<T> {
/**
* Called when a new instance is constructed by the provider
*
* @param provider The provider used to provide the injecting instance
* @param instance The instance. Its own injectable members should have been injected recursively as well.
*/
void onCreated(Provider<T> provider, T instance);
}
/**
* Listener monitoring when the instance provided by the provider is freed. It happens either
* <ul>
* <li>The provider doesn't have a scope instances and a provided instance is dereferenced</li>
* <li>The provider has a scope instances and the provider is dereferenced with 0 reference count</li>
* </ul>
*/
public interface DisposeListener {
/**
* Called when either
* <ul>
* <li>The provider doesn't have a scope instances and a provided instance is dereferenced</li>
* <li>The provider has a scope instances and the provider is dereferenced with 0 reference count</li>
* </ul>
*
* @param provider The provider used to provide the injecting instance
* @param instance The instance. Its own injectable members should have been injected recursively as well.
*/
<T> void onDisposed(Provider<T> provider, T instance);
}
/**
* Listener monitoring when the provider is referenced.
*/
public interface ReferencedListener<T> {
/**
* Called provider is first time used to inject its content. The call is guaranteed to be
* invoked after all injectable fields of its content is fully injected.
*
* @param provider The provider used to provide the injecting instance
* @param instance The instance. Its own injectable members should have been injected recursively as well.
*/
void onReferenced(Provider<T> provider, T instance);
}
/**
* Listener will be called when the given provider is dereferenced.
*/
public interface DereferenceListener {
/**
* listener to be invoked when when the given provider is not referenced by any objects.
*
* @param provider The provider whose content is not referenced by any objects.
* @param instance The instance that is being dereferrenced
*/
<T> void onDereferenced(Provider<T> provider, T instance);
}
private final Class<T> type;
private final Annotation qualifier;
//The component the provider is attached to
private Component component;
private ScopeCache scopeCache;
Map<Object, Map<String, Integer>> owners = new HashMap<>();
private int totalRefCount = 0;
/**
* Construct an unscoped and unqualified provider
* @param type The type of the instance the provider is providing
*/
public Provider(Class<T> type) {
this(type, null, null);
}
/**
* Construct an unscoped and qualified provider
* @param type The type of the instance the provider is providing
* @param qualifier Qualifier
*/
public Provider(Class<T> type, Annotation qualifier) {
this.type = type;
this.qualifier = qualifier;
this.scopeCache = null;
}
/**
* Construct a scoped and unqualified provider
* @param type The type of the instance the provider is providing
* @param scopeCache the instances for the scope
*/
public Provider(Class<T> type, ScopeCache scopeCache) {
this(type, null, scopeCache);
}
/**
* Construct a scoped and qualified provider
* @param type The type of the instance the provider is providing
* @param qualifier Qualifier
* @param scopeCache ScopeCache
*/
public Provider(Class<T> type, Annotation qualifier, ScopeCache scopeCache) {
this.type = type;
this.qualifier = qualifier;
this.scopeCache = scopeCache;
}
Component getComponent() {
return component;
}
void setComponent(Component component) {
this.component = component;
}
/**
* Get the scope instances. If this provider is not attached to any component. It returns this
* provider's own scope instances otherwise the component's scope instances.
* @return
*/
ScopeCache getScopeCache() {
if (component == null) {
return scopeCache;
} else {
return component.scopeCache;
}
}
int getReferenceCount(Object owner, Field field) {
Map<String, Integer> fields = owners.get(owner);
if (fields != null) {
Integer count = fields.get(field.toGenericString());
if(count != null) {
return count;
}
}
return 0;
}
/**
* Increase reference count.
*/
public void retain() {
totalRefCount++;
}
/**
* Retain an instance injected as a field of an object
* @param owner The owner of the field
* @param field The field
*/
void retain(Object owner, Field field) {
retain();
Map<String, Integer> fields = owners.get(owner);
if (fields == null) {
fields = new HashMap<>();
owners.put(owner, fields);
}
Integer count = fields.get(field.toGenericString());
if (count == null) {
fields.put(field.toGenericString(), 1);
} else {
count++;
fields.put(field.toGenericString(), count);
}
}
/**
* Decrease reference count.
*/
public void release() {
totalRefCount--;
if (totalRefCount == 0) {
freeCache();
}
}
private void freeCache() {
ScopeCache cache = getScopeCache();
if (cache != null) {
String key = PokeHelper.makeProviderKey(type, qualifier);
Object instance = cache.findInstance(key);
if (instance != null) {
cache.removeInstance(key);
}
}
}
/**
* Release an instance injected as a field of an object
* @param owner The owner of the field
* @param field The field
*/
void release(Object owner, Field field) {
Map<String, Integer> fields = owners.get(owner);
if(fields != null) {
release();
Integer count = fields.get(field.toGenericString());
if(--count > 0) {
fields.put(field.toGenericString(), count);
} else {
fields.remove(field.toGenericString());
}
}
if(fields != null && fields.isEmpty()) {
owners.remove(owner);
}
}
public int getReferenceCount() {
return totalRefCount;
}
/**
* The listeners when the instance is injected.
*/
private List<CreationListener<T>> creationListeners;
/**
* @return Instantiation listeners if there are registered listeners. Null may be returned if
* nothing is registered ever.
*/
public List<CreationListener<T>> getCreationListeners() {
return creationListeners;
}
/**
* The listeners when the instance is referenced.
*/
private List<ReferencedListener<T>> referencedListeners;
/**
* @return Referenced listeners if there are registered listeners. Null may be returned if
* nothing is registered ever.
*/
public List<ReferencedListener<T>> getReferencedListeners() {
return referencedListeners;
}
/**
* Get qualifier of the provider
* @return The qualifier
*/
public Annotation getQualifier() {
return this.qualifier;
}
/**
* Get the cached instance of this provider when there is a instances associated with this provider
* and the instance is cached already. Note that, the method will NOT increase reference count of
* this provider
* @return The cached instance of this provider if there is a instances associated with this provider
* and the instance is cached already, otherwise null will be returned
*/
public T getCachedInstance() {
ScopeCache cache = getScopeCache();
if (cache != null) {
String key = PokeHelper.makeProviderKey(type, qualifier);
Object instance = cache.findInstance(key);
if(instance != null) {
return (T) instance;
}
}
return null;
}
public void registerCreationListener(CreationListener<T> listener) {
if(creationListeners == null) {
creationListeners = new CopyOnWriteArrayList<>();
}
creationListeners.add(listener);
}
public void unregisterCreationListener(CreationListener<T> listener) {
if (creationListeners != null) {
creationListeners.remove(listener);
if (creationListeners.isEmpty()) {
creationListeners = null;
}
}
}
public void clearCreationListeners() {
if (creationListeners != null) {
creationListeners.clear();
creationListeners = null;
}
}
public void registerOnReferencedListener(ReferencedListener<T> listener) {
if(referencedListeners == null) {
referencedListeners = new CopyOnWriteArrayList<>();
}
referencedListeners.add(listener);
}
public void unregisterOnReferencedListener(ReferencedListener<T> listener) {
if (referencedListeners != null) {
referencedListeners.remove(listener);
if (referencedListeners.isEmpty()) {
referencedListeners = null;
}
}
}
public void clearOnReferencedListener() {
if (referencedListeners != null) {
referencedListeners.clear();
referencedListeners = null;
}
}
/**
* Get an instance of the type the provider is providing. When there is a {@link ScopeCache}
* associated to it the provider will try to use the cached instance when applicable otherwise
* always generates a new instance.
* @return The instance being created or cached
* @throws ProvideException Exception thrown during constructing the object
*/
final T get() throws ProvideException {
ScopeCache cache = getScopeCache();
if(cache == null) {
T impl = createInstance();
if (impl == null) {
String qualifierName = (qualifier == null) ? "null" : qualifier.getClass().getName();
throw new ProvideException(String.format("Provider (type: %s, qualifier: " +
"%s) should not provide NULL as instance", type.getName(), qualifierName));
}
newlyCreatedInstance = impl;
return impl;
} else {
return cache.get(this);
}
}
/**
* Delay notifying instantiation listeners since they need to be full
* injected if the instance has injectable fields
*/
T newlyCreatedInstance = null;
private void notifyInstanceCreationWhenNeeded() {
if (newlyCreatedInstance != null && creationListeners != null) {
for (CreationListener l : creationListeners) {
l.onCreated(this, newlyCreatedInstance);
}
}
newlyCreatedInstance = null;
}
/**
* Notify the instance with the given type is <b>FULLY</b> injected which means all of its
* nested injectable fields are injected and ready <b>RECURSIVELY</b>.
* <p>This method should get called every time the instance is injected, no matter if it's a
* newly created instance or it's a reused cached instance.</p>
*
* @param instance The instance. Its own injectable members should have been injected recursively as well.
*/
void notifyReferenced(Provider provider, T instance) {
notifyInstanceCreationWhenNeeded();
if (referencedListeners != null) {
int len = referencedListeners.size();
for(int i = 0; i < len; i++) {
referencedListeners.get(i).onReferenced(provider, instance);
}
}
}
/**
* Type of the contract/interface
* @return The type of the provider
*/
public final Class<T> type() {
return type;
}
/**
* Override to implement how the provider instantiates a new instance
* @return The newly created instance
* @throws ProvideException
*/
protected abstract T createInstance() throws ProvideException;
}