From 4c7ea743619cd93e634f352b158d1d778f1eb1df Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Wed, 23 Sep 2026 09:13:27 +0300 Subject: [PATCH 1/3] Replace chained instanceof filter dispatch with the existing FilterVisitor Closes java/chained-type-tests for ObjectNormalizerFacade.normalizeFilter (12 tests) and AbstractFilterTranslator.createLeafExpression (9 tests): both already had a real Filter type hierarchy and a FilterVisitor interface available, so the instanceof chains become proper visitor dispatch instead of a mechanical alternative. Behavior-preserving, including a null-safety edge case the first pass of this refactor briefly broke (caught by the existing test suite, fixed, and locked in with a new regression test). The other two java/chained-type-tests alerts (EqualsHashCodeBuilder, SQLUtil) are dismissed on GitHub as won't-fix: they dispatch on final JDK types (primitive array component types / Integer, Double, Blob, Timestamp, ...) that a visitor pattern cannot be added to. --- .../operations/ObjectNormalizerFacade.java | 111 ++++++++++++------ .../ObjectNormalizerFacadeTests.java | 12 ++ .../filter/AbstractFilterTranslator.java | 86 +++++++++++--- 3 files changed, 151 insertions(+), 58 deletions(-) diff --git a/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacade.java b/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacade.java index e5765f3ed..da474b298 100644 --- a/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacade.java +++ b/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacade.java @@ -20,6 +20,7 @@ * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * Portions Copyrighted 2010-2013 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems, LLC */ package org.identityconnectors.framework.impl.api.local.operations; @@ -34,12 +35,12 @@ import org.identityconnectors.framework.common.objects.SyncDelta; import org.identityconnectors.framework.common.objects.SyncDeltaBuilder; import org.identityconnectors.framework.common.objects.filter.AndFilter; -import org.identityconnectors.framework.common.objects.filter.AttributeFilter; import org.identityconnectors.framework.common.objects.filter.ContainsAllValuesFilter; import org.identityconnectors.framework.common.objects.filter.ContainsFilter; import org.identityconnectors.framework.common.objects.filter.EndsWithFilter; import org.identityconnectors.framework.common.objects.filter.EqualsFilter; import org.identityconnectors.framework.common.objects.filter.Filter; +import org.identityconnectors.framework.common.objects.filter.FilterVisitor; import org.identityconnectors.framework.common.objects.filter.GreaterThanFilter; import org.identityconnectors.framework.common.objects.filter.GreaterThanOrEqualFilter; import org.identityconnectors.framework.common.objects.filter.LessThanFilter; @@ -146,47 +147,79 @@ public SyncDelta normalizeSyncDelta(SyncDelta delta) { * @return The normalized filter. */ public Filter normalizeFilter(Filter filter) { - if (filter instanceof ContainsFilter) { - AttributeFilter afilter = (AttributeFilter) filter; - return new ContainsFilter(normalizeAttribute(afilter.getAttribute())); - } else if (filter instanceof EndsWithFilter) { - AttributeFilter afilter = (AttributeFilter) filter; - return new EndsWithFilter(normalizeAttribute(afilter.getAttribute())); - } else if (filter instanceof EqualsFilter) { - AttributeFilter afilter = (AttributeFilter) filter; - return new EqualsFilter(normalizeAttribute(afilter.getAttribute())); - } else if (filter instanceof GreaterThanFilter) { - AttributeFilter afilter = (AttributeFilter) filter; - return new GreaterThanFilter(normalizeAttribute(afilter.getAttribute())); - } else if (filter instanceof GreaterThanOrEqualFilter) { - AttributeFilter afilter = (AttributeFilter) filter; - return new GreaterThanOrEqualFilter(normalizeAttribute(afilter.getAttribute())); - } else if (filter instanceof LessThanFilter) { - AttributeFilter afilter = (AttributeFilter) filter; - return new LessThanFilter(normalizeAttribute(afilter.getAttribute())); - } else if (filter instanceof LessThanOrEqualFilter) { - AttributeFilter afilter = (AttributeFilter) filter; - return new LessThanOrEqualFilter(normalizeAttribute(afilter.getAttribute())); - } else if (filter instanceof StartsWithFilter) { - AttributeFilter afilter = (AttributeFilter) filter; - return new StartsWithFilter(normalizeAttribute(afilter.getAttribute())); - } else if (filter instanceof ContainsAllValuesFilter) { - AttributeFilter afilter = (AttributeFilter) filter; - return new ContainsAllValuesFilter(normalizeAttribute(afilter.getAttribute())); - } else if (filter instanceof NotFilter) { - NotFilter notFilter = (NotFilter) filter; - return new NotFilter(normalizeFilter(notFilter.getFilter())); - } else if (filter instanceof AndFilter) { - AndFilter andFilter = (AndFilter) filter; - return new AndFilter(normalizeFilter(andFilter.getLeft()), normalizeFilter(andFilter - .getRight())); - } else if (filter instanceof OrFilter) { - OrFilter orFilter = (OrFilter) filter; - return new OrFilter(normalizeFilter(orFilter.getLeft()), normalizeFilter(orFilter + if (filter == null) { + return null; + } + return filter.accept(NormalizingFilterVisitor.INSTANCE, this); + } + + /** + * Applies {@link #normalizeAttribute(Attribute)} to every attribute + * referenced by a filter, recursing through composite (AND/OR/NOT) + * filters. Filter kinds with no attribute of their own (e.g. presence, + * extended) are returned unchanged. + */ + private static final class NormalizingFilterVisitor implements + FilterVisitor { + + static final NormalizingFilterVisitor INSTANCE = new NormalizingFilterVisitor(); + + public Filter visitAndFilter(ObjectNormalizerFacade p, AndFilter filter) { + return new AndFilter(p.normalizeFilter(filter.getLeft()), p.normalizeFilter(filter .getRight())); - } else { + } + + public Filter visitContainsFilter(ObjectNormalizerFacade p, ContainsFilter filter) { + return new ContainsFilter(p.normalizeAttribute(filter.getAttribute())); + } + + public Filter visitContainsAllValuesFilter(ObjectNormalizerFacade p, + ContainsAllValuesFilter filter) { + return new ContainsAllValuesFilter(p.normalizeAttribute(filter.getAttribute())); + } + + public Filter visitEqualsFilter(ObjectNormalizerFacade p, EqualsFilter filter) { + return new EqualsFilter(p.normalizeAttribute(filter.getAttribute())); + } + + public Filter visitExtendedFilter(ObjectNormalizerFacade p, Filter filter) { return filter; } + + public Filter visitGreaterThanFilter(ObjectNormalizerFacade p, GreaterThanFilter filter) { + return new GreaterThanFilter(p.normalizeAttribute(filter.getAttribute())); + } + + public Filter visitGreaterThanOrEqualFilter(ObjectNormalizerFacade p, + GreaterThanOrEqualFilter filter) { + return new GreaterThanOrEqualFilter(p.normalizeAttribute(filter.getAttribute())); + } + + public Filter visitLessThanFilter(ObjectNormalizerFacade p, LessThanFilter filter) { + return new LessThanFilter(p.normalizeAttribute(filter.getAttribute())); + } + + public Filter visitLessThanOrEqualFilter(ObjectNormalizerFacade p, + LessThanOrEqualFilter filter) { + return new LessThanOrEqualFilter(p.normalizeAttribute(filter.getAttribute())); + } + + public Filter visitNotFilter(ObjectNormalizerFacade p, NotFilter filter) { + return new NotFilter(p.normalizeFilter(filter.getFilter())); + } + + public Filter visitOrFilter(ObjectNormalizerFacade p, OrFilter filter) { + return new OrFilter(p.normalizeFilter(filter.getLeft()), p.normalizeFilter(filter + .getRight())); + } + + public Filter visitStartsWithFilter(ObjectNormalizerFacade p, StartsWithFilter filter) { + return new StartsWithFilter(p.normalizeAttribute(filter.getAttribute())); + } + + public Filter visitEndsWithFilter(ObjectNormalizerFacade p, EndsWithFilter filter) { + return new EndsWithFilter(p.normalizeAttribute(filter.getAttribute())); + } } } diff --git a/OpenICF-java-framework/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacadeTests.java b/OpenICF-java-framework/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacadeTests.java index 868466129..4f73504f9 100644 --- a/OpenICF-java-framework/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacadeTests.java +++ b/OpenICF-java-framework/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacadeTests.java @@ -19,6 +19,7 @@ * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== + * "Portions Copyrighted 2026 3A Systems, LLC" */ package org.identityconnectors.framework.impl.api.local.operations; @@ -162,6 +163,17 @@ public void testNot() { assertNormalizedFilter(expected, filter); } + @Test + public void testPresenceFilterPassedThroughUnchanged() { + Filter filter = FilterBuilder.present("foo"); + assertNormalizedFilter(filter, filter); + } + + @Test + public void testNullFilterReturnsNull() { + assertEquals(createTestNormalizer().normalizeFilter(null), null); + } + @Test public void testContainsAllValues() { Filter expected = FilterBuilder.containsAllValues(createNormalizedTestAttribute()); diff --git a/OpenICF-java-framework/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/filter/AbstractFilterTranslator.java b/OpenICF-java-framework/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/filter/AbstractFilterTranslator.java index 1ce21d98d..193468d9c 100644 --- a/OpenICF-java-framework/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/filter/AbstractFilterTranslator.java +++ b/OpenICF-java-framework/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/filter/AbstractFilterTranslator.java @@ -409,28 +409,76 @@ private T createLeafExpression(Filter filter) { * @return The expression or null (for everything) */ private T createLeafExpression(Filter filter, boolean not) { - if (filter instanceof ContainsFilter) { - return createContainsExpression((ContainsFilter) filter, not); - } else if (filter instanceof EndsWithFilter) { - return createEndsWithExpression((EndsWithFilter) filter, not); - } else if (filter instanceof EqualsFilter) { - return createEqualsExpression((EqualsFilter) filter, not); - } else if (filter instanceof GreaterThanFilter) { - return createGreaterThanExpression((GreaterThanFilter) filter, not); - } else if (filter instanceof GreaterThanOrEqualFilter) { - return createGreaterThanOrEqualExpression((GreaterThanOrEqualFilter) filter, not); - } else if (filter instanceof LessThanFilter) { - return createLessThanExpression((LessThanFilter) filter, not); - } else if (filter instanceof LessThanOrEqualFilter) { - return createLessThanOrEqualExpression((LessThanOrEqualFilter) filter, not); - } else if (filter instanceof StartsWithFilter) { - return createStartsWithExpression((StartsWithFilter) filter, not); - } else if (filter instanceof ContainsAllValuesFilter) { - return createContainsAllValuesExpression((ContainsAllValuesFilter) filter, not); - } else { + if (filter == null) { + return null; + } + return filter.accept(new LeafExpressionVisitor(), not); + } + + /** + * Dispatches a leaf filter to its corresponding createXxxExpression + * override. AND/OR/NOT filters never reach here (see + * {@link #createLeafExpression(Filter)}, which already unwraps a + * top-level NOT); those and any other unrecognized filter kind return + * {@code null}, matching the previous instanceof-chain's fallback. + */ + private final class LeafExpressionVisitor implements FilterVisitor { + + public T visitAndFilter(Boolean not, AndFilter filter) { + // unrecognized expression - nothing we can do + return null; + } + + public T visitContainsFilter(Boolean not, ContainsFilter filter) { + return createContainsExpression(filter, not); + } + + public T visitContainsAllValuesFilter(Boolean not, ContainsAllValuesFilter filter) { + return createContainsAllValuesExpression(filter, not); + } + + public T visitEqualsFilter(Boolean not, EqualsFilter filter) { + return createEqualsExpression(filter, not); + } + + public T visitExtendedFilter(Boolean not, Filter filter) { // unrecognized expression - nothing we can do return null; } + + public T visitGreaterThanFilter(Boolean not, GreaterThanFilter filter) { + return createGreaterThanExpression(filter, not); + } + + public T visitGreaterThanOrEqualFilter(Boolean not, GreaterThanOrEqualFilter filter) { + return createGreaterThanOrEqualExpression(filter, not); + } + + public T visitLessThanFilter(Boolean not, LessThanFilter filter) { + return createLessThanExpression(filter, not); + } + + public T visitLessThanOrEqualFilter(Boolean not, LessThanOrEqualFilter filter) { + return createLessThanOrEqualExpression(filter, not); + } + + public T visitNotFilter(Boolean not, NotFilter filter) { + // unrecognized expression - nothing we can do + return null; + } + + public T visitOrFilter(Boolean not, OrFilter filter) { + // unrecognized expression - nothing we can do + return null; + } + + public T visitStartsWithFilter(Boolean not, StartsWithFilter filter) { + return createStartsWithExpression(filter, not); + } + + public T visitEndsWithFilter(Boolean not, EndsWithFilter filter) { + return createEndsWithExpression(filter, not); + } } /** From 5eee2b9562522aaf0ff40ced87a91e1e2eef3106 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Fri, 2 Oct 2026 20:59:13 +0300 Subject: [PATCH 2/3] Pin the null guards in filter dispatch and add @Override to visitor methods - FilterTranslatorTests.testNullLeafTranslatesToEverything: a null leaf under AND/NOT translates to "everything" instead of throwing an NPE. - ObjectNormalizerFacadeTests.testNullSubFilterIsPassedThrough: null children of AND/OR/NOT (incl. a one-element AndFilter) pass through. - Correct the NormalizingFilterVisitor Javadoc: ExtendedMatchFilter has an attribute, it is just not normalized. - @Override on all FilterVisitor implementations (CodeQL java/missing-override-annotation). - Fix the 3A Systems copyright lines in the touched test files. --- .../operations/ObjectNormalizerFacade.java | 18 +++++++++++++-- .../ObjectNormalizerFacadeTests.java | 22 ++++++++++++++++++- .../filter/AbstractFilterTranslator.java | 13 +++++++++++ .../objects/filter/FilterTranslatorTests.java | 9 ++++++++ 4 files changed, 59 insertions(+), 3 deletions(-) diff --git a/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacade.java b/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacade.java index da474b298..e72f9d41c 100644 --- a/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacade.java +++ b/OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacade.java @@ -156,67 +156,81 @@ public Filter normalizeFilter(Filter filter) { /** * Applies {@link #normalizeAttribute(Attribute)} to every attribute * referenced by a filter, recursing through composite (AND/OR/NOT) - * filters. Filter kinds with no attribute of their own (e.g. presence, - * extended) are returned unchanged. + * filters. Filters dispatched to visitExtendedFilter (presence, + * extended-match, pass-through and custom filters) are returned + * unchanged; an ExtendedMatchFilter's attribute is not normalized. */ private static final class NormalizingFilterVisitor implements FilterVisitor { static final NormalizingFilterVisitor INSTANCE = new NormalizingFilterVisitor(); + @Override public Filter visitAndFilter(ObjectNormalizerFacade p, AndFilter filter) { return new AndFilter(p.normalizeFilter(filter.getLeft()), p.normalizeFilter(filter .getRight())); } + @Override public Filter visitContainsFilter(ObjectNormalizerFacade p, ContainsFilter filter) { return new ContainsFilter(p.normalizeAttribute(filter.getAttribute())); } + @Override public Filter visitContainsAllValuesFilter(ObjectNormalizerFacade p, ContainsAllValuesFilter filter) { return new ContainsAllValuesFilter(p.normalizeAttribute(filter.getAttribute())); } + @Override public Filter visitEqualsFilter(ObjectNormalizerFacade p, EqualsFilter filter) { return new EqualsFilter(p.normalizeAttribute(filter.getAttribute())); } + @Override public Filter visitExtendedFilter(ObjectNormalizerFacade p, Filter filter) { return filter; } + @Override public Filter visitGreaterThanFilter(ObjectNormalizerFacade p, GreaterThanFilter filter) { return new GreaterThanFilter(p.normalizeAttribute(filter.getAttribute())); } + @Override public Filter visitGreaterThanOrEqualFilter(ObjectNormalizerFacade p, GreaterThanOrEqualFilter filter) { return new GreaterThanOrEqualFilter(p.normalizeAttribute(filter.getAttribute())); } + @Override public Filter visitLessThanFilter(ObjectNormalizerFacade p, LessThanFilter filter) { return new LessThanFilter(p.normalizeAttribute(filter.getAttribute())); } + @Override public Filter visitLessThanOrEqualFilter(ObjectNormalizerFacade p, LessThanOrEqualFilter filter) { return new LessThanOrEqualFilter(p.normalizeAttribute(filter.getAttribute())); } + @Override public Filter visitNotFilter(ObjectNormalizerFacade p, NotFilter filter) { return new NotFilter(p.normalizeFilter(filter.getFilter())); } + @Override public Filter visitOrFilter(ObjectNormalizerFacade p, OrFilter filter) { return new OrFilter(p.normalizeFilter(filter.getLeft()), p.normalizeFilter(filter .getRight())); } + @Override public Filter visitStartsWithFilter(ObjectNormalizerFacade p, StartsWithFilter filter) { return new StartsWithFilter(p.normalizeAttribute(filter.getAttribute())); } + @Override public Filter visitEndsWithFilter(ObjectNormalizerFacade p, EndsWithFilter filter) { return new EndsWithFilter(p.normalizeAttribute(filter.getAttribute())); } diff --git a/OpenICF-java-framework/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacadeTests.java b/OpenICF-java-framework/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacadeTests.java index 4f73504f9..04e809468 100644 --- a/OpenICF-java-framework/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacadeTests.java +++ b/OpenICF-java-framework/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacadeTests.java @@ -19,12 +19,15 @@ * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== - * "Portions Copyrighted 2026 3A Systems, LLC" + * Portions Copyrighted 2026 3A Systems, LLC */ package org.identityconnectors.framework.impl.api.local.operations; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNull; + +import java.util.Collections; import org.identityconnectors.framework.common.objects.Attribute; import org.identityconnectors.framework.common.objects.AttributeBuilder; @@ -36,8 +39,11 @@ import org.identityconnectors.framework.common.objects.SyncDeltaBuilder; import org.identityconnectors.framework.common.objects.SyncDeltaType; import org.identityconnectors.framework.common.objects.SyncToken; +import org.identityconnectors.framework.common.objects.filter.AndFilter; import org.identityconnectors.framework.common.objects.filter.Filter; import org.identityconnectors.framework.common.objects.filter.FilterBuilder; +import org.identityconnectors.framework.common.objects.filter.NotFilter; +import org.identityconnectors.framework.common.objects.filter.OrFilter; import org.identityconnectors.framework.common.serializer.SerializerUtil; import org.identityconnectors.framework.spi.AttributeNormalizer; import org.testng.annotations.Test; @@ -174,6 +180,20 @@ public void testNullFilterReturnsNull() { assertEquals(createTestNormalizer().normalizeFilter(null), null); } + @Test + public void testNullSubFilterIsPassedThrough() { + ObjectNormalizerFacade normalizer = createTestNormalizer(); + Filter contains = FilterBuilder.contains(createTestAttribute()); + assertNull(((NotFilter) normalizer.normalizeFilter(new NotFilter(null))).getFilter()); + assertNull(((AndFilter) normalizer.normalizeFilter(new AndFilter(contains, null))) + .getRight()); + assertNull(((OrFilter) normalizer.normalizeFilter(new OrFilter(contains, null))) + .getRight()); + // a one-element AndFilter has a null right-hand side + assertNull(((AndFilter) normalizer.normalizeFilter(new AndFilter(Collections + .singletonList(contains)))).getRight()); + } + @Test public void testContainsAllValues() { Filter expected = FilterBuilder.containsAllValues(createNormalizedTestAttribute()); diff --git a/OpenICF-java-framework/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/filter/AbstractFilterTranslator.java b/OpenICF-java-framework/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/filter/AbstractFilterTranslator.java index 193468d9c..b6ac659d3 100644 --- a/OpenICF-java-framework/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/filter/AbstractFilterTranslator.java +++ b/OpenICF-java-framework/connector-framework/src/main/java/org/identityconnectors/framework/common/objects/filter/AbstractFilterTranslator.java @@ -424,58 +424,71 @@ private T createLeafExpression(Filter filter, boolean not) { */ private final class LeafExpressionVisitor implements FilterVisitor { + @Override public T visitAndFilter(Boolean not, AndFilter filter) { // unrecognized expression - nothing we can do return null; } + @Override public T visitContainsFilter(Boolean not, ContainsFilter filter) { return createContainsExpression(filter, not); } + @Override public T visitContainsAllValuesFilter(Boolean not, ContainsAllValuesFilter filter) { return createContainsAllValuesExpression(filter, not); } + @Override public T visitEqualsFilter(Boolean not, EqualsFilter filter) { return createEqualsExpression(filter, not); } + @Override public T visitExtendedFilter(Boolean not, Filter filter) { // unrecognized expression - nothing we can do return null; } + @Override public T visitGreaterThanFilter(Boolean not, GreaterThanFilter filter) { return createGreaterThanExpression(filter, not); } + @Override public T visitGreaterThanOrEqualFilter(Boolean not, GreaterThanOrEqualFilter filter) { return createGreaterThanOrEqualExpression(filter, not); } + @Override public T visitLessThanFilter(Boolean not, LessThanFilter filter) { return createLessThanExpression(filter, not); } + @Override public T visitLessThanOrEqualFilter(Boolean not, LessThanOrEqualFilter filter) { return createLessThanOrEqualExpression(filter, not); } + @Override public T visitNotFilter(Boolean not, NotFilter filter) { // unrecognized expression - nothing we can do return null; } + @Override public T visitOrFilter(Boolean not, OrFilter filter) { // unrecognized expression - nothing we can do return null; } + @Override public T visitStartsWithFilter(Boolean not, StartsWithFilter filter) { return createStartsWithExpression(filter, not); } + @Override public T visitEndsWithFilter(Boolean not, EndsWithFilter filter) { return createEndsWithExpression(filter, not); } diff --git a/OpenICF-java-framework/connector-framework/src/test/java/org/identityconnectors/framework/common/objects/filter/FilterTranslatorTests.java b/OpenICF-java-framework/connector-framework/src/test/java/org/identityconnectors/framework/common/objects/filter/FilterTranslatorTests.java index e5129d1f4..57e0394fd 100644 --- a/OpenICF-java-framework/connector-framework/src/test/java/org/identityconnectors/framework/common/objects/filter/FilterTranslatorTests.java +++ b/OpenICF-java-framework/connector-framework/src/test/java/org/identityconnectors/framework/common/objects/filter/FilterTranslatorTests.java @@ -19,6 +19,7 @@ * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== + * Portions Copyrighted 2026 3A Systems, LLC */ package org.identityconnectors.framework.common.objects.filter; @@ -492,6 +493,14 @@ public void testSimplifyNoAndNoOrNoLeaf() { "( CONTAINS d d )", atIndex(1)); } + @Test + public void testNullLeafTranslatesToEverything() { + Filter eq = FilterBuilder.equalTo(AttributeBuilder.build("a", "a")); + assertEquals(new AllFiltersTranslator().translate(new AndFilter(eq, null)), + new AllFiltersTranslator().translate(eq)); + assertThat(new AllFiltersTranslator().translate(new NotFilter(null))).isEmpty(); + } + private static String translateSingle(AbstractFilterTranslator translator, Filter filter) { List translated = translator.translate(filter); assertEquals(translated.size(), 1); From 3514d47e9ccf80b1d813b6e4f7f4f135fb861a7e Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Sun, 4 Oct 2026 11:56:11 +0300 Subject: [PATCH 3/3] Pin the null left child of AND/OR in ObjectNormalizerFacadeTests testNullSubFilterIsPassedThrough now also builds AndFilter(null, x) and OrFilter(null, x), so routing getLeft() through the null-checked normalizeFilter is pinned as well: each of the five composite sites, mutated on its own, fails the test with an NPE. --- .../api/local/operations/ObjectNormalizerFacadeTests.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OpenICF-java-framework/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacadeTests.java b/OpenICF-java-framework/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacadeTests.java index 04e809468..11af55b85 100644 --- a/OpenICF-java-framework/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacadeTests.java +++ b/OpenICF-java-framework/connector-framework-internal/src/test/java/org/identityconnectors/framework/impl/api/local/operations/ObjectNormalizerFacadeTests.java @@ -189,6 +189,10 @@ public void testNullSubFilterIsPassedThrough() { .getRight()); assertNull(((OrFilter) normalizer.normalizeFilter(new OrFilter(contains, null))) .getRight()); + assertNull(((AndFilter) normalizer.normalizeFilter(new AndFilter(null, contains))) + .getLeft()); + assertNull(((OrFilter) normalizer.normalizeFilter(new OrFilter(null, contains))) + .getLeft()); // a one-element AndFilter has a null right-hand side assertNull(((AndFilter) normalizer.normalizeFilter(new AndFilter(Collections .singletonList(contains)))).getRight());