[java-support] 02/08: Put back notion of "null" object, and add unit tests.

Scott Cantor cantor.2 at osu.edu
Mon Sep 13 23:37:41 UTC 2021


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch dev/JSPT-111
in repository java-support.

View the commit online:
http://git.shibboleth.net/view/?p=java-support.git;a=commit;h=c67ebc58c49680f185cad7c96987a4304b029642

commit c67ebc58c49680f185cad7c96987a4304b029642
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon May 17 10:13:47 2021 -0400

    Put back notion of "null" object, and add unit tests.
---
 .../shibboleth/utilities/java/support/ddf/DDF.java | 107 +++++++++++++--------
 .../utilities/java/support/ddf/DDFTest.java        |  72 +++++++++++++-
 2 files changed, 137 insertions(+), 42 deletions(-)

diff --git a/src/main/java/net/shibboleth/utilities/java/support/ddf/DDF.java b/src/main/java/net/shibboleth/utilities/java/support/ddf/DDF.java
index 8e75e06..8ee2374 100644
--- a/src/main/java/net/shibboleth/utilities/java/support/ddf/DDF.java
+++ b/src/main/java/net/shibboleth/utilities/java/support/ddf/DDF.java
@@ -19,11 +19,10 @@ package net.shibboleth.utilities.java.support.ddf;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Collectors;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -54,7 +53,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
  * the other version(s) of the same API.</p>
  */
 @NotThreadSafe
