[MNG-7954] New dependency injection mechanism (#1393)

This commit is contained in:
Guillaume Nodet 2024-02-05 10:45:47 +01:00 committed by GitHub
parent 3f9fec2307
commit a37cf3d37f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 3431 additions and 158 deletions

View File

@ -52,8 +52,8 @@
<artifactId>maven-api-plugin</artifactId>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
<groupId>org.apache.maven</groupId>
<artifactId>maven-api-di</artifactId>
</dependency>
</dependencies>

View File

@ -22,8 +22,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import jakarta.inject.Scope;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -32,6 +30,8 @@
* Indicates that the annotated bean has a lifespan limited to a given mojo execution,
* which means each mojo execution will result in a different instance being injected.
*
* TODO: this is currently not implemented
*
* @since 4.0.0
*/
@Scope

View File

@ -22,8 +22,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import jakarta.inject.Scope;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -32,6 +30,8 @@
* Indicates that annotated component should be instantiated before session execution starts
* and discarded after session execution completes.
*
* TODO: this is currently not implemented
*
* @since 4.0.0
*/
@Scope

View File

@ -35,7 +35,7 @@
*/
@Experimental
@Documented
@Retention(RetentionPolicy.CLASS)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Execute {

View File

@ -38,7 +38,7 @@
*/
@Experimental
@Documented
@Retention(RetentionPolicy.CLASS)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Mojo {

View File

@ -41,7 +41,7 @@
*/
@Experimental
@Documented
@Retention(RetentionPolicy.CLASS)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
@Inherited
public @interface Parameter {

40
api/maven-api-di/pom.xml Normal file
View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.maven</groupId>
<artifactId>maven-api</artifactId>
<version>4.0.0-alpha-13-SNAPSHOT</version>
</parent>
<artifactId>maven-api-di</artifactId>
<name>Maven 4 API :: Dependency Injection</name>
<description>Maven 4 API - Dependency Injection</description>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-api-meta</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.api.di;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({FIELD, CONSTRUCTOR, METHOD})
@Retention(RUNTIME)
@Documented
public @interface Inject {}

View File

@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.api.di;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Retention(RUNTIME)
@Documented
public @interface Named {
String value() default "";
}

View File

@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.api.di;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({TYPE, METHOD})
@Retention(RUNTIME)
@Documented
public @interface Priority {
int value();
}

View File

@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.api.di;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Can be used on a static method to provide a bean.
*/
@Target(METHOD)
@Retention(RUNTIME)
@Documented
public @interface Provides {}

View File

@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.api.di;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
@Documented
public @interface Qualifier {}

View File

@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.api.di;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
@Documented
public @interface Scope {}

View File

@ -0,0 +1,29 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.api.di;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

View File

@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.api.di;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({FIELD, METHOD, TYPE})
@Retention(RUNTIME)
@Documented
public @interface Typed {
Class<?>[] value() default {};
}

View File

@ -33,6 +33,7 @@
<modules>
<module>maven-api-meta</module>
<module>maven-api-di</module>
<module>maven-api-xml</module>
<module>maven-api-model</module>
<module>maven-api-plugin</module>

View File

@ -33,6 +33,10 @@ under the License.
<dependencies>
<!-- Maven -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-di</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>

View File

@ -52,16 +52,26 @@ public void configureComponent(
try {
ClassRealmConverter.pushContextRealm(realm);
new EnhancedConfigurationConverter()
.processConfiguration(
converterLookup,
component,
realm, //
configuration,
evaluator,
listener);
this.configureComponent(component, configuration, evaluator, (ClassLoader) realm, listener);
} finally {
ClassRealmConverter.popContextRealm();
}
}
public void configureComponent(
Object component,
PlexusConfiguration configuration,
ExpressionEvaluator evaluator,
ClassLoader loader,
ConfigurationListener listener)
throws ComponentConfigurationException {
new EnhancedConfigurationConverter()
.processConfiguration(
converterLookup,
component,
loader, //
configuration,
evaluator,
listener);
}
}

View File

@ -37,7 +37,7 @@ public MojoExecutionScopeModule(MojoExecutionScope scope) {
@Override
protected void configure() {
bindScope(MojoExecutionScoped.class, scope);
bindScope(org.apache.maven.api.di.MojoExecutionScoped.class, scope);
// bindScope(org.apache.maven.api.di.MojoExecutionScoped.class, scope);
bind(MojoExecutionScope.class).toInstance(scope);
bind(MavenProject.class)
.toProvider(MojoExecutionScope.seededKeyProvider())

View File

@ -22,31 +22,25 @@
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.*;
import java.lang.annotation.Annotation;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import com.google.inject.AbstractModule;
import com.google.inject.name.Names;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.api.Project;
import org.apache.maven.api.Session;
import org.apache.maven.api.xml.XmlNode;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.classrealm.ClassRealmManager;
import org.apache.maven.di.Injector;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.scope.internal.MojoExecutionScope;
import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
import org.apache.maven.internal.impl.DefaultLog;
import org.apache.maven.internal.impl.DefaultMojoExecution;
import org.apache.maven.internal.impl.InternalSession;
import org.apache.maven.internal.xml.XmlPlexusConfiguration;
@ -429,29 +423,6 @@ private void discoverPluginComponents(
((DefaultPlexusContainer) container)
.discoverComponents(
pluginRealm,
new AbstractModule() {
@Override
protected void configure() {
if (pluginDescriptor != null) {
for (MojoDescriptor mojo : pluginDescriptor.getMojos()) {
if (mojo.isV4Api()) {
try {
mojo.setRealm(pluginRealm);
Class<?> cl = mojo.getImplementationClass();
if (cl == null) {
cl = pluginRealm.loadClass(mojo.getImplementation());
}
bind(org.apache.maven.api.plugin.Mojo.class)
.annotatedWith(Names.named(mojo.getId()))
.to((Class) cl);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Unable to load mojo class", e);
}
}
}
}
}
},
new SessionScopeModule(container.lookup(SessionScope.class)),
new MojoExecutionScopeModule(container.lookup(MojoExecutionScope.class)),
new PluginConfigurationModule(plugin.getDelegate()));
@ -528,117 +499,191 @@ public <T> T getConfiguredMojo(Class<T> mojoInterface, MavenSession session, Moj
Thread.currentThread().setContextClassLoader(pluginRealm);
try {
T mojo;
try {
mojo = container.lookup(mojoInterface, mojoDescriptor.getRoleHint());
} catch (ComponentLookupException e) {
Throwable cause = e.getCause();
while (cause != null
&& !(cause instanceof LinkageError)
&& !(cause instanceof ClassNotFoundException)) {
cause = cause.getCause();
}
if ((cause instanceof NoClassDefFoundError) || (cause instanceof ClassNotFoundException)) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
+ pluginDescriptor.getId() + "'. A required class is missing: "
+ cause.getMessage());
pluginRealm.display(ps);
throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
} else if (cause instanceof LinkageError) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
+ pluginDescriptor.getId() + "' due to an API incompatibility: "
+ e.getClass().getName() + ": " + cause.getMessage());
pluginRealm.display(ps);
throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
}
throw new PluginContainerException(
mojoDescriptor,
pluginRealm,
"Unable to load the mojo '" + mojoDescriptor.getGoal()
+ "' (or one of its required components) from the plugin '"
+ pluginDescriptor.getId() + "'",
e);
}
if (mojo instanceof ContextEnabled) {
MavenProject project = session.getCurrentProject();
Map<String, Object> pluginContext = session.getPluginContext(pluginDescriptor, project);
if (pluginContext != null) {
pluginContext.put("project", project);
pluginContext.put("pluginDescriptor", pluginDescriptor);
((ContextEnabled) mojo).setPluginContext(pluginContext);
}
}
if (mojo instanceof Mojo) {
Logger mojoLogger = LoggerFactory.getLogger(mojoDescriptor.getImplementation());
((Mojo) mojo).setLog(new MojoLogWrapper(mojoLogger));
}
if (mojo instanceof Contextualizable) {
pluginValidationManager.reportPluginMojoValidationIssue(
PluginValidationManager.IssueLocality.EXTERNAL,
session,
mojoDescriptor,
mojo.getClass(),
"Mojo implements `Contextualizable` interface from Plexus Container, which is EOL.");
}
XmlNode dom = mojoExecution.getConfiguration() != null
? mojoExecution.getConfiguration().getDom()
: null;
PlexusConfiguration pomConfiguration;
if (dom == null) {
pomConfiguration = new DefaultPlexusConfiguration("configuration");
} else {
pomConfiguration = XmlPlexusConfiguration.toPlexusConfiguration(dom);
}
ExpressionEvaluator expressionEvaluator;
InternalSession sessionV4 = InternalSession.from(session.getSession());
if (mojoDescriptor.isV4Api()) {
expressionEvaluator = new PluginParameterExpressionEvaluatorV4(
sessionV4,
sessionV4.getProject(session.getCurrentProject()),
new DefaultMojoExecution(sessionV4, mojoExecution));
return loadV4Mojo(mojoInterface, session, mojoExecution, mojoDescriptor, pluginDescriptor, pluginRealm);
} else {
expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution);
return loadV3Mojo(mojoInterface, session, mojoExecution, mojoDescriptor, pluginDescriptor, pluginRealm);
}
for (MavenPluginConfigurationValidator validator : configurationValidators) {
validator.validate(session, mojoDescriptor, mojo.getClass(), pomConfiguration, expressionEvaluator);
}
populateMojoExecutionFields(
mojo,
mojoExecution.getExecutionId(),
mojoDescriptor,
pluginRealm,
pomConfiguration,
expressionEvaluator);
return mojo;
} finally {
Thread.currentThread().setContextClassLoader(oldClassLoader);
container.setLookupRealm(oldLookupRealm);
}
}
private <T> T loadV4Mojo(
Class<T> mojoInterface,
MavenSession session,
MojoExecution mojoExecution,
MojoDescriptor mojoDescriptor,
PluginDescriptor pluginDescriptor,
ClassRealm pluginRealm)
throws PluginContainerException, PluginConfigurationException {
T mojo;
InternalSession sessionV4 = InternalSession.from(session.getSession());
Project project = sessionV4.getProject(session.getCurrentProject());
org.apache.maven.api.MojoExecution execution = new DefaultMojoExecution(sessionV4, mojoExecution);
org.apache.maven.api.plugin.Log log = new DefaultLog(
LoggerFactory.getLogger(mojoExecution.getMojoDescriptor().getFullGoalName()));
try {
Set<String> classes = new HashSet<>();
try (InputStream is = pluginRealm.getResourceAsStream("META-INF/maven/org.apache.maven.api.di.Inject");
BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(is)))) {
reader.lines().forEach(classes::add);
}
Injector injector = Injector.create();
// Add known classes
// TODO: get those from the existing plexus scopes ?
injector.bindInstance(Session.class, sessionV4);
injector.bindInstance(Project.class, project);
injector.bindInstance(org.apache.maven.api.MojoExecution.class, execution);
injector.bindInstance(org.apache.maven.api.plugin.Log.class, log);
// Add plugin classes
for (String className : classes) {
Class<?> clazz = pluginRealm.loadClass(className);
injector.bindImplicit(clazz);
}
mojo = mojoInterface.cast(injector.getInstance(mojoDescriptor.getImplementationClass()));
} catch (Exception e) {
throw new PluginContainerException(mojoDescriptor, pluginRealm, "Unable to lookup Mojo", e);
}
XmlNode dom = mojoExecution.getConfiguration() != null
? mojoExecution.getConfiguration().getDom()
: null;
PlexusConfiguration pomConfiguration;
if (dom == null) {
pomConfiguration = new DefaultPlexusConfiguration("configuration");
} else {
pomConfiguration = XmlPlexusConfiguration.toPlexusConfiguration(dom);
}
ExpressionEvaluator expressionEvaluator =
new PluginParameterExpressionEvaluatorV4(sessionV4, project, execution);
for (MavenPluginConfigurationValidator validator : configurationValidators) {
validator.validate(session, mojoDescriptor, mojo.getClass(), pomConfiguration, expressionEvaluator);
}
populateMojoExecutionFields(
mojo,
mojoExecution.getExecutionId(),
mojoDescriptor,
pluginRealm,
pomConfiguration,
expressionEvaluator);
return mojo;
}
private <T> T loadV3Mojo(
Class<T> mojoInterface,
MavenSession session,
MojoExecution mojoExecution,
MojoDescriptor mojoDescriptor,
PluginDescriptor pluginDescriptor,
ClassRealm pluginRealm)
throws PluginContainerException, PluginConfigurationException {
T mojo;
try {
mojo = container.lookup(mojoInterface, mojoDescriptor.getRoleHint());
} catch (ComponentLookupException e) {
Throwable cause = e.getCause();
while (cause != null && !(cause instanceof LinkageError) && !(cause instanceof ClassNotFoundException)) {
cause = cause.getCause();
}
if ((cause instanceof NoClassDefFoundError) || (cause instanceof ClassNotFoundException)) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
+ pluginDescriptor.getId() + "'. A required class is missing: "
+ cause.getMessage());
pluginRealm.display(ps);
throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
} else if (cause instanceof LinkageError) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
+ pluginDescriptor.getId() + "' due to an API incompatibility: "
+ e.getClass().getName() + ": " + cause.getMessage());
pluginRealm.display(ps);
throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
}
throw new PluginContainerException(
mojoDescriptor,
pluginRealm,
"Unable to load the mojo '" + mojoDescriptor.getGoal()
+ "' (or one of its required components) from the plugin '"
+ pluginDescriptor.getId() + "'",
e);
}
if (mojo instanceof ContextEnabled) {
MavenProject project = session.getCurrentProject();
Map<String, Object> pluginContext = session.getPluginContext(pluginDescriptor, project);
if (pluginContext != null) {
pluginContext.put("project", project);
pluginContext.put("pluginDescriptor", pluginDescriptor);
((ContextEnabled) mojo).setPluginContext(pluginContext);
}
}
if (mojo instanceof Mojo) {
Logger mojoLogger = LoggerFactory.getLogger(mojoDescriptor.getImplementation());
((Mojo) mojo).setLog(new MojoLogWrapper(mojoLogger));
}
if (mojo instanceof Contextualizable) {
pluginValidationManager.reportPluginMojoValidationIssue(
PluginValidationManager.IssueLocality.EXTERNAL,
session,
mojoDescriptor,
mojo.getClass(),
"Mojo implements `Contextualizable` interface from Plexus Container, which is EOL.");
}
XmlNode dom = mojoExecution.getConfiguration() != null
? mojoExecution.getConfiguration().getDom()
: null;
PlexusConfiguration pomConfiguration;
if (dom == null) {
pomConfiguration = new DefaultPlexusConfiguration("configuration");
} else {
pomConfiguration = XmlPlexusConfiguration.toPlexusConfiguration(dom);
}
InternalSession sessionV4 = InternalSession.from(session.getSession());
ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution);
for (MavenPluginConfigurationValidator validator : configurationValidators) {
validator.validate(session, mojoDescriptor, mojo.getClass(), pomConfiguration, expressionEvaluator);
}
populateMojoExecutionFields(
mojo,
mojoExecution.getExecutionId(),
mojoDescriptor,
pluginRealm,
pomConfiguration,
expressionEvaluator);
return mojo;
}
private void populateMojoExecutionFields(
Object mojo,
String executionId,
@ -892,4 +937,29 @@ private List<Artifact> resolveExtensionArtifacts(
pluginDependenciesResolver.resolvePlugin(extensionPlugin, null, null, repositories, session);
return toMavenArtifacts(root);
}
static class NamedImpl implements Named {
private final String value;
NamedImpl(String value) {
this.value = value;
}
public String value() {
return this.value;
}
@SuppressWarnings("checkstyle:MagicNumber")
public int hashCode() {
return 127 * "value".hashCode() ^ this.value.hashCode();
}
public boolean equals(Object o) {
return o instanceof Named && this.value.equals(((Named) o).value());
}
public Class<? extends Annotation> annotationType() {
return Named.class;
}
}
}

