使用地图地图作为Maven插件参数

时间:2016-07-28 06:14:47

标签: maven maven-plugin

是否可以使用地图地图作为Maven插件参数?,例如

@Parameter
private Map<String, Map<String, String>> converters;

然后像

一样使用它
<converters>
  <json>
     <indent>true</indent>
     <strict>true</strict>
  </json>
  <yaml>
      <stripComments>false</stripComments>
  </yaml>
<converters>

如果我这样使用它,converters只包含键jsonyaml,其值为null。

我知道可以将复杂对象作为值,但是在某种程度上可以使用映射作为变量元素值,如本示例所示吗?

2 个答案:

答案 0 :(得分:3)

这显然是Mojo API内部使用的sisu.plexus项目的限制。如果您查看MapConverter源内部,您会发现它首先尝试通过尝试将配置解释为String(调用fromExpression)来尝试获取映射的值,并且当此失败时,looks up the expected type of the value。但是,此方法不检查parameterized types,这是我们的情况(因为地图值的类型是Map<String, String>)。我在此项目的Bugzilla上提交了the bug 498757来跟踪此事。

使用自定义包装器对象

一种解决方法是不使用Map<String, String>作为值,而是使用自定义对象:

@Parameter
private Map<String, Converter> converters;

,类Converter,位于与Mojo相同的包中,为:

public class Converter {

    @Parameter
    private Map<String, String> properties;

    @Override
    public String toString() { return properties.toString(); } // to test

}

然后,您可以使用以下命令配置Mojo:

<converters>
  <json>
    <properties>
      <indent>true</indent>
      <strict>true</strict>
    </properties>
  </json>
  <yaml>
    <properties>
      <stripComments>false</stripComments>
    </properties>
  </yaml>
</converters>

此配置将正确地在内部映射中注入值。它还保留变量方面:对象仅作为内部映射的包装引入。我用一个简单的测试魔法测试了这个

public void execute() throws MojoExecutionException, MojoFailureException {
    getLog().info(converters.toString());
}

,输出是预期的{json={indent=true, strict=true}, yaml={stripComments=false}}

使用自定义配置程序

我还找到了一种使用自定义Map<String, Map<String, String>>保留ComponentConfigurator的方法。

所以我们希望通过继承它来修复MapConverter,问题是如何注册这个新的FixedMapConverter。默认情况下,Maven使用BasicComponentConfigurator来配置Mojo,它依赖于DefaultConverterLookup来查找要用于特定类的转换器。在这种情况下,我们希望为Map提供一个自定义转换,它将返回我们的固定版本。因此,我们需要扩展这个基本的配置器并注册我们的新转换器。

import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.component.configurator.BasicComponentConfigurator;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;

public class CustomBasicComponentConfigurator extends BasicComponentConfigurator {
    @Override
    public void configureComponent(final Object component, final PlexusConfiguration configuration,
            final ExpressionEvaluator evaluator, final ClassRealm realm, final ConfigurationListener listener)
            throws ComponentConfigurationException {
        converterLookup.registerConverter(new FixedMapConverter());
        super.configureComponent(component, configuration, evaluator, realm, listener);
    }
}

