Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make parent level configurable for reference value in requirePropertyDiverges #295

Merged
merged 3 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public class RequirePropertyDiverges extends AbstractEnforcerRule {
*/
private String regex = null;

private String reference = "DEFINING";

private static final String RULE_NAME =
StringUtils.lowercaseFirstLetter(RequirePropertyDiverges.class.getSimpleName());

Expand All @@ -89,13 +91,15 @@ public void execute() throws EnforcerRuleException {
Object propValue = getPropertyValue();
checkPropValueNotBlank(propValue);

ParentReference parentReference = getParentReference();

getLog().debug(() -> getRuleName() + ": checking property '" + property + "' for project " + project);

final MavenProject parent = findDefiningParent(project);
final MavenProject parent = findParent(project, parentReference);

// fail fast if the defining parent could not be found due to a bug in the rule
// fail fast if the reference parent could not be found due to a bug in the rule
if (parent == null) {
throw new IllegalStateException("Failed to find parent POM which defines the current rule");
throw new IllegalStateException("Failed to find reference POM which defines the current value");
}

if (project.equals(parent)) {
Expand All @@ -112,7 +116,7 @@ public void execute() throws EnforcerRuleException {
}

/**
* Checks the value of the project against the one given in the defining ancestor project.
* Checks the value of the project against the one given in the reference ancestor project.
*
* @param project
* @param parent
Expand Down Expand Up @@ -153,6 +157,26 @@ void checkAgainstRegex(Object propValue) throws EnforcerRuleException {
}
}

/**
* Finds the ancestor project which defines the reference value.
*
* @param project to inspect
* @param reference project to diverge from
* @return the defining ancestor project.
*/
final MavenProject findParent(final MavenProject project, ParentReference reference) {
switch (reference) {
case BASE:
return findBaseParent(project);
case DEFINING:
return findDefiningParent(project);
case PARENT:
return findDirectParent(project);
default:
throw new IllegalArgumentException("Unhandled ParentReference: " + reference.name());
}
}

/**
* Finds the ancestor project which defines the rule.
*
Expand All @@ -176,6 +200,32 @@ final MavenProject findDefiningParent(final MavenProject project) {
return parent;
}

/**
* Finds the direct ancestor project.
*
* @param project to inspect
* @return the top-most local project or current project if it has no parent.
*
*/
final MavenProject findDirectParent(MavenProject project) {
MavenProject parent = project.getParent();
return parent == null ? project : parent;
}

/**
* Finds the top-most local ancestor project.
*
* @param project to inspect
* @return the top-most local project.
*/
final MavenProject findBaseParent(final MavenProject project) {
MavenProject current = project;
while (current.getParentFile() != null) {
current = project.getParent();
}
return current;
}

/**
* Creates a {@link Xpp3Dom} which corresponds to the configuration of the invocation.
*
Expand Down Expand Up @@ -337,6 +387,31 @@ void checkPropValueNotBlank(Object propValue) throws EnforcerRuleException {
}
}

/**
* Extracted for easier testability.
*
* @return the ParentReference constant.
* @throws EnforcerRuleException if null or no ParentReference matches (case-insensitively)
*/
ParentReference getParentReference() throws EnforcerRuleException {
return getParentReference(reference);
}

/**
* Extracted for easier testability.
*
* @param parentReferenceName name of the ParentReference.
* @return the ParentReference enum constant.
* @throws EnforcerRuleException if null or no ParentReference matches (case-insensitively)
*/
ParentReference getParentReference(String parentReferenceName) throws EnforcerRuleException {
try {
return ParentReference.valueOf(StringUtils.upperCase(StringUtils.strip(parentReferenceName)));
} catch (IllegalArgumentException iae) {
throw new EnforcerRuleError("Unknown parent reference value: " + parentReferenceName, iae);
}
}

/**
* Either return the submitted errorMessage or replace it with the custom message set in the rule extended
* by the property name.
Expand Down Expand Up @@ -375,6 +450,13 @@ void setMessage(String message) {
this.message = message;
}

/**
* @param reference the reference to set
*/
void setReference(String reference) {
this.reference = reference;
}

/**
* Creates the DOM of the invoking rule, but returns the children alphabetically sorted.
*/
Expand Down Expand Up @@ -416,4 +498,19 @@ private void addChildrenToRuleDom() {
}
}
}

enum ParentReference {
/**
* The top-most local project (i.e.: the highest parent whose POM does not come from a repository)
*/
BASE,
/**
* The parent where the current requirePropertyDiverges rule is defined
*/
DEFINING,
/**
* The immediate parent (same as ${project.parent})
*/
PARENT
}
}
10 changes: 9 additions & 1 deletion src/site/apt/requirePropertyDiverges.apt.vm
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,22 @@ Require Property Diverges

* <<property>> - name of the property which should diverge. Must be given.

* <<reference>> - parent level to use as evaluation context for the reference value:

* DEFINING - the rule-defining POM. Default historical behaviour.

* PARENT - the direct parent of the current POM. The rule is skipped if the POM has no parent.

* BASE - the top-most local POM of the project. The rule is skipped if the current POM is the top-most POM.

* <<regex>> - match the property value to a given regular expression.
When not given, this rule checks that the property in the child does not equal
the one from the defining ancestor.

[]

