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

se.jbee.inject.util.Scoped Maven / Gradle / Ivy

There is a newer version: 0.6
Show newest version
/*
 *  Copyright (c) 2012, Jan Bernitt 
 *			
 *  Licensed under the Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0
 */
package se.jbee.inject.util;

import java.util.HashMap;
import java.util.Map;

import se.jbee.inject.Demand;
import se.jbee.inject.Dependency;
import se.jbee.inject.Injectable;
import se.jbee.inject.Repository;
import se.jbee.inject.Scope;

/**
 * Utility as a factory to create/use {@link Scope}s.
 * 
 * @author Jan Bernitt ([email protected])
 */
public class Scoped {

	public interface KeyDeduction {

		 String deduceKey( Demand demand );
	}

	public static final KeyDeduction DEPENDENCY_TYPE_KEY = new DependencyTypeAsKey();
	public static final KeyDeduction TARGET_INSTANCE_KEY = new TargetInstanceAsKey();

	/**
	 * Often called the 'default' or 'prototype'-scope. Asks the {@link Injectable} once per
	 * injection.
	 */
	public static final Scope INJECTION = new InjectionScope();
	/**
	 * Asks the {@link Injectable} once per binding. Thereby instances become singletons local to
	 * the application.
	 */
	public static final Scope APPLICATION = new ApplicationScope();
	/**
	 * Asks the {@link Injectable} once per thread per binding which is understand commonly as a
	 * usual 'per-thread' singleton.
	 */
	public static final Scope THREAD = new ThreadScope( new ThreadLocal(), APPLICATION );

	public static final Scope DEPENDENCY_TYPE = uniqueBy( DEPENDENCY_TYPE_KEY );
	public static final Scope TARGET_INSTANCE = uniqueBy( TARGET_INSTANCE_KEY );

	public static Scope uniqueBy( KeyDeduction keyDeduction ) {
		return new KeyDeductionScope( keyDeduction );
	}

	public static Repository asSnapshot( Repository src, Repository dest ) {
		return new SnapshotRepository( src, dest );
	}

	/**
	 * What is usually called a 'default'-{@link Scope} will ask the {@link Injectable} passed each
	 * time the {@link Repository#serve(Dependency, Injectable)}-method is invoked.
	 * 
	 * The {@link Scope} is also used as {@link Repository} instance since both don#t have any
	 * state.
	 * 
	 * @see Scoped#INJECTION
	 * 
	 * @author Jan Bernitt ([email protected])
	 */
	private static final class InjectionScope
			implements Scope, Repository {

		InjectionScope() {
			// make visible
		}

		@Override
		public Repository init() {
			return this;
		}

		@Override
		public  T serve( Demand demand, Injectable injectable ) {
			return injectable.instanceFor( demand );
		}

		@Override
		public String toString() {
			return "(default)";
		}

	}

	private static final class ThreadScope
			implements Scope, Repository {

		private final ThreadLocal threadRepository;
		private final Scope repositoryScope;

		ThreadScope( ThreadLocal threadRepository, Scope repositoryScope ) {
			super();
			this.threadRepository = threadRepository;
			this.repositoryScope = repositoryScope;
		}

		@Override
		public  T serve( Demand demand, Injectable injectable ) {
			Repository repository = threadRepository.get();
			if ( repository == null ) {
				// since each thread is just accessing its own repo there cannot be a repo set for the running thread after we checked for null
				repository = repositoryScope.init();
				threadRepository.set( repository );
			}
			return repository.serve( demand, injectable );
		}

		@Override
		public Repository init() {
			return this;
		}

		@Override
		public String toString() {
			return "(per-thread)";
		}
	}