View File

@ -46,7 +46,7 @@ public SessionScopeModule(SessionScope scope) {
@Override
protected void configure() {
bindScope(SessionScoped.class, scope);
bindScope(org.apache.maven.api.di.SessionScoped.class, scope);
// bindScope(org.apache.maven.api.di.SessionScoped.class, scope);
bind(SessionScope.class).toInstance(scope);
bind(MavenSession.class)

View File

@ -23,8 +23,8 @@
import javax.inject.Singleton;
import com.google.inject.OutOfScopeException;
import org.apache.maven.SessionScoped;
import org.apache.maven.api.Session;
import org.apache.maven.api.di.SessionScoped;
import org.apache.maven.session.scope.internal.SessionScope;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;

51
maven-di/pom.xml Normal file
View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.maven</groupId>
<artifactId>maven</artifactId>
<version>4.0.0-alpha-13-SNAPSHOT</version>
</parent>
<artifactId>maven-di</artifactId>
<name>Maven Dependency Injection</name>
<description>Provides the implementation for the Dependency Injection mechanism in Maven</description>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-api-di</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-api-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-xml-impl</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-xml</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di;
import java.lang.annotation.Annotation;
import org.apache.maven.di.impl.InjectorImpl;
public interface Injector {
//
// Builder API
//
static Injector create() {
return new InjectorImpl();
}
Injector bindScope(Class<? extends Annotation> scopeAnnotation, Scope scope);
Injector bindImplicit(Class<?> cls);
<T> Injector bindInstance(Class<T> cls, T instance);
//
// Bean access
//
<T> void injectInstance(T instance);
<T> T getInstance(Class<T> key);
<T> T getInstance(Key<T> key);
}

View File

@ -0,0 +1,161 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Objects;
import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.di.impl.ReflectionUtils;
import org.apache.maven.di.impl.TypeUtils;
import org.apache.maven.di.impl.Types;
import org.apache.maven.di.impl.Utils;
/**
* The key defines an identity of a binding. In any DI, a key is usually a type of the object along
* with some optional tag to distinguish between bindings which make objects of the same type.
* <p>
* In ActiveJ Inject, a key is also a type token - special abstract class that can store type information
* with the shortest syntax possible in Java.
* <p>
* For example, to create a key of type Map&lt;String, List&lt;Integer&gt;&gt;, you can just use
* this syntax: <code>new Key&lt;Map&lt;String, List&lt;Integer&gt;&gt;&gt;(){}</code>.
* <p>
* If your types are not known at compile time, you can use {@link Types#parameterizedType} to make a
* parameterized type and give it to a {@link #ofType Key.ofType} constructor.
*
* @param <T> binding type
*/
public abstract class Key<T> {
private final Type type;
private final @Nullable Object qualifier;
private int hash;
protected Key() {
this(null);
}
protected Key(@Nullable Object qualifier) {
this.type = TypeUtils.simplifyType(getTypeParameter());
this.qualifier = qualifier;
}
protected Key(Type type, @Nullable Object qualifier) {
this.type = TypeUtils.simplifyType(type);
this.qualifier = qualifier;
}
static final class KeyImpl<T> extends Key<T> {
KeyImpl(Type type, Object qualifier) {
super(type, qualifier);
}
}
public static <T> Key<T> of(Class<T> type) {
return new KeyImpl<>(type, null);
}
public static <T> Key<T> of(Class<T> type, @Nullable Object qualifier) {
return new KeyImpl<>(type, qualifier);
}
public static <T> Key<T> ofType(Type type) {
return new KeyImpl<>(type, null);
}
public static <T> Key<T> ofType(Type type, @Nullable Object qualifier) {
return new KeyImpl<>(type, qualifier);
}
private Type getTypeParameter() {
// this cannot possibly fail so not even a check here
Type typeArgument = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
Object outerInstance = ReflectionUtils.getOuterClassInstance(this);
// // the outer instance is null in static context
return outerInstance != null
? Types.bind(typeArgument, Types.getAllTypeBindings(outerInstance.getClass()))
: typeArgument;
}
public Type getType() {
return type;
}
/**
* A shortcut for <code>{@link Types#getRawType(Type)}(key.getType())</code>.
* Also casts the result to a properly parameterized class.
*/
@SuppressWarnings("unchecked")
public Class<T> getRawType() {
return (Class<T>) Types.getRawType(type);
}
/**
* Returns a type parameter of the underlying type wrapped as a key with no qualifier.
*
* @throws IllegalStateException when underlying type is not a parameterized one.
*/
public <U> Key<U> getTypeParameter(int index) {
if (type instanceof ParameterizedType) {
return new KeyImpl<>(((ParameterizedType) type).getActualTypeArguments()[index], null);
}
throw new IllegalStateException("Expected type from key " + getDisplayString() + " to be parameterized");
}
public @Nullable Object getQualifier() {
return qualifier;
}
/**
* Returns an underlying type with display string formatting (package names stripped)
* and prepended qualifier display string if this key has a qualifier.
*/
public String getDisplayString() {
return (qualifier != null ? Utils.getDisplayString(qualifier) + " " : "")
+ ReflectionUtils.getDisplayName(type);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Key<?>)) {
return false;
}
Key<?> that = (Key<?>) o;
return type.equals(that.type) && Objects.equals(qualifier, that.qualifier);
}
@Override
public int hashCode() {
int hashCode = hash;
if (hashCode == 0) {
hash = 31 * type.hashCode() + (qualifier == null ? 0 : qualifier.hashCode());
}
return hash;
}
@Override
public String toString() {
return (qualifier != null ? qualifier + " " : "") + type.getTypeName();
}
}

View File

@ -0,0 +1,27 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di;
import java.lang.annotation.Annotation;
import java.util.function.Supplier;
public interface Scope {
<T> Supplier<T> scope(Key<T> key, Annotation scope, Supplier<T> unscoped);
}

View File