Note that certain properties (e.g. <<<project.url>>> or <<<project.scm.connection>>>
are automatically extended with the child's <<<project.artifactId>>>, so using
are automatically extended with the child's <<<project.artifactId>>> (see {{{https://maven.apache.org/ref/current/maven-model-builder/#inheritance-assembly}Inheritance assembly}}), so using
a regex will help in this case, see sample below.

Another caveat: properties defined in the <<<properties>>> section will be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
* under the License.
*/

import java.io.File;

import org.apache.maven.enforcer.rule.api.EnforcerLogger;
import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
import org.apache.maven.model.Build;
Expand Down Expand Up @@ -222,6 +224,87 @@ public void testCheckAgainstParentValue() throws EnforcerRuleException, Expressi
testCheckAgainstParentValue("company.parent-pom", "company.project1");
}

@Test(expected = NullPointerException.class)
public void testGetParentReferenceNull() throws EnforcerRuleException {
RequirePropertyDiverges instance = createMockRule(mock(MavenProject.class));
instance.getParentReference(null);
}

@Test(expected = EnforcerRuleException.class)
public void testGetParentReferenceEmpty() throws EnforcerRuleException {
RequirePropertyDiverges instance = createMockRule(mock(MavenProject.class));
instance.getParentReference("");
}

@Test
public void testGetParentReferenceKnownValues() throws EnforcerRuleException {
RequirePropertyDiverges instance = createMockRule(mock(MavenProject.class));
assertEquals(RequirePropertyDiverges.ParentReference.BASE, instance.getParentReference("BaSE"));
assertEquals(RequirePropertyDiverges.ParentReference.DEFINING, instance.getParentReference("Defining"));
assertEquals(RequirePropertyDiverges.ParentReference.PARENT, instance.getParentReference("PaRenT"));
}

@Test(expected = EnforcerRuleException.class)
public void testGetParentReferenceUnknownValue() throws EnforcerRuleException {
RequirePropertyDiverges instance = createMockRule(mock(MavenProject.class));
instance.getParentReference("BOGUS");
}

@Test
public void testGetParentReferenceDefault() throws EnforcerRuleException {
RequirePropertyDiverges instance = createMockRule(mock(MavenProject.class));
assertEquals(RequirePropertyDiverges.ParentReference.DEFINING, instance.getParentReference());
}

@Test
public void testFindParentDefining() throws EnforcerRuleException {
RequirePropertyDiverges instance = createMockRule(mock(MavenProject.class));
MavenProject root = createParentProject();
MavenProject base = createMavenProject("company", "main-pom");
MavenProject child = createMavenProject("company", "child");
child.setParent(base);
base.setParent(root);
// Rule is defined in "root", expect root always
assertEquals(root, instance.findParent(root, RequirePropertyDiverges.ParentReference.DEFINING));
assertEquals(root, instance.findParent(base, RequirePropertyDiverges.ParentReference.DEFINING));
assertEquals(root, instance.findParent(child, RequirePropertyDiverges.ParentReference.DEFINING));
}

@Test
public void testFindParentParent() throws EnforcerRuleException {
RequirePropertyDiverges instance = createMockRule(mock(MavenProject.class));
MavenProject root = createParentProject();
MavenProject base = createMavenProject("company", "main-pom");
MavenProject child = createMavenProject("company", "child");
child.setParent(base);
base.setParent(root);
// "root" has no parent, expect root (self)
assertEquals(root, instance.findParent(root, RequirePropertyDiverges.ParentReference.PARENT));
// base's parent is root, expect root
assertEquals(root, instance.findParent(base, RequirePropertyDiverges.ParentReference.PARENT));
// child's parent is base, expect base
assertEquals(base, instance.findParent(child, RequirePropertyDiverges.ParentReference.PARENT));
}

@Test
public void testFindParentBase() throws EnforcerRuleException {
RequirePropertyDiverges instance = createMockRule(mock(MavenProject.class));
MavenProject root = createParentProject();
MavenProject base = createMavenProject("company", "main-pom");
base.setParentFile(null);
MavenProject child = createMavenProject("company", "child");
child.setParent(base);
child.setParentFile(mock(File.class));
base.setParent(root);

// "root" is not local, expect root (self)
assertEquals(root, instance.findParent(root, RequirePropertyDiverges.ParentReference.BASE));
// base is the top-most local (has no parentFile), expect base (self)
assertEquals(base, instance.findParent(base, RequirePropertyDiverges.ParentReference.BASE));
// child has a local parent, expect base
assertEquals(base, instance.findParent(child, RequirePropertyDiverges.ParentReference.BASE));
}

void testCheckAgainstParentValue(final String parentGroupId, final String childGroupId)
throws ExpressionEvaluationException, EnforcerRuleException {
MavenProject project = createMavenProject(childGroupId, "child");
Expand Down Expand Up @@ -251,7 +334,7 @@ static MavenProject createMavenProject(final String groupId, final String artifa
}

void setUpHelper(final MavenProject project, final String propertyValue) throws RuntimeException {
// when(helper.evaluate("${project}")).thenReturn(project);
// when(helper.evaluate("${project}")).thenReturn(project);
try {
when(evaluator.evaluate("${checkedProperty}")).thenReturn(propertyValue);
} catch (ExpressionEvaluationException e) {
Expand Down