/******************************************************************************* * Copyright (c) 2010-present Sonatype, Inc. * 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: * Stuart McCulloch (Sonatype, Inc.) - initial API and implementation *******************************************************************************/ package org.eclipse.sisu.wire; import java.util.Map; import javax.inject.Inject; import org.eclipse.sisu.Parameters; import org.eclipse.sisu.inject.BeanLocator; import com.google.inject.Key; import com.google.inject.Provider; import com.google.inject.ProvisionException; import com.google.inject.TypeLiteral; import com.google.inject.name.Named; import com.google.inject.name.Names; import com.google.inject.spi.TypeConverter; /** * Provides a single bean; the name used to lookup/convert the bean is selected at runtime. */ final class PlaceholderBeanProvider<V> implements Provider<V> { // ---------------------------------------------------------------------- // Constants // ---------------------------------------------------------------------- private static final int EXPRESSION_RECURSION_LIMIT = 8; // ---------------------------------------------------------------------- // Implementation fields // ---------------------------------------------------------------------- @Inject @Parameters @SuppressWarnings( "rawtypes" ) private Map properties; @Inject private TypeConverterCache converterCache; private final Provider<BeanLocator> locator; private final Key<V> placeholderKey; // ---------------------------------------------------------------------- // Constructors // ---------------------------------------------------------------------- PlaceholderBeanProvider( final Provider<BeanLocator> locator, final Key<V> key ) { this.locator = locator; placeholderKey = key; } // ---------------------------------------------------------------------- // Public methods // ---------------------------------------------------------------------- @SuppressWarnings( "unchecked" ) public V get() { final String template = ( (Named) placeholderKey.getAnnotation() ).value(); final TypeLiteral<V> expectedType = placeholderKey.getTypeLiteral(); // ---------------- INTERPOLATION ---------------- final Class<?> clazz = expectedType.getRawType(); Object value = interpolate( template, clazz ); if ( false == value instanceof String ) { return (V) value; // found non-String mapping } // ------------------- LOOKUP -------------------- final Key<V> lookupKey = Key.get( expectedType, Names.named( (String) value ) ); if ( String.class != clazz ) { final V bean = lookup( lookupKey ); if ( null != bean ) { return bean; // found non-String binding } } // ----------------- CONVERSION ------------------ if ( template == value ) // NOPMD we want to know if same instance { // no interpolation occurred; is this perhaps a Guice constant? value = nullify( lookup( lookupKey.ofType( String.class ) ) ); } if ( null == value || String.class == clazz ) { return (V) value; // no conversion required } final TypeConverter converter = converterCache.getTypeConverter( expectedType ); if ( null != converter ) { return (V) converter.convert( (String) value, expectedType ); } return null; } // ---------------------------------------------------------------------- // Implementation methods // ---------------------------------------------------------------------- private <T> T lookup( final Key<T> key ) { return BeanProviders.firstOf( locator.get().locate( key ) ); } private static String nullify( final String value ) { return "null".equals( value ) ? null : value; } private Object interpolate( final String template, final Class<?> clazz ) { final StringBuilder buf; if ( template.contains( "${" ) ) { buf = new StringBuilder( template ); } else if ( properties.containsKey( template ) ) { // handle situations where someone missed out the main brackets buf = new StringBuilder( "${" ).append( template ).append( '}' ); } else { return template; // nothing to interpolate, maintain reference } int x = 0, y, expressionEnd = 0, expressionNum = 0; while ( ( x = buf.indexOf( "${", x ) ) >= 0 && ( y = buf.indexOf( "}", x ) + 1 ) > 0 ) { if ( y > expressionEnd ) // making progress { expressionNum = 0; expressionEnd = y; } final String key = buf.substring( x + 2, y - 1 ); final int anchor = key.indexOf( ":-" ); Object value = properties.get( anchor < 0 ? key : key.substring( 0, anchor ) ); if ( value == null && anchor >= 0 ) { value = key.substring( anchor + 2 ); } if ( expressionNum++ >= EXPRESSION_RECURSION_LIMIT ) { throw new ProvisionException( "Recursive configuration: " + template + " stopped at: " + buf ); } final int len = buf.length(); if ( 0 == x && len == y && String.class != clazz && clazz.isInstance( value ) ) { return value; // found compatible (non-String) instance in the properties! } buf.replace( x, y, String.valueOf( value ) ); expressionEnd += buf.length() - len; } return nullify( buf.toString() ); } }