@ -0,0 +1,188 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di.impl;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.maven.di.Key;
import static java.util.stream.Collectors.joining;
public abstract class Binding<T> {
private final Set<Key<?>> dependencies;
private Annotation scope;
private int priority;
private Key<?> originalKey;
protected Binding(Key<? extends T> originalKey, Set<Key<?>> dependencies) {
this(originalKey, dependencies, null, 0);
}
protected Binding(Key<?> originalKey, Set<Key<?>> dependencies, Annotation scope, int priority) {
this.originalKey = originalKey;
this.dependencies = dependencies;
this.scope = scope;
this.priority = priority;
}
public static <T> Binding<T> toInstance(T instance) {
return new BindingToInstance<>(instance);
}
public static <R> Binding<R> to(TupleConstructorN<R> constructor, Class<?>[] types) {
return Binding.to(constructor, Stream.of(types).map(Key::of).toArray(Key<?>[]::new));
}
public static <R> Binding<R> to(TupleConstructorN<R> constructor, Key<?>[] dependencies) {
return to(constructor, dependencies, 0);
}
public static <R> Binding<R> to(TupleConstructorN<R> constructor, Key<?>[] dependencies, int priority) {
return new BindingToConstructor<>(null, constructor, dependencies, priority);
}
// endregion
public Binding<T> scope(Annotation scope) {
this.scope = scope;
return this;
}
public Binding<T> prioritize(int priority) {
this.priority = priority;
return this;
}
public Binding<T> withKey(Key<?> key) {
this.originalKey = key;
return this;
}
public Binding<T> initializeWith(BindingInitializer<T> bindingInitializer) {
return new Binding<T>(
this.originalKey,
Stream.of(this.dependencies, bindingInitializer.getDependencies())
.flatMap(Set::stream)
.collect(Collectors.toSet()),
this.scope,
this.priority) {
@Override
public Supplier<T> compile(Function<Key<?>, Supplier<?>> compiler) {
final Supplier<T> compiledBinding = Binding.this.compile(compiler);
final Consumer<T> consumer = bindingInitializer.compile(compiler);
return () -> {
T instance = compiledBinding.get();
consumer.accept(instance);
return instance;
};
}
@Override
public String toString() {
return Binding.this.toString();
}
};
}
public abstract Supplier<T> compile(Function<Key<?>, Supplier<?>> compiler);
public Set<Key<?>> getDependencies() {
return dependencies;
}
public Annotation getScope() {
return scope;
}
public String getDisplayString() {
return dependencies.stream().map(Key::getDisplayString).collect(joining(", ", "[", "]"));
}
public Key<?> getOriginalKey() {
return originalKey;
}
public int getPriority() {
return priority;
}
@Override
public String toString() {
return "Binding" + dependencies.toString();
}
@FunctionalInterface
public interface TupleConstructorN<R> {
R create(Object... args);
}
public static class BindingToInstance<T> extends Binding<T> {
final T instance;
BindingToInstance(T instance) {
super(null, Collections.emptySet());
this.instance = instance;
}
@Override
public Supplier<T> compile(Function<Key<?>, Supplier<?>> compiler) {
return () -> instance;
}
@Override
public String toString() {
return "BindingToInstance[" + instance + "]" + getDependencies();
}
}
public static class BindingToConstructor<T> extends Binding<T> {
final TupleConstructorN<T> constructor;
BindingToConstructor(
Key<? extends T> key, TupleConstructorN<T> constructor, Key<?>[] dependencies, int priority) {
super(key, new HashSet<>(Arrays.asList(dependencies)), null, priority);
this.constructor = constructor;
}
@Override
public Supplier<T> compile(Function<Key<?>, Supplier<?>> compiler) {
return () -> {
Object[] args = getDependencies().stream()
.map(compiler)
.map(Supplier::get)
.toArray();
return constructor.create(args);
};
}
@Override
public String toString() {
return "BindingToConstructor[" + constructor + "]" + getDependencies();
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di.impl;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.maven.di.Key;
import static java.util.stream.Collectors.toSet;
public abstract class BindingInitializer<T> {
private final Set<Key<?>> dependencies;
protected BindingInitializer(Set<Key<?>> dependencies) {
this.dependencies = dependencies;
}
public Set<Key<?>> getDependencies() {
return dependencies;
}
public abstract Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler);
public static <T> BindingInitializer<T> combine(List<BindingInitializer<T>> bindingInitializers) {
Set<Key<?>> deps = bindingInitializers.stream()
.map(BindingInitializer::getDependencies)
.flatMap(Collection::stream)
.collect(toSet());
return new BindingInitializer<T>(deps) {
@Override
public Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler) {
return instance -> bindingInitializers.stream()
.map(bindingInitializer -> bindingInitializer.compile(compiler))
.forEach(i -> i.accept(instance));
}
};
}
}

View File

@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di.impl;
import org.apache.maven.di.Injector;
/**
* A runtime exception that is thrown on startup when some static conditions fail
* (missing or cyclic dependencies, incorrect annotations etc.) or in runtime when
* you ask an {@link Injector} for an instance it does not have a {@link Binding binding} for.
*/
public final class DIException extends RuntimeException {
public DIException(String message) {
super(message);
}
public DIException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,357 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di.impl;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.maven.api.di.Provides;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.di.Typed;
import org.apache.maven.di.Injector;
import org.apache.maven.di.Key;
import org.apache.maven.di.Scope;
public class InjectorImpl implements Injector {
private final Map<Key<?>, Set<Binding<?>>> bindings = new HashMap<>();
private final Map<Class<? extends Annotation>, Scope> scopes = new HashMap<>();
public InjectorImpl() {
bindScope(Singleton.class, new SingletonScope());
}
public <T> T getInstance(Class<T> key) {
return getInstance(Key.of(key));
}
public <T> T getInstance(Key<T> key) {
return getCompiledBinding(key).get();
}
@SuppressWarnings("unchecked")
@Override
public <T> void injectInstance(T instance) {
ReflectionUtils.generateInjectingInitializer(Key.of((Class<T>) instance.getClass()))
.compile(this::getCompiledBinding)
.accept(instance);
}
public Injector bindScope(Class<? extends Annotation> scopeAnnotation, Scope scope) {
if (scopes.put(scopeAnnotation, scope) != null) {
throw new DIException(
"Cannot rebind scope annotation class to a different implementation: " + scopeAnnotation);
}
return this;
}
public <U> Injector bindInstance(Class<U> clazz, U instance) {
Key<?> key = Key.of(clazz, ReflectionUtils.qualifierOf(clazz));
Binding<U> binding = Binding.toInstance(instance);
return doBind(key, binding);
}
@Override
public Injector bindImplicit(Class<?> clazz) {
Key<?> key = Key.of(clazz, ReflectionUtils.qualifierOf(clazz));
Binding<?> binding = ReflectionUtils.generateImplicitBinding(key);
return doBind(key, binding);
}
private Injector doBind(Key<?> key, Binding<?> binding) {
doBindImplicit(key, binding);
Class<?> cls = key.getRawType().getSuperclass();
while (cls != Object.class && cls != null) {
key = Key.of(cls, key.getQualifier());
doBindImplicit(key, binding);
cls = cls.getSuperclass();
}
return this;
}
protected <U> Injector bind(Key<U> key, Binding<U> b) {
Set<Binding<?>> bindingSet = bindings.computeIfAbsent(key, $ -> new HashSet<>());
bindingSet.add(b);
return this;
}
@SuppressWarnings({"unchecked", "rawtypes"})
private <T> Set<Binding<T>> getBindings(Key<T> key) {
return (Set) bindings.get(key);
}
public <Q> Supplier<Q> getCompiledBinding(Key<Q> key) {
Set<Binding<Q>> res = getBindings(key);
if (res != null) {
List<Binding<Q>> bindingList = new ArrayList<>(res);
Comparator<Binding<Q>> comparing = Comparator.comparing(Binding::getPriority);
bindingList.sort(comparing.reversed());
Binding<Q> binding = bindingList.get(0);
return compile(binding);
}
if (key.getRawType() == List.class) {
Set<Binding<Object>> res2 = getBindings(key.getTypeParameter(0));
if (res2 != null) {
List<Supplier<Object>> bindingList =
res2.stream().map(this::compile).collect(Collectors.toList());
//noinspection unchecked
return () -> (Q) new WrappingList<>(bindingList, Supplier::get);
}
}
if (key.getRawType() == Map.class) {
Key<?> k = key.getTypeParameter(0);
Key<Object> v = key.getTypeParameter(1);
Set<Binding<Object>> res2 = getBindings(v);
if (k.getRawType() == String.class && res2 != null) {
Map<String, Supplier<Object>> map = res2.stream()
.filter(b -> b.getOriginalKey().getQualifier() == null
|| b.getOriginalKey().getQualifier() instanceof String)
.collect(Collectors.toMap(
b -> (String) b.getOriginalKey().getQualifier(), this::compile));
//noinspection unchecked
return (() -> (Q) new WrappingMap<>(map, Supplier::get));
}
}
throw new DIException("No binding to construct an instance for key "
+ key.getDisplayString() + ". Existing bindings:\n"
+ bindings.keySet().stream().map(Key::toString).collect(Collectors.joining("\n - ", " - ", "")));
}
@SuppressWarnings("unchecked")
private <Q> Supplier<Q> compile(Binding<Q> binding) {
Supplier<Q> compiled = binding.compile(this::getCompiledBinding);
if (binding.getScope() != null) {
Scope scope = scopes.entrySet().stream()
.filter(e -> e.getKey().isInstance(binding.getScope()))
.map(Map.Entry::getValue)
.findFirst()
.orElseThrow(() -> new DIException("Scope not bound for annotation "
+ binding.getScope().getClass()));
compiled = scope.scope((Key<Q>) binding.getOriginalKey(), binding.getScope(), compiled);
}
return compiled;
}
protected void doBindImplicit(Key<?> key, Binding<?> binding) {
if (binding != null) {
// For non-explicit bindings, also bind all their base classes and interfaces according to the @Type
Set<Key<?>> toBind = new HashSet<>();
Deque<Key<?>> todo = new ArrayDeque<>();
todo.add(key);
Set<Class<?>> types;
Typed typed = key.getRawType().getAnnotation(Typed.class);
if (typed != null) {
Class<?>[] typesArray = typed.value();
if (typesArray == null || typesArray.length == 0) {
types = new HashSet<>(Arrays.asList(key.getRawType().getInterfaces()));
types.add(Object.class);
} else {
types = new HashSet<>(Arrays.asList(typesArray));
}
} else {
types = null;
}
Set<Key<?>> done = new HashSet<>();
while (!todo.isEmpty()) {
Key<?> type = todo.remove();
if (done.add(type)) {
Class<?> cls = Types.getRawType(type.getType());
Type[] interfaces = cls.getGenericInterfaces();
Arrays.stream(interfaces)
.map(t -> Key.ofType(t, key.getQualifier()))
.forEach(todo::add);
Type supercls = cls.getGenericSuperclass();
if (supercls != null) {
todo.add(Key.ofType(supercls, key.getQualifier()));
}
if (types == null || types.contains(cls)) {
toBind.add(type);
}
}
}
// Also bind without the qualifier
if (key.getQualifier() != null) {
new HashSet<>(toBind).forEach(k -> toBind.add(Key.ofType(k.getType())));
}
toBind.forEach((k -> bind((Key<Object>) k, (Binding<Object>) binding)));
}
// Bind inner classes
for (Class<?> inner : key.getRawType().getDeclaredClasses()) {
bindImplicit(inner);
}
// Bind inner providers
for (Method method : key.getRawType().getDeclaredMethods()) {
if (method.isAnnotationPresent(Provides.class)) {
Object qualifier = ReflectionUtils.qualifierOf(method);
Annotation scope = ReflectionUtils.scopeOf(method);
TypeVariable<Method>[] methodTypeParameters = method.getTypeParameters();
if (methodTypeParameters.length != 0) {
throw new DIException("Parameterized method are not supported " + method);
}
Map<TypeVariable<?>, Type> mapping = new HashMap<>();
for (TypeVariable<Method> methodTypeParameter : methodTypeParameters) {
mapping.put(methodTypeParameter, methodTypeParameter);
}
mapping.putAll(Types.getAllTypeBindings(key.getRawType()));
Type returnType = Types.bind(method.getGenericReturnType(), mapping);
Key<Object> rkey = Key.ofType(returnType, qualifier);
Set<Class<?>> types;
Typed typed = method.getAnnotation(Typed.class);
if (typed != null) {
Class<?>[] typesArray = typed.value();
if (typesArray == null || typesArray.length == 0) {
types = new HashSet<>(Arrays.asList(rkey.getRawType().getInterfaces()));
types.add(Object.class);
} else {
types = new HashSet<>(Arrays.asList(typesArray));
}
} else {
types = null;
}
Set<Key<?>> toBind = new HashSet<>();
Deque<Key<?>> todo = new ArrayDeque<>();
todo.add(rkey);
Set<Key<?>> done = new HashSet<>();
while (!todo.isEmpty()) {
Key<?> type = todo.remove();
if (done.add(type)) {
Class<?> cls = Types.getRawType(type.getType());
Type[] interfaces = cls.getGenericInterfaces();
Arrays.stream(interfaces)
.map(t -> Key.ofType(t, qualifier))
.forEach(todo::add);
Type supercls = cls.getGenericSuperclass();
if (supercls != null) {
todo.add(Key.ofType(supercls, qualifier));
}
if (types == null || types.contains(cls)) {
toBind.add(type);
}
}
}
// Also bind without the qualifier
if (qualifier != null) {
new HashSet<>(toBind).forEach(k -> toBind.add(Key.ofType(k.getType())));
}
Binding<Object> bind = ReflectionUtils.bindingFromMethod(method).scope(scope);
toBind.forEach((k -> bind((Key<Object>) k, bind)));
}
}
}
private static class WrappingMap<K, V, T> extends AbstractMap<K, V> {
private final Map<K, T> delegate;
private final Function<T, V> mapper;
WrappingMap(Map<K, T> delegate, Function<T, V> mapper) {
this.delegate = delegate;
this.mapper = mapper;
}
@SuppressWarnings("NullableProblems")
@Override
public Set<Entry<K, V>> entrySet() {
return new AbstractSet<Entry<K, V>>() {
@Override
public Iterator<Entry<K, V>> iterator() {
Iterator<Entry<K, T>> it = delegate.entrySet().iterator();
return new Iterator<Entry<K, V>>() {
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Entry<K, V> next() {
Entry<K, T> n = it.next();
return new SimpleImmutableEntry<>(n.getKey(), mapper.apply(n.getValue()));
}
};
}
@Override
public int size() {
return delegate.size();
}
};
}
}
private static class WrappingList<Q, T> extends AbstractList<Q> {
private final List<T> delegate;
private final Function<T, Q> mapper;
WrappingList(List<T> delegate, Function<T, Q> mapper) {
this.delegate = delegate;
this.mapper = mapper;
}
@Override
public Q get(int index) {
return mapper.apply(delegate.get(index));
}
@Override
public int size() {
return delegate.size();
}
}
private static class SingletonScope implements Scope {
Map<Key<?>, java.util.function.Supplier<?>> cache = new HashMap<>();
@SuppressWarnings("unchecked")
@Override
public <T> java.util.function.Supplier<T> scope(
Key<T> key, Annotation scope, java.util.function.Supplier<T> unscoped) {
return (java.util.function.Supplier<T>)
cache.computeIfAbsent(key, k -> new java.util.function.Supplier<T>() {
volatile T instance;
@Override
public T get() {
if (instance == null) {
synchronized (this) {
if (instance == null) {
instance = unscoped.get();
}
}
}
return instance;
}
});
}
}
}

View File

@ -0,0 +1,381 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di.impl;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.api.di.*;
import org.apache.maven.di.Key;
import static java.util.stream.Collectors.toList;
public final class ReflectionUtils {
private static final String IDENT = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
private static final Pattern PACKAGE = Pattern.compile("(?:" + IDENT + "\\.)*");
private static final Pattern PACKAGE_AND_PARENT = Pattern.compile(PACKAGE.pattern() + "(?:" + IDENT + "\\$\\d*)?");
private static final Pattern ARRAY_SIGNATURE = Pattern.compile("\\[L(.*?);");
public static String getDisplayName(Type type) {
Class<?> raw = Types.getRawType(type);
String typeName;
if (raw.isAnonymousClass()) {
Type superclass = raw.getGenericSuperclass();
typeName = "? extends " + superclass.getTypeName();
} else {
typeName = type.getTypeName();
}
return PACKAGE_AND_PARENT
.matcher(ARRAY_SIGNATURE.matcher(typeName).replaceAll("$1[]"))
.replaceAll("");
}
public static @Nullable Object getOuterClassInstance(Object innerClassInstance) {
if (innerClassInstance == null) {
return null;
}
Class<?> cls = innerClassInstance.getClass();
Class<?> enclosingClass = cls.getEnclosingClass();
if (enclosingClass == null) {
return null;
}
for (Field field : cls.getDeclaredFields()) {
if (!field.isSynthetic() || !field.getName().startsWith("this$") || field.getType() != enclosingClass) {
continue;
}
field.setAccessible(true);
try {
return field.get(innerClassInstance);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
return null;
}
public static @Nullable Object qualifierOf(AnnotatedElement annotatedElement) {
Object qualifier = null;
for (Annotation annotation : annotatedElement.getDeclaredAnnotations()) {
if (annotation.annotationType().isAnnotationPresent(Qualifier.class)) {
if (qualifier != null) {
throw new DIException("More than one qualifier annotation on " + annotatedElement);
}
if (annotation instanceof Named) {
qualifier = ((Named) annotation).value();
} else {
Class<? extends Annotation> annotationType = annotation.annotationType();
qualifier = Utils.isMarker(annotationType) ? annotationType : annotation;
}
}
}
return qualifier;
}
public static @Nullable Annotation scopeOf(AnnotatedElement annotatedElement) {
Annotation scope = null;
for (Annotation annotation : annotatedElement.getDeclaredAnnotations()) {
if (annotation.annotationType().isAnnotationPresent(org.apache.maven.api.di.Scope.class)) {
if (scope != null) {
throw new DIException("More than one scope annotation on " + annotatedElement);
}
scope = annotation;
}
}
return scope;
}
public static <T> Key<T> keyOf(@Nullable Type container, Type type, AnnotatedElement annotatedElement) {
return Key.ofType(
container != null ? Types.bind(type, Types.getAllTypeBindings(container)) : type,
qualifierOf(annotatedElement));
}
public static <T extends AnnotatedElement & Member> List<T> getAnnotatedElements(
Class<?> cls,
Class<? extends Annotation> annotationType,
Function<Class<?>, T[]> extractor,
boolean allowStatic) {
List<T> result = new ArrayList<>();
while (cls != null) {
for (T element : extractor.apply(cls)) {
if (element.isAnnotationPresent(annotationType)) {
if (!allowStatic && Modifier.isStatic(element.getModifiers())) {
throw new DIException(
"@" + annotationType.getSimpleName() + " annotation is not allowed on " + element);
}
result.add(element);
}
}
cls = cls.getSuperclass();
}
return result;
}
public static <T> @Nullable Binding<T> generateImplicitBinding(Key<T> key) {
Binding<T> binding = generateConstructorBinding(key);
if (binding != null) {
Annotation scope = scopeOf(key.getRawType());
if (scope != null) {
binding = binding.scope(scope);
}
binding = binding.initializeWith(generateInjectingInitializer(key));
}
return binding;
}
@SuppressWarnings("unchecked")
public static <T> @Nullable Binding<T> generateConstructorBinding(Key<T> key) {
Class<?> cls = key.getRawType();
Annotation classInjectAnnotation = Stream.of(cls.getAnnotations())
.filter(a -> a.annotationType().isAnnotationPresent(Qualifier.class))
.findAny()
.orElse(null);
List<Constructor<?>> constructors = Arrays.asList(cls.getDeclaredConstructors());
List<Constructor<?>> injectConstructors = constructors.stream()
.filter(c -> c.isAnnotationPresent(Inject.class))
.collect(toList());
List<Method> factoryMethods = Arrays.stream(cls.getDeclaredMethods())
.filter(method -> method.getReturnType() == cls && Modifier.isStatic(method.getModifiers()))
.collect(toList());
List<Method> injectFactoryMethods = factoryMethods.stream()
.filter(method -> method.isAnnotationPresent(Inject.class))
.collect(toList());
if (classInjectAnnotation != null) {
if (!injectConstructors.isEmpty()) {
throw failedImplicitBinding(key, "inject annotation on class with inject constructor");
}
if (!factoryMethods.isEmpty()) {
throw failedImplicitBinding(key, "inject annotation on class with factory method");
}
if (constructors.isEmpty()) {
throw failedImplicitBinding(key, "inject annotation on interface");
}
if (constructors.size() > 1) {
throw failedImplicitBinding(key, "inject annotation on class with multiple constructors");
}
Constructor<T> declaredConstructor =
(Constructor<T>) constructors.iterator().next();
Class<?> enclosingClass = cls.getEnclosingClass();
if (enclosingClass != null
&& !Modifier.isStatic(cls.getModifiers())
&& declaredConstructor.getParameterCount() != 1) {
throw failedImplicitBinding(
key,
"inject annotation on local class that closes over outside variables and/or has no default constructor");
}
return bindingFromConstructor(key, declaredConstructor);
}
if (!injectConstructors.isEmpty()) {
if (injectConstructors.size() > 1) {
throw failedImplicitBinding(key, "more than one inject constructor");
}
if (!injectFactoryMethods.isEmpty()) {
throw failedImplicitBinding(key, "both inject constructor and inject factory method are present");
}
return bindingFromConstructor(
key, (Constructor<T>) injectConstructors.iterator().next());
}
if (!injectFactoryMethods.isEmpty()) {
if (injectFactoryMethods.size() > 1) {
throw failedImplicitBinding(key, "more than one inject factory method");
}
return bindingFromMethod(injectFactoryMethods.iterator().next());
}
return null;
}
private static DIException failedImplicitBinding(Key<?> requestedKey, String message) {
return new DIException(
"Failed to generate implicit binding for " + requestedKey.getDisplayString() + ", " + message);
}
public static <T> BindingInitializer<T> generateInjectingInitializer(Key<T> container) {
Class<T> rawType = container.getRawType();
List<BindingInitializer<T>> initializers = Stream.concat(
getAnnotatedElements(rawType, Inject.class, Class::getDeclaredFields, false).stream()
.map(field -> fieldInjector(container, field)),
getAnnotatedElements(rawType, Inject.class, Class::getDeclaredMethods, true).stream()
.filter(method -> !Modifier.isStatic(
method.getModifiers())) // we allow them and just filter out to allow
// static factory methods
.map(method -> methodInjector(container, method)))
.collect(toList());
return BindingInitializer.combine(initializers);
}
public static <T> BindingInitializer<T> fieldInjector(Key<T> container, Field field) {
field.setAccessible(true);
Key<Object> key = keyOf(container.getType(), field.getGenericType(), field);
return new BindingInitializer<T>(Collections.singleton(key)) {
@Override
public Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler) {
Supplier<?> binding = compiler.apply(key);
return (T instance) -> {
Object arg = binding.get();
try {
field.set(instance, arg);
} catch (IllegalAccessException e) {
throw new DIException("Not allowed to set injectable field " + field, e);
}
};
}
};
}
public static <T> BindingInitializer<T> methodInjector(Key<T> container, Method method) {
method.setAccessible(true);
Key<?>[] dependencies = toDependencies(container.getType(), method);
return new BindingInitializer<T>(new HashSet<>(Arrays.asList(dependencies))) {
@Override
public Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler) {
return instance -> {
Object[] args = getDependencies().stream()
.map(compiler)
.map(Supplier::get)
.toArray();
try {
method.invoke(instance, args);
} catch (IllegalAccessException e) {
throw new DIException("Not allowed to call injectable method " + method, e);
} catch (InvocationTargetException e) {
throw new DIException("Failed to call injectable method " + method, e.getCause());
}
};
}
};
}
public static Key<?>[] toDependencies(@Nullable Type container, Executable executable) {
Key<?>[] keys = toArgDependencies(container, executable);
if (executable instanceof Constructor || Modifier.isStatic(executable.getModifiers())) {
return keys;
} else {
Key<?>[] nkeys = new Key[keys.length + 1];
nkeys[0] = Key.ofType(container);
System.arraycopy(keys, 0, nkeys, 1, keys.length);
return nkeys;
}
}
private static Key<?>[] toArgDependencies(@Nullable Type container, Executable executable) {
Parameter[] parameters = executable.getParameters();
Key<?>[] dependencies = new Key<?>[parameters.length];
if (parameters.length == 0) {
return dependencies;
}
Type type = parameters[0].getParameterizedType();
Parameter parameter = parameters[0];
dependencies[0] = keyOf(container, type, parameter);
Type[] genericParameterTypes = executable.getGenericParameterTypes();
boolean hasImplicitDependency = genericParameterTypes.length != parameters.length;
for (int i = 1; i < dependencies.length; i++) {
type = genericParameterTypes[hasImplicitDependency ? i - 1 : i];
parameter = parameters[i];
dependencies[i] = keyOf(container, type, parameter);
}
return dependencies;
}
@SuppressWarnings("unchecked")
public static <T> Binding<T> bindingFromMethod(Method method) {
method.setAccessible(true);
Binding<T> binding = Binding.to(
args -> {
try {
Object instance;
Object[] params;
if (Modifier.isStatic(method.getModifiers())) {
instance = null;
params = args;
} else {
instance = args[0];
params = Arrays.copyOfRange(args, 1, args.length);
}
T result = (T) method.invoke(instance, params);
if (result == null) {
throw new NullPointerException(
"@Provides method must return non-null result, method " + method);
}
return result;
} catch (IllegalAccessException e) {
throw new DIException("Not allowed to call method " + method, e);
} catch (InvocationTargetException e) {
throw new DIException("Failed to call method " + method, e.getCause());
}
},
toDependencies(method.getDeclaringClass(), method));
Priority priority = method.getAnnotation(Priority.class);
if (priority != null) {
binding = binding.prioritize(priority.value());
}
return binding;
}
public static <T> Binding<T> bindingFromConstructor(Key<T> key, Constructor<T> constructor) {
constructor.setAccessible(true);
Key<?>[] dependencies = toDependencies(key.getType(), constructor);
Binding<T> binding = Binding.to(
args -> {
try {
return constructor.newInstance(args);
} catch (InstantiationException e) {
throw new DIException(
"Cannot instantiate object from the constructor " + constructor
+ " to provide requested key " + key,
e);
} catch (IllegalAccessException e) {
throw new DIException(
"Not allowed to call constructor " + constructor + " to provide requested key " + key,
e);
} catch (InvocationTargetException e) {
throw new DIException(
"Failed to call constructor " + constructor + " to provide requested key " + key,
e.getCause());
}
},
dependencies);
Priority priority = constructor.getDeclaringClass().getAnnotation(Priority.class);
if (priority != null) {
binding = binding.prioritize(priority.value());
}
return binding.withKey(key);
}
}

View File

@ -0,0 +1,379 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di.impl;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* This class contains reflection utilities to work with Java types.
* Its main use is for method {@link Types#parameterizedType Types.parameterized}.
* However, just like with {@link ReflectionUtils}, other type utility
* methods are pretty clean too, so they are left public.
*/
public final class TypeUtils {
public static boolean isInheritedFrom(Type type, Type from, Map<Type, Type> dejaVu) {
if (from == Object.class) {
return true;
}
if (matches(type, from, dejaVu) || matches(from, type, dejaVu)) {
return true;
}
if (!(type instanceof Class || type instanceof ParameterizedType || type instanceof GenericArrayType)) {
return false;
}
Class<?> rawType = Types.getRawType(type);
Type superclass = rawType.getGenericSuperclass();
if (superclass != null && isInheritedFrom(superclass, from, dejaVu)) {
return true;
}
return Arrays.stream(rawType.getGenericInterfaces()).anyMatch(iface -> isInheritedFrom(iface, from, dejaVu));
}
public static boolean matches(Type strict, Type pattern) {
return matches(strict, pattern, new HashMap<>());
}
private static boolean matches(Type strict, Type pattern, Map<Type, Type> dejaVu) {
if (strict.equals(pattern) || dejaVu.get(strict) == pattern) {
return true;
}
dejaVu.put(strict, pattern);
try {
if (pattern instanceof WildcardType) {
WildcardType wildcard = (WildcardType) pattern;
return Arrays.stream(wildcard.getUpperBounds())
.allMatch(bound -> isInheritedFrom(strict, bound, dejaVu))
&& Arrays.stream(wildcard.getLowerBounds())
.allMatch(bound -> isInheritedFrom(bound, strict, dejaVu));
}
if (pattern instanceof TypeVariable<?>) {
TypeVariable<?> typevar = (TypeVariable<?>) pattern;
return Arrays.stream(typevar.getBounds()).allMatch(bound -> isInheritedFrom(strict, bound, dejaVu));
}
if (strict instanceof GenericArrayType && pattern instanceof GenericArrayType) {
return matches(
((GenericArrayType) strict).getGenericComponentType(),
((GenericArrayType) pattern).getGenericComponentType(),
dejaVu);
}
if (!(strict instanceof ParameterizedType) || !(pattern instanceof ParameterizedType)) {
return false;
}
ParameterizedType parameterizedStrict = (ParameterizedType) strict;
ParameterizedType parameterizedPattern = (ParameterizedType) pattern;
if (parameterizedPattern.getOwnerType() != null) {
if (parameterizedStrict.getOwnerType() == null) {
return false;
}
if (!matches(parameterizedPattern.getOwnerType(), parameterizedStrict.getOwnerType(), dejaVu)) {
return false;
}
}
if (!matches(parameterizedPattern.getRawType(), parameterizedStrict.getRawType(), dejaVu)) {
return false;
}
Type[] strictParams = parameterizedStrict.getActualTypeArguments();
Type[] patternParams = parameterizedPattern.getActualTypeArguments();
if (strictParams.length != patternParams.length) {
return false;
}
for (int i = 0; i < strictParams.length; i++) {
if (!matches(strictParams[i], patternParams[i], dejaVu)) {
return false;
}
}
return true;
} finally {
dejaVu.remove(strict);
}
}
public static boolean contains(Type type, Type sub) {
if (type.equals(sub)) {
return true;
}
if (type instanceof GenericArrayType) {
return contains(((GenericArrayType) type).getGenericComponentType(), sub);
}
if (!(type instanceof ParameterizedType)) {
return false;
}
ParameterizedType parameterized = (ParameterizedType) type;
if (contains(parameterized.getRawType(), sub)) {
return true;
}
if (parameterized.getOwnerType() != null && contains(parameterized.getOwnerType(), sub)) {
return true;
}
return Arrays.stream(parameterized.getActualTypeArguments()).anyMatch(argument -> contains(argument, sub));
}
// pattern = Map<K, List<V>>
// real = Map<String, List<Integer>>
//
// result = {K -> String, V -> Integer}
public static Map<TypeVariable<?>, Type> extractMatchingGenerics(Type pattern, Type real) {
Map<TypeVariable<?>, Type> result = new HashMap<>();
extractMatchingGenerics(pattern, real, result);
return result;
}
private static void extractMatchingGenerics(Type pattern, Type real, Map<TypeVariable<?>, Type> result) {
if (pattern instanceof TypeVariable) {
result.put((TypeVariable<?>) pattern, real);
return;
}
if (pattern.equals(real)) {
return;
}
if (pattern instanceof GenericArrayType && real instanceof GenericArrayType) {
extractMatchingGenerics(
((GenericArrayType) pattern).getGenericComponentType(),
((GenericArrayType) real).getGenericComponentType(),
result);
return;
}
if (!(pattern instanceof ParameterizedType) || !(real instanceof ParameterizedType)) {
return;
}
ParameterizedType parameterizedPattern = (ParameterizedType) pattern;
ParameterizedType parameterizedReal = (ParameterizedType) real;
if (!parameterizedPattern.getRawType().equals(parameterizedReal.getRawType())) {
return;
}
extractMatchingGenerics(parameterizedPattern.getRawType(), parameterizedReal.getRawType(), result);
if (!Objects.equals(parameterizedPattern.getOwnerType(), parameterizedReal.getOwnerType())) {
return;
}
if (parameterizedPattern.getOwnerType() != null) {
extractMatchingGenerics(parameterizedPattern.getOwnerType(), parameterizedReal.getOwnerType(), result);
}
Type[] patternTypeArgs = parameterizedPattern.getActualTypeArguments();
Type[] realTypeArgs = parameterizedReal.getActualTypeArguments();
if (patternTypeArgs.length != realTypeArgs.length) {
return;
}
for (int i = 0; i < patternTypeArgs.length; i++) {
extractMatchingGenerics(patternTypeArgs[i], realTypeArgs[i], result);
}
}
public static Type simplifyType(Type original) {
if (original instanceof Class) {
return original;
}
if (original instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) original).getGenericComponentType();
Type repackedComponentType = simplifyType(componentType);
if (componentType != repackedComponentType) {
return Types.genericArrayType(repackedComponentType);
}
return original;
}
if (original instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) original;
Type[] typeArguments = parameterizedType.getActualTypeArguments();
Type[] repackedTypeArguments = simplifyTypes(typeArguments);
if (isAllObjects(repackedTypeArguments)) {
return parameterizedType.getRawType();
}
if (typeArguments != repackedTypeArguments) {
return Types.parameterizedType(
parameterizedType.getOwnerType(), parameterizedType.getRawType(), repackedTypeArguments);
}
return original;
}
if (original instanceof TypeVariable) {
throw new IllegalArgumentException("Key should not contain a type variable: " + original);
}
if (original instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) original;
Type[] upperBounds = wildcardType.getUpperBounds();
if (upperBounds.length == 1) {
Type upperBound = upperBounds[0];
if (upperBound != Object.class) {
return simplifyType(upperBound);
}
} else if (upperBounds.length > 1) {
throw new IllegalArgumentException("Multiple upper bounds not supported: " + original);
}
Type[] lowerBounds = wildcardType.getLowerBounds();
if (lowerBounds.length == 1) {
return simplifyType(lowerBounds[0]);
} else if (lowerBounds.length > 1) {
throw new IllegalArgumentException("Multiple lower bounds not supported: " + original);
}
return Object.class;
}
return original;
}
private static Type[] simplifyTypes(Type[] original) {
int length = original.length;
for (int i = 0; i < length; i++) {
Type typeArgument = original[i];
Type repackTypeArgument = simplifyType(typeArgument);
if (repackTypeArgument != typeArgument) {
Type[] repackedTypeArguments = new Type[length];
System.arraycopy(original, 0, repackedTypeArguments, 0, i);
repackedTypeArguments[i++] = repackTypeArgument;
for (; i < length; i++) {
repackedTypeArguments[i] = simplifyType(original[i]);
}
return repackedTypeArguments;
}
}
return original;
}
private static boolean isAllObjects(Type[] types) {
for (Type type : types) {
if (type != Object.class) {
return false;
}
}
return true;
}
/**
* Tests whether a {@code from} type is assignable to {@code to} type
*
* @param to a 'to' type that should be checked for possible assignment
* @param from a 'from' type that should be checked for possible assignment
* @return whether an object of type {@code from} is assignable to an object of type {@code to}
*/
public static boolean isAssignable(Type to, Type from) {
// shortcut
if (to instanceof Class && from instanceof Class) {
return ((Class<?>) to).isAssignableFrom((Class<?>) from);
}
return isAssignable(to, from, false);
}
private static boolean isAssignable(Type to, Type from, boolean strict) {
if (to instanceof WildcardType || from instanceof WildcardType) {
Type[] toUppers, toLowers;
if (to instanceof WildcardType) {
WildcardType wildcardTo = (WildcardType) to;
toUppers = wildcardTo.getUpperBounds();
toLowers = wildcardTo.getLowerBounds();
} else {
toUppers = new Type[] {to};
toLowers = strict ? toUppers : Types.NO_TYPES;
}
Type[] fromUppers, fromLowers;
if (from instanceof WildcardType) {
WildcardType wildcardTo = (WildcardType) to;
fromUppers = wildcardTo.getUpperBounds();
fromLowers = wildcardTo.getLowerBounds();
} else {
fromUppers = new Type[] {from};
fromLowers = strict ? fromUppers : Types.NO_TYPES;
}
for (Type toUpper : toUppers) {
for (Type fromUpper : fromUppers) {
if (!isAssignable(toUpper, fromUpper, false)) {
return false;
}
}
}
if (toLowers.length == 0) {
return true;
}
if (fromLowers.length == 0) {
return false;
}
for (Type toLower : toLowers) {
for (Type fromLower : fromLowers) {
if (!isAssignable(fromLower, toLower, false)) {
return false;
}
}
}
return true;
}
if (to instanceof GenericArrayType) {
to = Types.getRawType(to);
}
if (from instanceof GenericArrayType) {
from = Types.getRawType(from);
}
if (!strict && to instanceof Class) {
return ((Class<?>) to).isAssignableFrom(Types.getRawType(from));
}
Class<?> toRawClazz = Types.getRawType(to);
Type[] toTypeArguments = Types.getActualTypeArguments(to);
return isAssignable(toRawClazz, toTypeArguments, from, strict);
}
private static boolean isAssignable(Class<?> toRawClazz, Type[] toTypeArguments, Type from, boolean strict) {
Class<?> fromRawClazz = Types.getRawType(from);
if (strict && !toRawClazz.equals(fromRawClazz)) {
return false;
}
if (!strict && !toRawClazz.isAssignableFrom(fromRawClazz)) {
return false;
}
if (toRawClazz.isArray()) {
return true;
}
Type[] fromTypeArguments = Types.getActualTypeArguments(from);
if (toRawClazz == fromRawClazz) {
if (toTypeArguments.length > fromTypeArguments.length) {
return false;
}
for (int i = 0; i < toTypeArguments.length; i++) {
if (!isAssignable(toTypeArguments[i], fromTypeArguments[i], true)) {
return false;
}
}
return true;
}
Map<TypeVariable<?>, Type> typeBindings = Types.getTypeBindings(from);
for (Type anInterface : fromRawClazz.getGenericInterfaces()) {
if (isAssignable(
toRawClazz,
toTypeArguments,
Types.bind(anInterface, key -> typeBindings.getOrDefault(key, Types.wildcardTypeAny())),
false)) {
return true;
}
}
Type superclass = fromRawClazz.getGenericSuperclass();
return superclass != null
&& isAssignable(toRawClazz, toTypeArguments, Types.bind(superclass, typeBindings), false);
}
}

