Skip to content
This repository has been archived by the owner on Sep 13, 2024. It is now read-only.

Support configuration of collections with complex generic types #17

Merged
merged 1 commit into from
Jan 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*******************************************************************************
* Copyright (c) 2021-present Sonatype, Inc. and others.
* 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 - initial API and implementation
*******************************************************************************/
package org.eclipse.sisu.plexus;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.codehaus.plexus.component.configurator.BasicComponentConfigurator;
import org.codehaus.plexus.component.configurator.ComponentConfigurator;
import org.codehaus.plexus.component.configurator.expression.DefaultExpressionEvaluator;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;

import junit.framework.TestCase;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonMap;

public class ParameterizedCollectionTest
extends TestCase
{
static class MapHolder
{
Map<String, Map<String, Map<String, List<Boolean>>>> map;
}

static class ListHolder
{
List<Map<String, Map<String, List<Boolean>>>> list;
}

static class ArrayHolder
{
Map<String, Map<String, List<Boolean>>>[] array;
}

public void testParameterizedMap()
throws Exception
{
final MapHolder mapHolder = new MapHolder();

configure( mapHolder, "<configuration><map>" + //
"<key1><a><x><elem1>true</elem1><elem2>false</elem2></x></a></key1>" + //
"<key2><b><y><elem1>false</elem1><elem2>true</elem2></y></b></key2>" + //
"</map></configuration>" );

Map<String, Map<String, Map<String, List<Boolean>>>> expectedMap = new HashMap<>();
expectedMap.put( "key1", singletonMap( "a", singletonMap( "x", asList( true, false ) ) ) );
expectedMap.put( "key2", singletonMap( "b", singletonMap( "y", asList( false, true ) ) ) );
assertEquals( expectedMap, mapHolder.map );
}

public void testParameterizedList()
throws Exception
{
final ListHolder listHolder = new ListHolder();

configure( listHolder, "<configuration><list>" + //
"<elem><a><x><elem1>true</elem1><elem2>false</elem2></x></a></elem>" + //
"<elem><b><y><elem1>false</elem1><elem2>true</elem2></y></b></elem>" + //
"</list></configuration>" );

List<Map<String, Map<String, List<Boolean>>>> expectedList = new ArrayList<>();
expectedList.add( singletonMap( "a", singletonMap( "x", asList( true, false ) ) ) );
expectedList.add( singletonMap( "b", singletonMap( "y", asList( false, true ) ) ) );
assertEquals( expectedList, listHolder.list );
}

public void testParameterizedArray()
throws Exception
{
final ArrayHolder arrayHolder = new ArrayHolder();

configure( arrayHolder, "<configuration><array>" + //
"<elem><a><x><elem1>true</elem1><elem2>false</elem2></x></a></elem>" + //
"<elem><b><y><elem1>false</elem1><elem2>true</elem2></y></b></elem>" + //
"</array></configuration>" );

Map<String, Map<String, List<Boolean>>>[] expectedArray =
new Map[] { singletonMap( "a", singletonMap( "x", asList( true, false ) ) ),
singletonMap( "b", singletonMap( "y", asList( false, true ) ) ) };
assertEquals( 2, arrayHolder.array.length );
assertEquals( expectedArray[0], arrayHolder.array[0] );
assertEquals( expectedArray[1], arrayHolder.array[1] );
}

protected void configure( final Object component, final String xml )
throws Exception
{
final Xpp3Dom dom = Xpp3DomBuilder.build( new StringReader( xml ) );
final PlexusConfiguration config = new XmlPlexusConfiguration( dom );
final ComponentConfigurator configurator = new BasicComponentConfigurator();
final ExpressionEvaluator evaluator = new DefaultExpressionEvaluator();
configurator.configureComponent( component, config, evaluator, null );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
*******************************************************************************/
package org.eclipse.sisu.plexus;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.codehaus.plexus.component.annotations.Component;
Expand All @@ -26,6 +32,9 @@

import junit.framework.TestCase;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonMap;

public class PlexusConfigurationTest
extends TestCase
{
Expand Down Expand Up @@ -106,6 +115,12 @@ static class ConfiguredComponent
@Configuration( "5" )
double e;

@Configuration( "<map>" + //
"<key1><a><x><elem1>true</elem1><elem2>false</elem2></x></a></key1>" + //
"<key2><b><y><elem1>false</elem1><elem2>true</elem2></y></b></key2>" + //
"</map>" )
Map<String, Map<String, Map<String, List<Boolean>>>> map;

@Configuration( "<container><xml><element/></xml></container>" )
XmlContainerComponent xmlContainer;
}
Expand Down Expand Up @@ -141,6 +156,11 @@ public void testConfiguration()
assertEquals( Double.valueOf( 4.0 ), component.d );
assertEquals( 5.0, component.e, 0 );

Map<String, Map<String, Map<String, List<Boolean>>>> expectedMap = new HashMap<>();
expectedMap.put( "key1", singletonMap( "a", singletonMap( "x", asList( true, false ) ) ) );
expectedMap.put( "key2", singletonMap( "b", singletonMap( "y", asList( false, true ) ) ) );
assertEquals( expectedMap, component.map );

assertNotNull( component.xmlContainer );
assertNotNull( component.xmlContainer.xml );
assertEquals( "xml", component.xmlContainer.xml.getName() );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@
*******************************************************************************/
package org.codehaus.plexus.component.configurator.converters.composite;

import java.lang.reflect.Type;
import java.util.Collection;

import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
import org.codehaus.plexus.component.configurator.converters.AbstractConfigurationConverter;
import org.codehaus.plexus.component.configurator.converters.ConfigurationConverter;
import org.codehaus.plexus.component.configurator.converters.ParameterizedConfigurationConverter;
import org.codehaus.plexus.component.configurator.converters.lookup.ConverterLookup;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
import org.eclipse.sisu.plexus.TypeArguments;

public abstract class AbstractCollectionConverter
extends AbstractConfigurationConverter
Expand All @@ -38,31 +41,62 @@ protected abstract Collection<Object> instantiateCollection( final PlexusConfigu
// Shared methods
// ----------------------------------------------------------------------

// Maintain binary compatibility with old method signature
protected final Collection<Object> fromChildren( final ConverterLookup lookup,
final PlexusConfiguration configuration, final Class<?> type,
final Class<?> enclosingType, final ClassLoader loader,
final ExpressionEvaluator evaluator,
final ConfigurationListener listener, final Class<?> elementType )
throws ComponentConfigurationException
{
return fromChildren( lookup, configuration, type, enclosingType, //
loader, evaluator, listener, (Type) elementType );
}

protected final Collection<Object> fromChildren( final ConverterLookup lookup,
final PlexusConfiguration configuration, final Class<?> type,
final Class<?> enclosingType, final ClassLoader loader,
final ExpressionEvaluator evaluator,
final ConfigurationListener listener, final Type elementType )
throws ComponentConfigurationException
{
final Collection<Object> elements = instantiateCollection( configuration, type, loader );
for ( int i = 0, size = configuration.getChildCount(); i < size; i++ )
{
final PlexusConfiguration xml = configuration.getChild( i );
final Class<?> childType = getChildType( xml, enclosingType, loader, elementType );
final ConfigurationConverter converter = lookup.lookupConverterForType( childType );
elements.add( converter.fromConfiguration( lookup, xml, childType, enclosingType, //
loader, evaluator, listener ) );
final Type childType = getChildType( xml, enclosingType, loader, elementType );
final Class<?> rawChildType = TypeArguments.getRawType( childType );
final ConfigurationConverter c = lookup.lookupConverterForType( rawChildType );
if ( rawChildType != childType && c instanceof ParameterizedConfigurationConverter )
{
final ParameterizedConfigurationConverter pc = (ParameterizedConfigurationConverter) c;
elements.add( pc.fromConfiguration( lookup, xml, rawChildType, //
TypeArguments.get( childType ), enclosingType, //
loader, evaluator, listener ) );
}
else
{
elements.add( c.fromConfiguration( lookup, xml, rawChildType, enclosingType, //
loader, evaluator, listener ) );
}
}
return elements;
}

// Maintain binary compatibility with old method signature
protected final Class<?> getChildType( final PlexusConfiguration childConfiguration, final Class<?> enclosingType,
final ClassLoader loader, final Class<?> elementType )
throws ComponentConfigurationException
{
return (Class<?>) getChildType( childConfiguration, enclosingType, loader, (Type) elementType );
}

protected final Type getChildType( final PlexusConfiguration childConfiguration, final Class<?> enclosingType,
final ClassLoader loader, final Type elementType )
throws ComponentConfigurationException
{
final String childName = fromXML( childConfiguration.getName() );
Class<?> childType = getClassForImplementationHint( null, childConfiguration, loader );
Type childType = getClassForImplementationHint( null, childConfiguration, loader );
Throwable cause = null;

if ( null == childType && childName.indexOf( '.' ) > 0 )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,41 @@
package org.codehaus.plexus.component.configurator.converters.composite;

import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
import org.codehaus.plexus.component.configurator.converters.ParameterizedConfigurationConverter;
import org.codehaus.plexus.component.configurator.converters.lookup.ConverterLookup;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;

public class ArrayConverter
extends AbstractCollectionConverter
implements ParameterizedConfigurationConverter
{
public boolean canConvert( final Class<?> type )
{
return type.isArray();
}

@SuppressWarnings( "unchecked" )
public Object fromConfiguration( final ConverterLookup lookup, final PlexusConfiguration configuration,
final Class<?> type, final Class<?> enclosingType, final ClassLoader loader,
final ExpressionEvaluator evaluator, final ConfigurationListener listener )
throws ComponentConfigurationException
{
return fromConfiguration( lookup, configuration, type, null, enclosingType, loader, evaluator, listener );
}

@SuppressWarnings( "unchecked" )
public Object fromConfiguration( final ConverterLookup lookup, final PlexusConfiguration configuration,
final Class<?> type, final Type[] typeArguments, final Class<?> enclosingType,
final ClassLoader loader, final ExpressionEvaluator evaluator,
final ConfigurationListener listener )
throws ComponentConfigurationException
{
final Object value = fromExpression( configuration, evaluator );
if ( type.isInstance( value ) )
Expand All @@ -45,7 +57,7 @@ public Object fromConfiguration( final ConverterLookup lookup, final PlexusConfi
try
{
final Collection<Object> elements;
final Class<?> elementType = type.getComponentType();
final Type elementType = findElementType( type, typeArguments );
if ( null == value )
{
elements = fromChildren( lookup, configuration, type, enclosingType, loader, evaluator, listener,
Expand All @@ -65,7 +77,7 @@ else if ( value instanceof Collection<?> )
failIfNotTypeCompatible( value, type, configuration );
elements = Collections.emptyList(); // unreachable
}
return elements.toArray( (Object[]) Array.newInstance( elementType, elements.size() ) );
return elements.toArray( (Object[]) Array.newInstance( type.getComponentType(), elements.size() ) );
}
catch ( final ComponentConfigurationException e )
{
Expand All @@ -87,4 +99,13 @@ protected final Collection<Object> instantiateCollection( final PlexusConfigurat
{
return new ArrayList<Object>( configuration.getChildCount() );
}

private static Type findElementType( final Class<?> rawArrayType, final Type[] typeArguments )
{
if ( null != typeArguments && typeArguments.length > 0 )
{
return typeArguments[0];
}
return rawArrayType.getComponentType();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public Object fromConfiguration( final ConverterLookup lookup, final PlexusConfi
try
{
final Collection<Object> elements;
final Class<?> elementType = findElementType( typeArguments );
final Type elementType = findElementType( typeArguments );
if ( null == value )
{
elements = fromChildren( lookup, configuration, type, enclosingType, loader, evaluator, listener,
Expand Down Expand Up @@ -123,11 +123,11 @@ protected final Collection<Object> instantiateCollection( final PlexusConfigurat
return (Collection<Object>) impl;
}

private static Class<?> findElementType( final Type[] typeArguments )
private static Type findElementType( final Type[] typeArguments )
{
if ( null != typeArguments && typeArguments.length > 0 && typeArguments[0] instanceof Class<?> )
if ( null != typeArguments && typeArguments.length > 0 )
{
return (Class<?>) typeArguments[0];
return typeArguments[0];
}
return Object.class;
}
Expand Down
Loading