All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.shipdream.lib.poke.Graph Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
/*
 * Copyright 2015 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.CircularDependenciesException;
import com.shipdream.lib.poke.exception.ProvideException;
import com.shipdream.lib.poke.exception.ProviderMissingException;
import com.shipdream.lib.poke.util.ReflectUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Abstract graph manages how to inject dependencies to target objects.
 */
public abstract class Graph {
    private List providerFinders;
    private Map finderCache = new HashMap<>();
    private List onProviderFreedListeners;
    private List monitors;
    private String revisitedNode = null;
    private Set visitedInjectNodes = new LinkedHashSet<>();
    private Map> visitedFields = new HashMap<>();

    /**
     * Register {@link OnProviderFreedListener} which will be called when the provider
     *
     * @param onProviderFreedListener The listener
     */
    public void registerProviderFreedListener(OnProviderFreedListener onProviderFreedListener) {
        if (onProviderFreedListeners == null) {
            onProviderFreedListeners = new CopyOnWriteArrayList<>();
        }
        onProviderFreedListeners.add(onProviderFreedListener);
    }

    /**
     * Unregister {@link OnProviderFreedListener} which will be called when the last cached
     * instance of an injected contract is freed.
     *
     * @param onProviderFreedListener The listener
     */
    public void unregisterProviderFreedListener(OnProviderFreedListener onProviderFreedListener) {
        if (onProviderFreedListeners != null) {
            onProviderFreedListeners.remove(onProviderFreedListener);
            if (onProviderFreedListeners.isEmpty()) {
                onProviderFreedListeners = null;
            }
        }
    }

    /**
     * Clear {@link OnProviderFreedListener}s which will be called when the last cached
     * instance of an injected contract is freed.
     */
    public void clearOnProviderFreedListeners() {
        if (onProviderFreedListeners != null) {
            onProviderFreedListeners.clear();
            onProviderFreedListeners = null;
        }
    }

    /**
     * Register {@link Monitor} which will be called the graph is about to inject or release an object
     *
     * @param monitor The monitor
     */
    public void registerMonitor(Monitor monitor) {
        if (monitors == null) {
            monitors = new CopyOnWriteArrayList<>();
        }
        monitors.add(monitor);
    }

    /**
     * Register {@link Monitor} which will be called the graph is about to inject or release an object
     *
     * @param monitor The monitor
     */
    public void unregisterMonitor(Monitor monitor) {
        if (monitors != null) {
            monitors.remove(monitor);
            if (monitors.isEmpty()) {
                monitors = null;
            }
        }
    }

    /**
     * Clear {@link Monitor} which will be called the graph is about to inject or release an object
     */
    public void clearMonitors() {
        if (monitors != null) {
            monitors.clear();
            monitors = null;
        }
    }

    /**
     * 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 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);
            }
        }
        doInject(target, null, null, injectAnnotation);
        visitedInjectNodes.clear();
        revisitedNode = null;
        visitedFields.clear();
    }

    /**
     * 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 OnProviderFreedListener}.
     *
     * @param target           Whose fields will be injected
     * @param injectAnnotation Annotated which a field will be recognize
     */
    public void release(Object target, Class injectAnnotation) {
        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();
    }