View File

@ -0,0 +1,471 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di.impl;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.apache.maven.api.annotations.Nullable;
import static java.util.stream.Collectors.joining;
/**
* Various helper methods for type processing
*/
public class Types {
public static final Type[] NO_TYPES = new Type[0];
public static final WildcardType WILDCARD_TYPE_ANY = new WildcardTypeImpl(new Type[] {Object.class}, new Type[0]);
private static final Map<Type, Map<TypeVariable<?>, Type>> TYPE_BINDINGS_CACHE = new ConcurrentHashMap<>();
/**
* Returns a raw {@link Class} for a given {@link Type}.
* <p>
* A type can be any of {@link Class}, {@link ParameterizedType}, {@link WildcardType},
* {@link GenericArrayType} or {@link TypeVariable}
*/
public static Class<?> getRawType(Type type) {
if (type instanceof Class) {
return (Class<?>) type;
} else if (type instanceof ParameterizedType) {
return (Class<?>) ((ParameterizedType) type).getRawType();
} else if (type instanceof WildcardType) {
Type[] upperBounds = ((WildcardType) type).getUpperBounds();
return getRawType(getUppermostType(upperBounds));
} else if (type instanceof GenericArrayType) {
Class<?> rawComponentType = getRawType(((GenericArrayType) type).getGenericComponentType());
try {
return Class.forName("[L" + rawComponentType.getName() + ";");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
} else if (type instanceof TypeVariable) {
return getRawType(getUppermostType(((TypeVariable<?>) type).getBounds()));
} else {
throw new IllegalArgumentException("Unsupported type: " + type);
}
}
/**
* Returns the most common type among given types
*/
public static Type getUppermostType(Type[] types) {
Type result = types[0];
for (int i = 1; i < types.length; i++) {
Type type = types[i];
if (TypeUtils.isAssignable(type, result)) {
result = type;
continue;
} else if (TypeUtils.isAssignable(result, type)) {
continue;
}
throw new IllegalArgumentException("Unrelated types: " + result + " , " + type);
}
return result;
}
/**
* Returns an array of actual type arguments for a given {@link Type}
*
* @param type type whose actual type arguments should be retrieved
* @return an array of actual type arguments for a given {@link Type}
*/
public static Type[] getActualTypeArguments(Type type) {
if (type instanceof Class) {
return ((Class<?>) type).isArray() ? new Type[] {((Class<?>) type).getComponentType()} : NO_TYPES;
} else if (type instanceof ParameterizedType) {
return ((ParameterizedType) type).getActualTypeArguments();
} else if (type instanceof GenericArrayType) {
return new Type[] {((GenericArrayType) type).getGenericComponentType()};
}
throw new IllegalArgumentException("Unsupported type: " + type);
}
/**
* Returns a map of type bindings for a given {@link Type}
*/
public static Map<TypeVariable<?>, Type> getTypeBindings(Type type) {
Type[] typeArguments = getActualTypeArguments(type);
if (typeArguments.length == 0) {
return Collections.emptyMap();
}
TypeVariable<?>[] typeVariables = getRawType(type).getTypeParameters();
Map<TypeVariable<?>, Type> map = new HashMap<>();
for (int i = 0; i < typeVariables.length; i++) {
map.put(typeVariables[i], typeArguments[i]);
}
return map;
}
/**
* Returns a map of all type bindings for a given {@link Type}.
* Includes type bindings from a whole class hierarchy
*/
public static Map<TypeVariable<?>, Type> getAllTypeBindings(Type type) {
return TYPE_BINDINGS_CACHE.computeIfAbsent(type, t -> {
Map<TypeVariable<?>, Type> mapping = new HashMap<>();
getAllTypeBindingsImpl(t, mapping);
return mapping;
});
}
private static void getAllTypeBindingsImpl(Type type, Map<TypeVariable<?>, Type> mapping) {
Class<?> cls = getRawType(type);
if (type instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
if (typeArguments.length != 0) {
TypeVariable<? extends Class<?>>[] typeVariables = cls.getTypeParameters();
for (int i = 0; i < typeArguments.length; i++) {
Type typeArgument = typeArguments[i];
mapping.put(
typeVariables[i],
typeArgument instanceof TypeVariable
? Objects.requireNonNull(mapping.get(typeArgument))
: typeArgument);
}
}
}
Type superclass = cls.getGenericSuperclass();
if (superclass != null) {
getAllTypeBindingsImpl(superclass, mapping);
}
for (Type anInterface : cls.getGenericInterfaces()) {
getAllTypeBindingsImpl(anInterface, mapping);
}
}
/**
* Binds a given type with actual type arguments
*
* @param type a type to be bound
* @param bindings a map of actual types
*/
public static Type bind(Type type, Map<TypeVariable<?>, Type> bindings) {
return bind(type, bindings::get);
}
/**
* Binds a given type with actual type arguments
*
* @param type a type to be bound
* @param bindings a lookup function for actual types
*/
public static Type bind(Type type, Function<TypeVariable<?>, Type> bindings) {
if (type instanceof Class) {
return type;
}
if (type instanceof TypeVariable<?>) {
TypeVariable<?> typeVariable = (TypeVariable<?>) type;
Type actualType = bindings.apply(typeVariable);
if (actualType == null) {
throw new IllegalArgumentException("Type variable not found: " + typeVariable + " ( "
+ typeVariable.getGenericDeclaration() + " ) ");
}
return actualType;
}
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] typeArguments = parameterizedType.getActualTypeArguments();
Type[] typeArguments2 = new Type[typeArguments.length];
for (int i = 0; i < typeArguments.length; i++) {
typeArguments2[i] = bind(typeArguments[i], bindings);
}
return new ParameterizedTypeImpl(
parameterizedType.getOwnerType(), parameterizedType.getRawType(), typeArguments2);
}
if (type instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) type).getGenericComponentType();
return new GenericArrayTypeImpl(bind(componentType, bindings));
}
if (type instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type;
Type[] upperBounds = wildcardType.getUpperBounds();
Type[] upperBounds2 = new Type[upperBounds.length];
for (int i = 0; i < upperBounds.length; i++) {
upperBounds2[i] = bind(upperBounds[i], bindings);
}
Type[] lowerBounds = wildcardType.getLowerBounds();
Type[] lowerBounds2 = new Type[lowerBounds.length];
for (int i = 0; i < lowerBounds.length; i++) {
lowerBounds2[i] = bind(lowerBounds[i], bindings);
}
return new WildcardTypeImpl(upperBounds2, lowerBounds2);
}
throw new IllegalArgumentException("Unsupported type: " + type);
}
/**
* Creates an instance of {@link ParameterizedType}
*
* @param ownerType an owner type
* @param rawType a type to be parameterized
* @param parameters parameter types
* @return an instance of {@link ParameterizedType}
*/
public static ParameterizedType parameterizedType(@Nullable Type ownerType, Type rawType, Type[] parameters) {
return new ParameterizedTypeImpl(ownerType, rawType, parameters);
}
/**
* Creates an instance of {@link ParameterizedType}
*
* @see #parameterizedType(Type, Type, Type[])
*/
public static ParameterizedType parameterizedType(Class<?> rawType, Type... parameters) {
return new ParameterizedTypeImpl(null, rawType, parameters);
}
public static final class ParameterizedTypeImpl implements ParameterizedType {
private final @Nullable Type ownerType;
private final Type rawType;
private final Type[] actualTypeArguments;
ParameterizedTypeImpl(@Nullable Type ownerType, Type rawType, Type[] actualTypeArguments) {
this.ownerType = ownerType;
this.rawType = rawType;
this.actualTypeArguments = actualTypeArguments;
}
@Override
public Type getRawType() {
return rawType;
}
@Override
public Type[] getActualTypeArguments() {
return actualTypeArguments;
}
@Override
public @Nullable Type getOwnerType() {
return ownerType;
}
@Override
public int hashCode() {
return Objects.hashCode(ownerType) ^ Arrays.hashCode(actualTypeArguments) ^ rawType.hashCode();
}
@Override
public boolean equals(Object other) {
if (!(other instanceof ParameterizedType)) {
return false;
}
ParameterizedType that = (ParameterizedType) other;
return this.getRawType().equals(that.getRawType())
&& Objects.equals(this.getOwnerType(), that.getOwnerType())
&& Arrays.equals(this.getActualTypeArguments(), that.getActualTypeArguments());
}
@Override
public String toString() {
return rawType.getTypeName()
+ Arrays.stream(actualTypeArguments).map(Types::toString).collect(joining(", ", "<", ">"));
}
}
/**
* Creates an instance of {@link WildcardType} bound by upper and lower bounds
*
* @param upperBounds a wildcard upper bound types
* @param lowerBounds a wildcard lower bound types
* @return an instance of {@link WildcardType}
*/
public static WildcardType wildcardType(Type[] upperBounds, Type[] lowerBounds) {
return new WildcardTypeImpl(upperBounds, lowerBounds);
}
/**
* Returns an instance of {@link WildcardType} that matches any type
* <p>
* E.g. {@code <?>}
*
* @see #wildcardType(Type[], Type[])
*/
public static WildcardType wildcardTypeAny() {
return WILDCARD_TYPE_ANY;
}
/**
* Creates an instance of {@link WildcardType} bound by a single upper bound
* <p>
* E.g. {@code <? extends UpperBound>}
*
* @param upperBound a wildcard upper bound type
* @return an instance of {@link WildcardType}
* @see #wildcardType(Type[], Type[])
*/
public static WildcardType wildcardTypeExtends(Type upperBound) {
return new WildcardTypeImpl(new Type[] {upperBound}, NO_TYPES);
}
/**
* Creates an instance of {@link WildcardType} bound by a single lower bound
* <p>
* E.g. {@code <? super LowerBound>}
*
* @param lowerBound a wildcard lower bound type
* @return an instance of {@link WildcardType}
* @see #wildcardType(Type[], Type[])
*/
public static WildcardType wildcardTypeSuper(Type lowerBound) {
return new WildcardTypeImpl(NO_TYPES, new Type[] {lowerBound});
}
public static class WildcardTypeImpl implements WildcardType {
private final Type[] upperBounds;
private final Type[] lowerBounds;
WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
this.upperBounds = upperBounds;
this.lowerBounds = lowerBounds;
}
@Override
public Type[] getUpperBounds() {
return upperBounds;
}
@Override
public Type[] getLowerBounds() {
return lowerBounds;
}
@Override
public int hashCode() {
return Arrays.hashCode(upperBounds) ^ Arrays.hashCode(lowerBounds);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof WildcardType)) {
return false;
}
WildcardType that = (WildcardType) other;
return Arrays.equals(this.getUpperBounds(), that.getUpperBounds())
&& Arrays.equals(this.getLowerBounds(), that.getLowerBounds());
}
@Override
public String toString() {
return "?"
+ (upperBounds.length == 0
? ""
: " extends "
+ Arrays.stream(upperBounds)
.map(Types::toString)
.collect(joining(" & ")))
+ (lowerBounds.length == 0
? ""
: " super "
+ Arrays.stream(lowerBounds)
.map(Types::toString)
.collect(joining(" & ")));
}
}
/**
* Creates an instance of {@link GenericArrayType} with a given component type
* <p>
* Same as {@code T[]}
*
* @param componentType a component type of generic array
* @return an instance of {@link GenericArrayType}
* @see #wildcardType(Type[], Type[])
*/
public static GenericArrayType genericArrayType(Type componentType) {
return new GenericArrayTypeImpl(componentType);
}
public static final class GenericArrayTypeImpl implements GenericArrayType {
private final Type componentType;
GenericArrayTypeImpl(Type componentType) {
this.componentType = componentType;
}
@Override
public Type getGenericComponentType() {
return componentType;
}
@Override
public int hashCode() {
return componentType.hashCode();
}
@Override
public boolean equals(Object other) {
if (!(other instanceof GenericArrayType)) {
return false;
}
GenericArrayType that = (GenericArrayType) other;
return this.getGenericComponentType().equals(that.getGenericComponentType());
}
@Override
public String toString() {
return Types.toString(componentType) + "[]";
}
}
private static String toString(Type type) {
return type instanceof Class ? ((Class<?>) type).getName() : type.toString();
}
/**
* Returns a simple name for a given {@link Type}
*
* @see Class#getSimpleName()
*/
public static String getSimpleName(Type type) {
if (type instanceof Class) {
return ((Class<?>) type).getSimpleName();
} else if (type instanceof ParameterizedType) {
return Arrays.stream(((ParameterizedType) type).getActualTypeArguments())
.map(Types::getSimpleName)
.collect(joining(",", "<", ">"));
} else if (type instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type;
Type[] upperBounds = wildcardType.getUpperBounds();
Type[] lowerBounds = wildcardType.getLowerBounds();
return "?"
+ (upperBounds.length == 0
? ""
: " extends "
+ Arrays.stream(upperBounds)
.map(Types::getSimpleName)
.collect(joining(" & ")))
+ (lowerBounds.length == 0
? ""
: " super "
+ Arrays.stream(lowerBounds)
.map(Types::getSimpleName)
.collect(joining(" & ")));
} else if (type instanceof GenericArrayType) {
return Types.getSimpleName(((GenericArrayType) type).getGenericComponentType()) + "[]";
}
return type.getTypeName();
}
}

View File

@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di.impl;
import java.lang.annotation.Annotation;
import org.apache.maven.api.annotations.Nullable;
public final class Utils {
public static String getDisplayString(Class<? extends Annotation> annotationType, @Nullable Annotation annotation) {
if (annotation == null) {
return "@" + ReflectionUtils.getDisplayName(annotationType);
}
String typeName = annotationType.getName();
String str = annotation.toString();
return str.startsWith("@" + typeName)
? "@" + ReflectionUtils.getDisplayName(annotationType) + str.substring(typeName.length() + 1)
: str;
}
public static String getDisplayString(Object object) {
if (object instanceof Class && ((Class<?>) object).isAnnotation()) {
//noinspection unchecked
return getDisplayString((Class<? extends Annotation>) object, null);
}
if (object instanceof Annotation) {
Annotation annotation = (Annotation) object;
return getDisplayString(annotation.annotationType(), annotation);
}
return object.toString();
}
public static boolean isMarker(Class<? extends Annotation> annotationType) {
return annotationType.getDeclaredMethods().length == 0;
}
}

View File

@ -0,0 +1,152 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di.processor;
import javax.annotation.processing.Completion;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
import javax.tools.StandardLocation;
import javax.xml.stream.XMLStreamReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.ctc.wstx.stax.WstxInputFactory;
import org.apache.maven.api.di.Qualifier;
import org.apache.maven.api.xml.XmlNode;
import org.apache.maven.internal.xml.XmlNodeBuilder;
public class IndexAnnotationProcessor implements Processor {
private ProcessingEnvironment environment;
private Set<String> index = new TreeSet<>();
@Override
public void init(ProcessingEnvironment processingEnv) {
this.environment = processingEnv;
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
if (annotation.getAnnotation(Qualifier.class) != null) {
for (Element elem : roundEnv.getElementsAnnotatedWith(annotation)) {
if (elem.getKind().isClass()) {
addClassToIndex(environment
.getElementUtils()
.getBinaryName((TypeElement) elem)
.toString());
}
}
}
}
for (Element elem : roundEnv.getElementsAnnotatedWith(org.apache.maven.api.plugin.annotations.Mojo.class)) {
PackageElement packageElement = environment.getElementUtils().getPackageOf(elem);
String packageName = packageElement.getQualifiedName().toString();
String generatorClassName = elem.getSimpleName().toString() + "Factory";
String mojoName = elem.getAnnotation(org.apache.maven.api.plugin.annotations.Mojo.class)
.name();
try {
Reader reader = environment
.getFiler()
.getResource(StandardLocation.CLASS_OUTPUT, "", "META-INF/maven/plugin.xml")
.openReader(true);
XMLStreamReader parser = WstxInputFactory.newFactory().createXMLStreamReader(reader);
XmlNode plugin = XmlNodeBuilder.build(parser, null);
String groupId = plugin.getChild("groupId").getValue();
String artifactId = plugin.getChild("artifactId").getValue();
String version = plugin.getChild("version").getValue();
Writer file = environment
.getFiler()
.createSourceFile(packageName + "." + generatorClassName)
.openWriter();
file.write("package " + packageName + ";\n");
file.write("public class " + generatorClassName + " {\n");
file.write(" @org.apache.maven.api.di.Named(\"" + groupId + ":" + artifactId + ":" + version + ":"
+ mojoName + "\")\n");
file.write(" @org.apache.maven.api.di.Provides\n");
file.write(" public static " + ((TypeElement) elem).getQualifiedName() + " create() {\n");
file.write(" return new " + ((TypeElement) elem).getQualifiedName() + "();\n");
file.write(" }\n");
file.write("}\n");
file.flush();
file.close();
} catch (Exception ex) {
Logger.getLogger(IndexAnnotationProcessor.class.getName()).log(Level.SEVERE, null, ex);
}
addClassToIndex(packageName + "." + generatorClassName);
}
if (roundEnv.processingOver()) {
flushIndex();
}
return false;
}
protected void addClassToIndex(String className) {
index.add(className);
}
protected void flushIndex() {
try (BufferedWriter writer = new BufferedWriter(environment
.getFiler()
.createResource(StandardLocation.CLASS_OUTPUT, "", "META-INF/maven/org.apache.maven.api.di.Inject")
.openWriter())) {
for (String line : index) {
writer.write(line);
writer.newLine();
}
} catch (IOException e) {
environment.getMessager().printMessage(Diagnostic.Kind.WARNING, e.toString());
}
}
@Override
public Iterable<? extends Completion> getCompletions(
Element element, AnnotationMirror annotation, ExecutableElement member, String userText) {
return Collections.emptySet();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton("*");
}
@Override
public Set<String> getSupportedOptions() {
return Collections.emptySet();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}

View File

@ -0,0 +1 @@
org.apache.maven.di.processor.IndexAnnotationProcessor

View File

@ -0,0 +1,277 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di.impl;
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.maven.api.di.*;
import org.apache.maven.di.Injector;
import org.apache.maven.di.Key;
import org.junit.jupiter.api.Test;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.junit.jupiter.api.Assertions.*;
@SuppressWarnings("unused")
public class DITest {
@Test
void markerQualifierTest() {
Injector injector = Injector.create().bindImplicit(QualifierTest.class);
QualifierTest.MyMojo mojo = injector.getInstance(QualifierTest.MyMojo.class);
assertNotNull(mojo);
assertInstanceOf(QualifierTest.MyQualifiedServiceImpl.class, mojo.service);
}
static class QualifierTest {
@Qualifier
@Retention(RUNTIME)
@interface MyQualifier {}
interface MyService {}
@Named
@Priority(10)
static class MyNamedServiceImpl implements MyService {}
@MyQualifier
static class MyQualifiedServiceImpl implements MyService {}
@Named
static class MyMojo {
@Inject
@MyQualifier
MyService service;
}
}
@Test
void priorityTest() {
Injector injector = Injector.create().bindImplicit(PriorityTest.class);
PriorityTest.MyMojo mojo = injector.getInstance(PriorityTest.MyMojo.class);
assertNotNull(mojo);
assertInstanceOf(PriorityTest.MyPriorityServiceImpl.class, mojo.service);
}
static class PriorityTest {
interface MyService {}
@Named
static class MyServiceImpl implements MyService {}
@Named
@Priority(10)
static class MyPriorityServiceImpl implements MyService {}
@Named
static class MyMojo {
@Inject
MyService service;
}
}
@Test
void mojoTest() {
Injector injector = Injector.create().bindImplicit(MojoTest.class);
MojoTest.MyMojo mojo = injector.getInstance(MojoTest.MyMojo.class);
assertNotNull(mojo);
}
@SuppressWarnings("unused")
static class MojoTest {
@Qualifier
@Retention(RUNTIME)
@interface Mojo {}
interface MyService {}
@Named
static class MyServiceImpl implements MyService {}
@Mojo
static class MyMojo {
@Inject
MyService service;
}
}
@Test
void typedTest() {
Injector injector =
Injector.create().bindImplicit(TypedTest.MyServiceImpl.class).bindImplicit(TypedTest.MyMojo.class);
TypedTest.MyMojo mojo = injector.getInstance(TypedTest.MyMojo.class);
assertNotNull(mojo);
assertNotNull(mojo.service);
}
@SuppressWarnings("unused")
static class TypedTest {
interface MyService {}
@Named
@Typed
static class MyServiceImpl implements MyService {}
@Named
static class MyMojo {
@Inject
MyService service;
}
}
@Test
public void bindInterfacesTest() {
Injector injector = Injector.create().bindImplicit(BindInterfaces.class);
BindInterfaces.TestInterface<String> inst =
injector.getInstance(new Key<BindInterfaces.TestInterface<String>>() {});
assertNotNull(inst);
}
static class BindInterfaces {
interface TestInterface<T> {
T getObj();
}
@Named
static class ClassImpl implements TestInterface<String> {
@Override
public String getObj() {
return null;
}
}
@Named
@Typed
static class TypedClassImpl implements TestInterface<String> {
@Override
public String getObj() {
return null;
}
}
}
@Test
void injectListTest() {
Injector injector = Injector.create().bindImplicit(InjectList.class);
List<InjectList.MyService> services = injector.getInstance(new Key<List<InjectList.MyService>>() {});
assertNotNull(services);
assertEquals(2, services.size());
assertNotNull(services.get(0));
assertInstanceOf(InjectList.MyService.class, services.get(0));
assertNotNull(services.get(1));
assertInstanceOf(InjectList.MyService.class, services.get(1));
assertNotSame(services.get(0).getClass(), services.get(1).getClass());
}
static class InjectList {
interface MyService {}
@Named("foo")
static class MyServiceImpl implements MyService {}
@Named("bar")
static class AnotherServiceImpl implements MyService {}
}
@Test
void injectMapTest() {
Injector injector = Injector.create().bindImplicit(InjectMap.class);
Map<String, InjectMap.MyService> services =
injector.getInstance(new Key<Map<String, InjectMap.MyService>>() {});
assertNotNull(services);
assertEquals(2, services.size());
List<Map.Entry<String, InjectMap.MyService>> entries = new ArrayList<>(services.entrySet());
assertNotNull(entries.get(0));
assertInstanceOf(InjectMap.MyService.class, entries.get(0).getValue());
assertInstanceOf(String.class, entries.get(0).getKey());
assertNotNull(entries.get(1));
assertInstanceOf(String.class, entries.get(1).getKey());
assertInstanceOf(InjectMap.MyService.class, entries.get(1).getValue());
assertNotEquals(entries.get(0).getKey(), entries.get(1).getKey());
assertNotSame(
entries.get(0).getValue().getClass(), entries.get(1).getValue().getClass());
InjectMap.MyMojo mojo = injector.getInstance(InjectMap.MyMojo.class);
assertNotNull(mojo);
assertNotNull(mojo.services);
assertEquals(2, mojo.services.size());
}
static class InjectMap {
interface MyService {}
@Named("foo")
static class MyServiceImpl implements MyService {}
@Named("bar")
static class AnotherServiceImpl implements MyService {}
@Named
static class MyMojo {
@Inject
Map<String, MyService> services;
}
}
@Test
void testSingleton() {
Injector injector = Injector.create()
.bindImplicit(SingletonContainer.Bean1.class)
.bindImplicit(SingletonContainer.Bean2.class);
SingletonContainer.Bean1 b1a = injector.getInstance(SingletonContainer.Bean1.class);
assertNotNull(b1a);
SingletonContainer.Bean1 b1b = injector.getInstance(SingletonContainer.Bean1.class);
assertNotNull(b1b);
assertEquals(b1a.num, b1b.num);
SingletonContainer.Bean2 b2a = injector.getInstance(SingletonContainer.Bean2.class);
assertNotNull(b2a);
SingletonContainer.Bean2 b2b = injector.getInstance(SingletonContainer.Bean2.class);
assertNotNull(b2b);
assertNotEquals(b2a.num, b2b.num);
}
static class SingletonContainer {
private static final AtomicInteger bean1 = new AtomicInteger();
private static final AtomicInteger bean2 = new AtomicInteger();
@Named
@Singleton
static class Bean1 {
int num = bean1.incrementAndGet();
}
@Named
static class Bean2 {
int num = bean2.incrementAndGet();
}
}
}

