mirror of https://github.com/apache/maven.git
[MNG-7954] New dependency injection mechanism (#1393)
This commit is contained in:
parent
3f9fec2307
commit
a37cf3d37f
|
@ -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>
|
||||
|
||||
|
|
|
@ -22,8 +22,6 @@ import java.lang.annotation.Documented;
|
|||
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 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
* 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
|
||||
|
|
|
@ -22,8 +22,6 @@ import java.lang.annotation.Documented;
|
|||
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 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
* 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
|
||||
|
|
|
@ -35,7 +35,7 @@ import org.apache.maven.api.annotations.Nonnull;
|
|||
*/
|
||||
@Experimental
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
public @interface Execute {
|
||||
|
|
|
@ -38,7 +38,7 @@ import org.apache.maven.api.annotations.Nonnull;
|
|||
*/
|
||||
@Experimental
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
public @interface Mojo {
|
||||
|
|
|
@ -41,7 +41,7 @@ import org.apache.maven.api.annotations.Nonnull;
|
|||
*/
|
||||
@Experimental
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Inherited
|
||||
public @interface Parameter {
|
||||
|
|
|
@ -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>
|
|
@ -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 {}
|
|
@ -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 "";
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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 {}
|
|
@ -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 {}
|
|
@ -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 {}
|
|
@ -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 {}
|
|
@ -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 {};
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -52,16 +52,26 @@ public class EnhancedComponentConfigurator extends BasicComponentConfigurator {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ public class MojoExecutionScopeModule extends AbstractModule {
|
|||
@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())
|
||||
|
|
|
@ -22,31 +22,25 @@ import javax.inject.Inject;
|
|||
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 @@ public class DefaultMavenPluginManager implements MavenPluginManager {
|
|||
((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 class DefaultMavenPluginManager implements MavenPluginManager {
|
|||
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 @@ public class DefaultMavenPluginManager implements MavenPluginManager {
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ public class SessionScopeModule extends AbstractModule {
|
|||
@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)
|
||||
|
|
|
@ -23,8 +23,8 @@ import javax.inject.Named;
|
|||
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;
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
|
@ -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<String, List<Integer>>, you can just use
|
||||
* this syntax: <code>new Key<Map<String, List<Integer>>>(){}</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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.apache.maven.di.processor.IndexAnnotationProcessor
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
11
pom.xml
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue