PluginDependenciesResolverDefaultMethodsTest.java
/*
* 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.plugin.internal;
import java.lang.reflect.Method;
import java.util.List;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.PluginResolutionException;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResult;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
/**
* {@link PluginDependenciesResolver} is documented as internal, but tools that embed Maven ��� most visibly
* IntelliJ IDEA's {@code Maven40PluginDependenciesResolver} ��� implement it out of tree and are compiled against
* one Maven version while running against another. When {@code resolveCoreExtensionAndFlatten} and
* {@code resolvePluginAndFlatten} were forward-ported from Maven 3.10.0 as <em>abstract</em> methods, every such
* implementation started failing with {@code AbstractMethodError} as soon as core invoked them.
*
* <p>These tests pin the compatibility contract: an implementation providing only the methods that existed before
* the forward port must remain a legal implementation, and the two newer methods must stay {@code default}.
*/
class PluginDependenciesResolverDefaultMethodsTest {
private static final DependencyResult RESULT = new DependencyResult(new DependencyRequest());
/**
* Implements exactly the method set of the pre-forward-port interface ��� nothing more. This class failing to
* compile <em>is</em> the regression: it means the two newer methods went back to being abstract.
*/
private static final class LegacyResolver implements PluginDependenciesResolver {
private Plugin plugin;
private Artifact pluginArtifact;
private DependencyFilter dependencyFilter;
private List<RemoteRepository> repositories;
private RepositorySystemSession session;
@Override
public Artifact resolve(Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session) {
throw new UnsupportedOperationException();
}
@Override
public org.eclipse.aether.graph.DependencyNode resolve(
Plugin plugin,
Artifact pluginArtifact,
DependencyFilter dependencyFilter,
List<RemoteRepository> repositories,
RepositorySystemSession session) {
throw new UnsupportedOperationException();
}
@Override
public DependencyResult resolvePlugin(
Plugin plugin,
Artifact pluginArtifact,
DependencyFilter dependencyFilter,
List<RemoteRepository> repositories,
RepositorySystemSession session) {
this.plugin = plugin;
this.pluginArtifact = pluginArtifact;
this.dependencyFilter = dependencyFilter;
this.repositories = repositories;
this.session = session;
return RESULT;
}
}
@Test
void resolvePluginAndFlattenDelegatesToResolvePlugin() throws PluginResolutionException {
LegacyResolver resolver = new LegacyResolver();
Plugin plugin = new Plugin();
Artifact artifact = new DefaultArtifact("g:a:1.0");
DependencyFilter filter = (node, parents) -> true;
List<RemoteRepository> repositories = List.of();
RepositorySystemSession session = mock(RepositorySystemSession.class);
assertSame(RESULT, resolver.resolvePluginAndFlatten(plugin, artifact, filter, repositories, session));
assertSame(plugin, resolver.plugin);
assertSame(artifact, resolver.pluginArtifact);
assertSame(filter, resolver.dependencyFilter);
assertSame(repositories, resolver.repositories);
assertSame(session, resolver.session);
}
@Test
void resolveCoreExtensionAndFlattenDelegatesToResolvePlugin() throws PluginResolutionException {
LegacyResolver resolver = new LegacyResolver();
Plugin plugin = new Plugin();
DependencyFilter filter = (node, parents) -> true;
List<RemoteRepository> repositories = List.of();
RepositorySystemSession session = mock(RepositorySystemSession.class);
assertSame(RESULT, resolver.resolveCoreExtensionAndFlatten(plugin, filter, repositories, session));
assertSame(plugin, resolver.plugin);
assertNull(resolver.pluginArtifact, "the extension's main artifact is resolved from the plugin GAV");
assertSame(filter, resolver.dependencyFilter);
assertSame(repositories, resolver.repositories);
assertSame(session, resolver.session);
}
/**
* Guards the property that actually broke IntelliJ IDEA: these methods are invoked on implementations compiled
* against an older Maven, so they must carry an implementation in the interface itself. Turning either back into
* an abstract method reintroduces {@code AbstractMethodError} for every out-of-tree implementation.
*/
@Test
void newerMethodsAreDefaultMethods() throws NoSuchMethodException {
Method resolveCoreExtensionAndFlatten = PluginDependenciesResolver.class.getMethod(
"resolveCoreExtensionAndFlatten",
Plugin.class,
DependencyFilter.class,
List.class,
RepositorySystemSession.class);
Method resolvePluginAndFlatten = PluginDependenciesResolver.class.getMethod(
"resolvePluginAndFlatten",
Plugin.class,
Artifact.class,
DependencyFilter.class,
List.class,
RepositorySystemSession.class);
assertTrue(
resolveCoreExtensionAndFlatten.isDefault(),
"resolveCoreExtensionAndFlatten must stay a default method");
assertTrue(resolvePluginAndFlatten.isDefault(), "resolvePluginAndFlatten must stay a default method");
}
}