-public class DDF {
+public class DDF implements Iterable<DDF> {
 
     /** Name of node. */
     @Nullable private String name;
@@ -65,6 +64,9 @@ public class DDF {
     /** Type enum. */
     public enum DDFType {
 
+        /** A null node. */
+        DDF_NULL(-1),
+
         /** An empty node with no value. */
         DDF_EMPTY(0),
         
@@ -120,7 +122,7 @@ public class DDF {
     
     /** Constructor. */
     public DDF() {
-        type = DDFType.DDF_EMPTY;
+        type = DDFType.DDF_NULL;
     }
 
     /**
@@ -131,7 +133,7 @@ public class DDF {
      * @param n node name
      */
     public DDF(@Nullable @NotEmpty final String n) {
-        this();
+        type = DDFType.DDF_EMPTY;
         name(n);
     }
 
@@ -197,7 +199,7 @@ public class DDF {
      */
     @Nonnull public DDF destroy() {
         remove().empty().name(null);
-        type = null;
+        type = DDFType.DDF_NULL;
         return this;
     }
     
@@ -211,6 +213,10 @@ public class DDF {
         final DDF dup = new DDF(name);
         
         switch (type) {
+            case DDF_NULL:
+                dup.destroy();
+                break;
+                
             case DDF_EMPTY:
                 break;
                 
@@ -271,7 +277,7 @@ public class DDF {
      * @return this object
      */
     @Nonnull public DDF name(@Nullable @NotEmpty final String n) {
-        if (parent == null || !parent.isstruct()) {
+        if (!isnull() && (parent == null || !parent.isstruct())) {
             if (n != null) {
                 name = Constraint.isNotEmpty(n.substring(0,Integer.min(n.length(), 255)), "Name cannot be empty");
             } else {
@@ -281,6 +287,15 @@ public class DDF {
         return this;
     }
     
+    /**
+     * Returns true iff the node is null.
+     * 
+     * @return true iff the node is null
+     */
+    public boolean isnull() {
+        return type == DDFType.DDF_NULL;
+    }
+    
     /**
      * Returns true iff the node is empty.
      * 
@@ -614,7 +629,7 @@ public class DDF {
      */
     @SuppressWarnings("unchecked")
     @Nonnull public DDF add(@Nonnull final DDF child) {
-        if ((!isstruct() && !islist()) || this == child.parent) {
+        if ((!isstruct() && !islist()) || child.isnull() || this == child.parent) {
             return child;
         }
 
@@ -647,7 +662,7 @@ public class DDF {
      * @return the child
      */
     @Nonnull public DDF addbefore(@Nonnull final DDF child, @Nonnull final DDF before) {
-        if (!islist() || before.parent != this) {
+        if (!islist() || child.isnull() || before.parent != this) {
             return child;
         }
 
@@ -672,7 +687,7 @@ public class DDF {
      * @return the child
      */
     @Nonnull public DDF addafter(@Nonnull final DDF child, @Nonnull final DDF after) {
-        if (!islist() || after.parent != this) {
+        if (!islist() || child.isnull() || after.parent != this) {
             return child;
         }
 
@@ -721,36 +736,33 @@ public class DDF {
     }
     
     /**
-     * Expose an immutable map representing a structure or list node.
+     * Expose an immutable map representing a structure node.
      * 
-     * @return immutable map, or null
+     * @return immutable map, or null if the node is not a structure
      */
     @SuppressWarnings("unchecked")
-    @Nonnull @NonnullElements @Unmodifiable @NotLive public Map<String,DDF> asMap() {
+    @Nullable @NonnullElements @Unmodifiable @NotLive public Map<String,DDF> asMap() {
         if (isstruct()) {
             return Map.copyOf((Map<String,DDF>) value);
-        } else if (islist()) {
-            return ((List<DDF>) value).stream().collect(
-                    Collectors.toUnmodifiableMap(DDF::name, Function.identity()));
         }
         
-        return Collections.emptyMap();
+        return null;
     }
 
     /**
      * Expose an immutable list representing a structure or list node.
      * 
-     * @return immutable list, or null
+     * @return immutable list, or null if the node is not a structure or list
      */
     @SuppressWarnings("unchecked")
-    @Nonnull @NonnullElements @Unmodifiable @NotLive public List<DDF> asList() {
+    @Nullable @NonnullElements @Unmodifiable @NotLive public List<DDF> asList() {
         if (isstruct()) {
             return List.copyOf(((Map<String,DDF>) value).values());
         } else if (islist()) {
             return List.copyOf((List<DDF>) value);
         }
         
-        return Collections.emptyList();
+        return null;
     }
 
     /**
@@ -764,45 +776,49 @@ public class DDF {
      * 
      * @param path dotted path to use
      * 
-     * @return the last node added to the nested tree
+     * @return the last node added to the nested tree, or a null node if unable to do so
      */
     @Nonnull public DDF addmember(@Nonnull @NotEmpty final String path) {
         final String[] tokens = Constraint.isNotEmpty(path, "Path cannot be null").split("\\.");
         Constraint.isNotEmpty(tokens, "Path did not produce an array of path segments");
         
-        DDF base = this;
-        for (final String segment : tokens) {
-            if (!base.isstruct()) {
-                base.structure();
-            }
-            
-            DDF node = base.getmember(segment);
-            if (node == null) {
-                node = base.add(new DDF(segment));
+        if (!isnull()) {
+            DDF base = this;
+            for (final String segment : tokens) {
+                if (!base.isstruct()) {
+                    base.structure();
+                }
+                
+                DDF node = base.getmember(segment);
+                if (node.isnull()) {
+                    node = base.add(new DDF(segment));
+                }
+                
+                base = node;
             }
             
-            base = node;
+            return base;
         }
         
-        return base;
+        return new DDF();
     }
 
     /**
      * Access a (possibly nested) structure member via dotted path notation, also allowing access to
      * list elements via "[n]" array notation.
      * 
-     * <p>Failure to navigate the tree at any point will cause a null to be returned.
+     * <p>Failure to navigate the tree at any point will cause a null node to be returned.</p>
      * 
      * @param path dotted path to use
      * 
-     * @return the matching node, or null
+     * @return the matching node, or a null node
      */
 // Checkstyle: CyclomaticComplexity OFF
     @SuppressWarnings("unchecked")
-    @Nullable public DDF getmember(@Nonnull @NotEmpty final String path) {
+    @Nonnull public DDF getmember(@Nonnull @NotEmpty final String path) {
         final String[] tokens = path.split("\\.");
-        if (tokens == null || tokens.length == 0) {
-            return null;
+        if (tokens == null || tokens.length == 0 || isnull()) {
+            return new DDF();
         }
 
         DDF current = this;
@@ -819,30 +835,39 @@ public class DDF {
                 if (islist() && index < ((List<DDF>) current.value).size()) {
                     current = ((List<DDF>) current.value).get(index);
                 } else {
-                    return null;
+                    return new DDF();
                 }
                 i++;
             } else if (current.isstruct()) {
                 // Access the named element and advance the path.
                 current = ((Map<String,DDF>) current.value).get(tokens[i]);
                 if (current == null) {
-                    return null;
+                    return new DDF();
                 }
                 i++;
             } else if (current.islist()) {
                 // Access first element of list, don't advance the path.
                 current = ((List<DDF>) current.value).get(0);
                 if (current == null) {
-                    return null;
+                    return new DDF();
                 }
             } else {
-                return null;
+                return new DDF();
             }
         }
         
         return current;
     }
 
+    /** {@inheritDoc} */
+    @Nonnull public Iterator<DDF> iterator() {
+        final List<DDF> list = asList();
+        if (list != null) {
+            return list.iterator();
+        }
+        return Collections.emptyListIterator();
+    }
+    
     /** {@inheritDoc} */
     @Override
     public boolean equals(final Object obj) {
diff --git a/src/test/java/net/shibboleth/utilities/java/support/ddf/DDFTest.java b/src/test/java/net/shibboleth/utilities/java/support/ddf/DDFTest.java
index 1989a9a..5270b08 100644
--- a/src/test/java/net/shibboleth/utilities/java/support/ddf/DDFTest.java
+++ b/src/test/java/net/shibboleth/utilities/java/support/ddf/DDFTest.java
@@ -21,6 +21,8 @@ import static org.testng.Assert.*;
 
 import org.testng.annotations.Test;
 
+import net.shibboleth.utilities.java.support.collection.Pair;
+
 /**
  * DDF unit tests.
  */
@@ -31,7 +33,7 @@ public class DDFTest {
     public void testConstructors() {
         DDF obj = new DDF();
         assertNull(obj.name());
-        assertTrue(obj.isempty());
+        assertTrue(obj.isnull());
         
         obj = new DDF("foo");
         assertEquals(obj.name(), "foo");
@@ -89,5 +91,73 @@ public class DDFTest {
         assertEquals(obj.integer(), Integer.valueOf(42));
         assertEquals(obj.floating(), Double.valueOf(42.42));
     }
+    
+    @Test
+    public void testLists() {
+        final DDF obj = new DDF().list();
+        assertTrue(obj.islist());
+        assertEquals(obj.integer(), Integer.valueOf(0));
+        
+        obj.add(new DDF("foo", "bar"));
+        obj.add(new DDF("foo2", 42));
+        obj.add(new DDF("foo3").pointer(new Pair<>()));
+        assertEquals(obj.integer(), Integer.valueOf(3));
+        
+        for (final DDF el : obj) {
+            switch (el.name()) {
+                case "foo":
+                    assertEquals(el.string(), "bar");
+                    break;
+                
+                case "foo2":
+                    assertEquals(el.integer(), Integer.valueOf(42));
+                    break;
+                        
+                case "foo3":
+                    assertEquals(el.pointer(), new Pair<>());
+                    break;
+                        
+                default:
+                    fail("Node unrecognized");
+            }
+        }
+        
+        assertEquals(obj.getmember("[0]"), new DDF("foo", "bar"));
+        assertEquals(obj.getmember("[1]"), new DDF("foo2", 42));
+        assertTrue(obj.getmember("[3]").isnull());
+        
+        obj.addafter(new DDF(null), obj.getmember("[0]"));
+        assertEquals(obj.integer(), Integer.valueOf(4));
+        assertTrue(obj.getmember("[1]").isempty());
 
+        obj.addbefore(new DDF("foo4"), obj.getmember("[2]"));
+        assertEquals(obj.integer(), Integer.valueOf(5));
+        assertTrue(obj.getmember("[2]").name().equals("foo4"));
+        
+        assertTrue(obj.asList().get(4).remove().ispointer());
+        assertEquals(obj.integer(), Integer.valueOf(4));
+    }
+
+    @Test
+    public void testStructures() {
+        final DDF obj = new DDF().structure();
+        assertTrue(obj.isstruct());
+        assertEquals(obj.integer(), Integer.valueOf(0));
+        
+        obj.add(new DDF("foo", "bar"));
+        assertEquals(obj.integer(), Integer.valueOf(1));
+        assertTrue(obj.getmember("foo").name().equals("foo"));
+        assertTrue(obj.getmember("foo").string().equals("bar"));
+        
+        obj.addmember("foo2").integer(42);
+        assertEquals(obj.integer(), Integer.valueOf(2));
+        
+        obj.addmember("foo2.foo3").string("bar3");
+        assertEquals(obj.integer(), Integer.valueOf(2));
+        assertTrue(obj.getmember("foo2").isstruct());
+        assertEquals(obj.getmember("foo2").integer(), Integer.valueOf(1));
+        assertTrue(obj.getmember("foo2").getmember("foo3").string().equals("bar3"));
+        assertTrue(obj.getmember("foo2.foo3").string().equals("bar3"));
+    }
+    
 }
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list