View File

@ -0,0 +1,83 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di.impl;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Type;
/**
* A type token for defining complex types (annotated, parameterized)
* <p>
* Usage example:
* <p>
* {@code Type listOfStringsType = new TypeT<List<String>>(){}.getType()}
*
* @param <T> actual type
*/
public abstract class TypeT<T> {
private final AnnotatedType annotatedType;
/**
* Creates a new type token. A type argument {@link T} <b>must</b> be specified.
* A typical usage is:
* <p>
* {@code TypeT<List<Integer>> integerListTypeT = new TypeT<List<Integer>>(){};}
*
* @throws AssertionError if a {@link TypeT} is created with a raw type
*/
protected TypeT() {
this.annotatedType = getSuperclassTypeParameter(this.getClass());
}
private static AnnotatedType getSuperclassTypeParameter(Class<?> subclass) {
AnnotatedType superclass = subclass.getAnnotatedSuperclass();
if (superclass instanceof AnnotatedParameterizedType) {
return ((AnnotatedParameterizedType) superclass).getAnnotatedActualTypeArguments()[0];
}
throw new AssertionError();
}
/**
* Returns an {@link AnnotatedType} of a {@link T}
*/
public final AnnotatedType getAnnotatedType() {
return annotatedType;
}
/**
* Returns a {@link Type} of a {@link T}
*/
public final Type getType() {
return annotatedType.getType();
}
/**
* Returns a raw type (e.g {@link Class}) of a {@link T}
*/
@SuppressWarnings("unchecked")
public final Class<T> getRawType() {
return (Class<T>) Types.getRawType(annotatedType.getType());
}
@Override
public final String toString() {
return annotatedType.toString();
}
}

