/** * Copyright (c) 2014 - 2017 Frank Appel * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Frank Appel - initial API and implementation */ package com.codeaffine.util.inject; import static com.codeaffine.util.ArgumentVerification.verifyCondition; import static com.codeaffine.util.ArgumentVerification.verifyNotNull; import static java.lang.String.format; import static java.lang.reflect.Modifier.isAbstract; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.util.function.BiFunction; import java.util.stream.Stream; import com.codeaffine.util.Disposable; public class Context implements Disposable { static final String TYPE_IS_NOT_ACCESSIBLE = "<%s> is not accessible and cannot be instantiated."; static final String INTERFACE_CANNOT_BE_INSTANTIATED = "<%s> is an interface and cannot be instantiated."; static final String ABSTRACT_TYPE_CANNOT_BE_INSTANTIATED = "<%s> is an abstract type and cannot be instantiated."; static final String ARGUMENT_KEY_MUST_NOT_BE_NULL = "Argument 'key' must not be null."; static final String WRONG_CONSTRUCTOR_COUNT = "<%s> has more than one constructor and cannot be managed by constructor injection."; static final String UNABLE_TO_CREATE_INSTANCE = "Unable to create instance of <%s> using constructor injection."; private final Map<Class<?>, Object> content; private final BiFunction<Constructor<?>, Context, Object[]> injectionParameterProvider; public Context() { this( ( constructor, context ) -> getInjectionParameters( constructor, context ) ); } public Context( BiFunction<Constructor<?>, Context, Object[]> injectionParameterProvider ) { verifyNotNull( injectionParameterProvider, "injectionParameterProvider" ); this.injectionParameterProvider = injectionParameterProvider; this.content = new HashMap<>(); set( Context.class, this ); } @Override public void dispose() { content.values().stream() .filter( value -> value instanceof Disposable && value != this ) .map( value -> ( Disposable )value ) .forEach( disposable -> disposable.dispose() ); content.clear(); } public <T> T get( Class<T> key ) { verifyNotNull( key, "key" ); return key.cast( content.get( key ) ); } public <T> void set( Class<T> key, T value ) { verifyNotNull( key, "key" ); content.put( key, value ); } public <T> T create( Class<T> type ) { verifyCondition( !type.isInterface(), INTERFACE_CANNOT_BE_INSTANTIATED, type.getName() ); verifyCondition( !isAbstract( type.getModifiers() ), ABSTRACT_TYPE_CANNOT_BE_INSTANTIATED, type.getName() ); verifyCondition( type.getDeclaredConstructors().length == 1, WRONG_CONSTRUCTOR_COUNT, type.getName() ); return doCreate( type, type.getDeclaredConstructors()[ 0 ] ); } private <T> T doCreate( Class<T> type, Constructor<?> constructor ) { try { constructor.setAccessible( true ); return type.cast( constructor.newInstance( injectionParameterProvider.apply( constructor, this ) ) ); } catch( RuntimeException rte ) { throw rte; } catch( InvocationTargetException ite ) { Throwable cause = ite.getCause(); if( cause instanceof RuntimeException ) { throw ( RuntimeException )cause; } throw createIllegalStateExceptionWrapper( type, cause ); } catch( IllegalAccessException e ) { throw new IllegalStateException( format( TYPE_IS_NOT_ACCESSIBLE, type.getName() ), e ); } catch( InstantiationException e ) { throw createIllegalStateExceptionWrapper( type, e ); } } private static Object[] getInjectionParameters( Constructor<?> constructor, Context context ) { Class<?>[] parameterTypes = constructor.getParameterTypes(); return Stream.of( parameterTypes ).map( parameterType -> context.get( parameterType ) ).toArray(); } private static <T> IllegalStateException createIllegalStateExceptionWrapper( Class<T> type, Throwable e ) { String message = format( UNABLE_TO_CREATE_INSTANCE, type.getName() ); return new IllegalStateException( message, e ); } }