然后我们需要告诉Maven使用这个新的配置器而不是基本配置器。这是一个分为两步的过程:

  1. 在Maven插件中,创建一个注册新组件的文件src/main/resources/META-INF/plexus/components.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <component-set>
      <components>
        <component>
          <role>org.codehaus.plexus.component.configurator.ComponentConfigurator</role>
          <role-hint>custom-basic</role-hint>
          <implementation>package.to.CustomBasicComponentConfigurator</implementation>
        </component>
      </components>
    </component-set>
    

    注意以下几点:我们声明一个具有提示"custom-basic"的新组件,这将作为引用它的id,<implementation>引用我们的配置器的完全限定类名。

  2. 告诉我们的Mojo将此配置程序与@Mojo注释的configurator属性一起使用:

    @Mojo(name = "test", configurator = "custom-basic")
    

    此处传递的配置程序对应于上面components.xml中指定的角色提示。

  3. 通过这样的设置,您最终可以声明

    @Parameter
    private Map<String, Map<String, String>> converters;
    

    并且所有内容都将正确注入:Maven将使用我们的自定义配置器,它将注册我们的固定版本的地图转换器并正确转换内部地图。

    FixedMapConverter的完整代码(几乎复制粘贴MapConverter,因为我们无法覆盖错误的方法):

    public class FixedMapConverter extends MapConverter {
    
        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, type);
            if (null != value) {
                return value;
            }
            try {
                final Map<Object, Object> map = instantiateMap(configuration, type, loader);
                final Class<?> elementType = findElementType(typeArguments);
                if (Object.class == elementType || String.class == elementType) {
                    for (int i = 0, size = configuration.getChildCount(); i < size; i++) {
                        final PlexusConfiguration element = configuration.getChild(i);
                        map.put(element.getName(), fromExpression(element, evaluator));
                    }
                    return map;
                }
                // handle maps with complex element types...
                final ConfigurationConverter converter = lookup.lookupConverterForType(elementType);
                for (int i = 0, size = configuration.getChildCount(); i < size; i++) {
                    Object elementValue;
                    final PlexusConfiguration element = configuration.getChild(i);
                    try {
                        elementValue = converter.fromConfiguration(lookup, element, elementType, enclosingType, //
                                loader, evaluator, listener);
                    }
                    // TEMP: remove when http://jira.codehaus.org/browse/MSHADE-168
                    // is fixed
                    catch (final ComponentConfigurationException e) {
                        elementValue = fromExpression(element, evaluator);
    
                        Logs.warn("Map in " + enclosingType + " declares value type as: {} but saw: {} at runtime",
                                elementType, null != elementValue ? elementValue.getClass() : null);
                    }
                    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                    map.put(element.getName(), elementValue);
                }
                return map;
            } catch (final ComponentConfigurationException e) {
                if (null == e.getFailedConfiguration()) {
                    e.setFailedConfiguration(configuration);
                }
                throw e;
            }
        }
    
        @SuppressWarnings("unchecked")
        private Map<Object, Object> instantiateMap(final PlexusConfiguration configuration, final Class<?> type,
                final ClassLoader loader) throws ComponentConfigurationException {
            final Class<?> implType = getClassForImplementationHint(type, configuration, loader);
            if (null == implType || Modifier.isAbstract(implType.getModifiers())) {
                return new TreeMap<Object, Object>();
            }
    
            final Object impl = instantiateObject(implType);
            failIfNotTypeCompatible(impl, type, configuration);
            return (Map<Object, Object>) impl;
        }
    
        private static Class<?> findElementType( final Type[] typeArguments )
        {
            if ( null != typeArguments && typeArguments.length > 1 )
            {
                if ( typeArguments[1] instanceof Class<?> )
                {
                    return (Class<?>) typeArguments[1];
                }
                // begin fix here
                if ( typeArguments[1] instanceof ParameterizedType )
                {
                    return (Class<?>) ((ParameterizedType) typeArguments[1]).getRawType();
                }
                // end fix here
            }
            return Object.class;
        }
    
    }
    

答案 1 :(得分:2)

一种解决方案非常简单,适用于1级嵌套。在替代答案中可以找到更复杂的方法,这可能也允许更深的地图嵌套。

不使用接口作为类型参数,只需使用类似TreeMap

的具体类
 @Parameter
 private Map<String, TreeMap> converters.

原因是MapConverter中的这个检查失败了一个接口但是超过了一个具体的类:

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

作为旁注,因为它也与Maven的answer相关&gt; 3.3.x它还可以通过继承BasicComponentConfigurator并将其用作Plexus组件来安装自定义转换器。 BasicComponentConfiguratorDefaultConverterLookup作为受保护的成员变量,因此可以轻松访问自定义转换器。