View File

@ -0,0 +1,157 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.di.impl;
import java.lang.reflect.Type;
import java.util.*;
import org.junit.jupiter.api.Test;
import static org.apache.maven.di.impl.TypeUtils.simplifyType;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TypeUtilsTest {
@Test
public void testSimplifyType() {
{
Type type = Integer.class;
assertEquals(type, simplifyType(type));
}
{
Type type = new TypeT<Set<Integer>>() {}.getType();
assertEquals(type, simplifyType(type));
}
{
Type type = new TypeT<Set<Set<Set<Integer>>>>() {}.getType();
assertEquals(type, simplifyType(type));
}
{
Type type = new TypeT<Set<? extends Integer>>() {}.getType();
Type expected = new TypeT<Set<Integer>>() {}.getType();
assertEquals(expected, simplifyType(type));
}
{
Type type = new TypeT<Set<? extends Set<? extends Set<? extends Integer>>>>() {}.getType();
Type expected = new TypeT<Set<Set<Set<Integer>>>>() {}.getType();
assertEquals(expected, simplifyType(type));
}
{
Type type = new TypeT<Set<Set<? extends Set<Integer>>>>() {}.getType();
Type expected = new TypeT<Set<Set<Set<Integer>>>>() {}.getType();
assertEquals(expected, simplifyType(type));
}
{
Type type = new TypeT<Set<? super Integer>>() {}.getType();
Type expected = new TypeT<Set<Integer>>() {}.getType();
assertEquals(expected, simplifyType(type));
}
{
Type type = new TypeT<Set<? super Set<? super Set<? super Integer>>>>() {}.getType();
Type expected = new TypeT<Set<Set<Set<Integer>>>>() {}.getType();
assertEquals(expected, simplifyType(type));
}
{
Type type = new TypeT<Set<Set<? super Set<Integer>>>>() {}.getType();
Type expected = new TypeT<Set<Set<Set<Integer>>>>() {}.getType();
assertEquals(expected, simplifyType(type));
}
{
Type type = new TypeT<Set<? extends Set<? super Set<? extends Integer>>>>() {}.getType();
Type expected = new TypeT<Set<Set<Set<Integer>>>>() {}.getType();
assertEquals(expected, simplifyType(type));
}
{
Type type = new TypeT<Set<? extends Integer>[]>() {}.getType();
Type expected = new TypeT<Set<Integer>[]>() {}.getType();
assertEquals(expected, simplifyType(type));
}
{
Type type = new TypeT<Set<? super Integer>[]>() {}.getType();
Type expected = new TypeT<Set<Integer>[]>() {}.getType();
assertEquals(expected, simplifyType(type));
}
{
Type type = new TypeT<TestInterface<? extends Integer, ? extends Integer>>() {}.getType();
Type expected = new TypeT<TestInterface<Integer, Integer>>() {}.getType();
assertEquals(expected, simplifyType(type));
}
{
Type type = new TypeT<TestInterface<Integer, Integer>>() {}.getType();
Type expected = new TypeT<TestInterface<Integer, Integer>>() {}.getType();
assertEquals(expected, simplifyType(type));
}
{
Type type = new TypeT<TestClass<?, ?, ?, ?, ?, ?, ?, ?, ?>>() {}.getType();
Type expected = TestClass.class;
assertEquals(expected, simplifyType(type));
}
{
Type type = new TypeT<TestClass<?, ?, ?, Object, ?, ?, ?, ?, ?>>() {}.getType();
Type expected = TestClass.class;
assertEquals(expected, simplifyType(type));
}
{
//noinspection TypeParameterExplicitlyExtendsObject
Type type = new TypeT<
TestClass<
Integer,
? extends Integer,
? super Integer,
Object,
? extends Object,
? super Object,
?,
Set<? extends TestInterface<Integer, ? extends Integer>>,
Set<? super TestInterface<Integer, ? super Integer>>>>() {}.getType();
Type expected = new TypeT<
TestClass<
Integer,
Integer,
Integer,
Object,
Object,
Object,
Object,
Set<TestInterface<Integer, Integer>>,
Set<TestInterface<Integer, Integer>>>>() {}.getType();
assertEquals(expected, simplifyType(type));
}
}
public static final class TestClass<A, B, C, D, E, F, G, H, I> {}
interface TestInterface<A, B extends Integer> {}
}

11
pom.xml
View File

@ -105,6 +105,7 @@ under the License.
<module>maven-model</module>
<module>maven-model-builder</module>
<module>api</module>
<module>maven-di</module>
<module>maven-xml-impl</module>
<module>maven-core</module>
<module>maven-settings</module>
@ -257,6 +258,16 @@ under the License.
<artifactId>maven-api-xml</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-api-di</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-di</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model-builder</artifactId>