	/**
	 * The 'synchronous'-{@link Repository} will be asked first passing a special resolver that will
	 * ask the 'asynchronous' repository when invoked. Thereby the repository originally bound will
	 * be asked once. Thereafter the result is stored in the synchronous repository.
	 * 
	 * Both repositories will remember the resolved instance whereby the repository considered as
	 * the synchronous-repository will deliver a consistent image of the world as long as it exists.
	 * 
	 * @author Jan Bernitt ([email protected])
	 */
	private static final class SnapshotRepository
			implements Repository {

		private final Repository dest;
		private final Repository src;

		SnapshotRepository( Repository src, Repository dest ) {
			super();
			this.dest = dest;
			this.src = src;
		}

		@Override
		public  T serve( Demand demand, Injectable injectable ) {
			return dest.serve( demand, new SnapshotingInjectable( injectable, src ) );
		}

		private static final class SnapshotingInjectable
				implements Injectable {

			private final Injectable supplier;
			private final Repository src;

			SnapshotingInjectable( Injectable supplier, Repository src ) {
				super();
				this.supplier = supplier;
				this.src = src;
			}

			@Override
			public T instanceFor( Demand demand ) {
				return src.serve( demand, supplier );
			}

		}

	}

	private static final class KeyDeductionScope
			implements Scope {

		private final KeyDeduction keyDeduction;

		KeyDeductionScope( KeyDeduction keyDeduction ) {
			super();
			this.keyDeduction = keyDeduction;
		}

		@Override
		public Repository init() {
			return new KeyDeductionRepository( keyDeduction );
		}

		@Override
		public String toString() {
			return "(per-" + keyDeduction + ")";
		}

	}

	private static final class TargetInstanceAsKey
			implements KeyDeduction {

		TargetInstanceAsKey() {
			// make visible
		}

		@Override
		public  String deduceKey( Demand demand ) {
			Dependency dependency = demand.getDependency();
			StringBuilder b = new StringBuilder();
			for ( int i = dependency.injectionDepth() - 1; i >= 0; i-- ) {
				b.append( dependency.target( i ) );
			}
			return b.toString();
		}

		@Override
		public String toString() {
			return "target-instance";
		}

	}

	private static final class DependencyTypeAsKey
			implements KeyDeduction {

		DependencyTypeAsKey() {
			// make visible
		}

		@Override
		public  String deduceKey( Demand demand ) {
			return demand.getDependency().getType().toString();
		}

		@Override
		public String toString() {
			return "dependendy-type";
		}

	}

	// e.g. get receiver class from dependency -to be reusable the provider could offer a identity --> a wrapper class would be needed anyway so maybe best is to have quite similar impl. all using a identity hash-map

	private static final class KeyDeductionRepository
			implements Repository {

		private final Map instances = new HashMap();
		private final KeyDeduction injectionKey;

		KeyDeductionRepository( KeyDeduction injectionKey ) {
			super();
			this.injectionKey = injectionKey;
		}

		@Override
		@SuppressWarnings ( "unchecked" )
		public  T serve( Demand demand, Injectable injectable ) {
			final String key = injectionKey.deduceKey( demand );
			T instance = (T) instances.get( key );
			if ( instance != null ) {
				return instance;
			}
			synchronized ( instances ) {
				instance = (T) instances.get( key );
				if ( instance == null ) {
					instance = injectable.instanceFor( demand );
					instances.put( key, instance );
				}
			}
			return instance;
		}

	}

	/**
	 * Will lead to instances that can be seen as application-wide-singletons.
	 * 
	 * @author Jan Bernitt ([email protected])
	 * 
	 */
	private static final class ApplicationScope
			implements Scope {

		ApplicationScope() {
			//make visible
		}

		@Override
		public Repository init() {
			return new ResourceRepository();
		}

		@Override
		public String toString() {
			return "(per-app)";
		}
	}

	/**
	 * Contains once instance per resource. Resources are never updated. This can be used to create
	 * a thread or request {@link Scope}.
	 * 
	 * @author Jan Bernitt ([email protected])
	 */
	private static final class ResourceRepository
			implements Repository {

		private Object[] instances;

		ResourceRepository() {
			super();
		}

		@Override
		@SuppressWarnings ( "unchecked" )
		public  T serve( Demand demand, Injectable injectable ) {
			if ( instances == null ) {
				instances = new Object[demand.envCardinality()];
			}
			T res = (T) instances[demand.envSerialNumber()];
			if ( res != null ) {
				return res;
			}
			// just sync the (later) unexpected path that is executed once
			synchronized ( instances ) {
				res = (T) instances[demand.envSerialNumber()];
				if ( res == null ) { // we need to ask again since the instance could have been initialized before we got entrance to the sync block
					res = injectable.instanceFor( demand );
					instances[demand.envSerialNumber()] = res;
				}
			}
			return res;
		}

	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy