Skip to content

Commit

Permalink
Make parent level configurable for reference value in requireProperty…
Browse files Browse the repository at this point in the history
…Diverges (#295)

* Add support for different reference policies
* Update site doc
* Add tests
  • Loading branch information
piercemar committed Mar 19, 2024
1 parent 3eaab6e commit 4d399e8
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 6 deletions.
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

0 comments on commit 4d399e8

Please sign in to comment.