    /**
     * 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 providerFinders The {@link ProviderFinder}s to add */ protected void addProviderFinders(ProviderFinder... providerFinders) { if (this.providerFinders == null) { this.providerFinders = new ArrayList<>(); } this.providerFinders.addAll(Arrays.asList(providerFinders)); } @SuppressWarnings("unchecked") private void doInject(Object target, Class targetType, Annotation targetQualifier, Class injectAnnotation) throws ProvideException, ProviderMissingException, CircularDependenciesException { boolean circularDetected = false; Provider targetProvider; ScopeCache.CachedItem cachedTargetItem = null; if (targetType != null) { //Nested injection circularDetected = recordVisit(targetType, targetQualifier); targetProvider = getProvider(targetType, targetQualifier); if (targetProvider.scopeCache != null) { cachedTargetItem = targetProvider.scopeCache.findCacheItem(targetType, targetQualifier); } boolean infiniteCircularInjection = true; if (circularDetected) { if (cachedTargetItem != null) { infiniteCircularInjection = false; } if (infiniteCircularInjection) { throwCircularDependenciesException(); } } } if (!circularDetected) { Class clazz = target.getClass(); while (clazz != null) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(injectAnnotation)) { 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); 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)); } } } clazz = clazz.getSuperclass(); } if (targetType != null) { unrecordVisit(targetType, targetQualifier); } } } private void doRelease(Object target, Class targetType, Annotation targetQualifier, final Class injectAnnotation) { Class clazz = target.getClass(); boolean circularDetected = false; if (targetType != null) { circularDetected = recordVisit(targetType, targetQualifier); } if (!circularDetected) { while (clazz != null) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(injectAnnotation)) { Object fieldValue = ReflectUtils.getFieldValue(target, field); 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()))); } boolean stillReferenced = provider.getReferenceCount(target, field) > 0; if (!isFieldVisited(target, field) && stillReferenced) { recordVisitField(target, field); doRelease(fieldValue, fieldType, fieldQualifier, injectAnnotation); provider.release(target, field); if (provider.totalReference() == 0) { if (onProviderFreedListeners != null) { int listenerSize = onProviderFreedListeners.size(); for (int k = 0; k < listenerSize; k++) { onProviderFreedListeners.get(k).onFreed(provider); } } provider.freeCache(); } } } } } clazz = clazz.getSuperclass(); } if (targetType != null) { unrecordVisit(targetType, targetQualifier); } } } private void recordVisitField(Object object, Field field) { Set fields = visitedFields.get(object); if(fields == null) { fields = new HashSet<>(); visitedFields.put(object, fields); } fields.add(field.getName()); } private boolean isFieldVisited(Object object, Field field) { Set fields = visitedFields.get(object); return fields != null && fields.contains(field.getName()); } private boolean recordVisit(Class classType, Annotation qualifier) { String key = makeCircularRecordKey(classType, qualifier); boolean circularVisitDetected = visitedInjectNodes.contains(key); if (!circularVisitDetected) { visitedInjectNodes.add(key); } else { revisitedNode = key; } return circularVisitDetected; } private void unrecordVisit(Class classType, Annotation qualifier) { String key = makeCircularRecordKey(classType, qualifier); visitedInjectNodes.remove(key); } private String makeCircularRecordKey(Class classType, Annotation qualifier) { return classType.getName() + "@" + ((qualifier == null) ? "NoQualifier" : qualifier.toString()); } Provider getProvider(Class type, Annotation qualifier) throws ProviderMissingException { //Try finder cache first. If not found try to cache it. Provider provider = null; ProviderFinder providerFinder = finderCache.get(type); if (providerFinder == null) { int count = providerFinders.size(); for (int i = 0; i < count; i++) { providerFinder = providerFinders.get(i); provider = providerFinder.findProvider(type, qualifier); if (provider != null) { finderCache.put(type, providerFinder); return provider; } } } else { provider = providerFinder.findProvider(type, qualifier); } return provider; } /** * Print readable circular graph * @throws CircularDependenciesException */ private void throwCircularDependenciesException() throws CircularDependenciesException { String msg = "Circular dependencies found. Check the circular graph below:\n"; boolean firstNode = true; String tab = " "; for (String visit : visitedInjectNodes) { if (!firstNode) { msg += tab + "->"; tab += tab; } msg += visit + "\n"; firstNode = false; } msg += tab.substring(2) + "->" + revisitedNode + "\n"; throw new CircularDependenciesException(msg); } /** * Listener will be called when the given provider is not referenced by any objects. The * callback will be called before its cached instance is freed if there is a cache associated. */ public interface OnProviderFreedListener { /** * Callback 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. */ void onFreed(Provider provider); } /** * Monitor to watch when the graph is about to inject or release an object */ public interface Monitor { /** * Called when the graph is about to inject dependencies into the given object * @param target The object to inject into */ void onInject(Object target); /** * Called when the graph is about to release dependencies from the given object * @param target The object to release */ void onRelease(Object target); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy