[java-support] 04/21: Implement draft serializer, start on unit tests.

Scott Cantor cantor.2 at osu.edu
Thu Jun 2 14:38:51 UTC 2022


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=a52d50661616b2781aac561fdf64d212626f4e7b

commit a52d50661616b2781aac561fdf64d212626f4e7b
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon May 17 19:16:59 2021 -0400

    Implement draft serializer, start on unit tests.
---
 .../shibboleth/utilities/java/support/ddf/DDF.java | 131 ++++++++++++++++++++-
 .../utilities/java/support/ddf/DDFTest.java        |  67 +++++++++++
 .../utilities/java/support/ddf/empty-name.ddf      |   1 +
 .../utilities/java/support/ddf/empty-noname.ddf    |   1 +
 4 files changed, 197 insertions(+), 3 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 83272ef..25724fc 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
@@ -17,7 +17,10 @@
 
 package net.shibboleth.utilities.java.support.ddf;
 
+import java.io.IOException;
+import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -930,6 +933,7 @@ public class DDF implements Iterable<DDF> {
         return dump(new StringBuilder(), 0).toString();
     }
 
+// Checkstyle: MethodLength|CyclomaticComplexity OFF
     /**
      * Helper method to dump to a string for debugging.
      * 
@@ -938,7 +942,6 @@ public class DDF implements Iterable<DDF> {
      * 
      * @return the first parameter
      */
-// Checkstyle: MethodLength|CyclomaticComplexity OFF
     @Nonnull private StringBuilder dump(@Nonnull final StringBuilder builder, final long indent) {
         
         for (long i = 0; i < indent; ++i) {
@@ -972,13 +975,17 @@ public class DDF implements Iterable<DDF> {
                 break;
                 
             case DDF_STRING_UNSAFE:
-                builder.append("byte[]");
+                builder.append("char[]");
                 if (name != null) {
                     builder.append(' ').append(name);
                 }
                 builder.append(" = ");
                 if (value != null) {
-                    builder.append(((String) value).getBytes());
+                    builder.append('{');
+                    for (final char c : ((String) value).toCharArray()) {
+                        builder.append(Integer.toHexString(c)).append(", ");
+                    }
+                    builder.append('}');
                 } else {
                     builder.append("null");
                 }
@@ -1057,6 +1064,124 @@ public class DDF implements Iterable<DDF> {
         
         return builder;
     }
+    
+    /**
+     * Serialize this object to a provided stream.
+     * 
+     * @param os output stream
+     *
+     * @return the output stream
+     * 
+     * @throws IOException if an error occurs
+     */
+    @Nonnull public OutputStream serialize(@Nonnull final OutputStream os) throws IOException {
+        if (!isnull()) {
+            if (name != null) {
+                encode(os, name.getBytes("UTF8"));
+            } else {
+                os.write('.');
+            }
+            os.write(' ');
+
+            switch (type) {
+                case DDF_EMPTY:
+                case DDF_POINTER:
+                    os.write(Integer.toString(DDFType.DDF_EMPTY.getValue()).getBytes("UTF8"));
+                    os.write('\n');
+                    break;
+
+                case DDF_STRING:
+                    os.write(Integer.toString(type.getValue()).getBytes("UTF8"));
+                    if (value != null) {
+                        os.write(' ');
+                        encode(os, ((String) value).getBytes("UTF-8"));
+                    }
+                    os.write('\n');
+                    break;
+
+                case DDF_STRING_UNSAFE:
+                    os.write(Integer.toString(type.getValue()).getBytes("UTF8"));
+                    if (value != null) {
+                        os.write(' ');
+                        encode(os, ((String) value).getBytes("ISO-8859-1"));
+                    }
+                    os.write('\n');
+                    break;
+
+                case DDF_INT:
+                    os.write(Integer.toString(type.getValue()).getBytes("UTF8"));
+                    os.write(' ');
+                    os.write(Integer.toString((Integer) value).getBytes("UTF8"));
+                    os.write('\n');
+                    break;
+
+                case DDF_FLOAT:
+                    os.write(Integer.toString(type.getValue()).getBytes("UTF8"));
+                    os.write(' ');
+                    os.write(Double.toString((Double) value).getBytes("UTF8"));
+                    os.write('\n');
+                    break;
+
+                case DDF_STRUCT:
+                    @SuppressWarnings("unchecked")
+                    final Collection<DDF> members = ((Map<String,DDF>) value).values();
+                    os.write(Integer.toString(type.getValue()).getBytes("UTF8"));
+                    os.write(' ');
+                    os.write(Integer.toString(members.size()).getBytes("UTF8"));
+                    os.write('\n');
+                    for (final DDF child : members) {
+                        child.serialize(os);
+                    }
+                    break;
+
+                case DDF_LIST:
+                    @SuppressWarnings("unchecked")
+                    final Collection<DDF> children = (List<DDF>) value;
+                    os.write(Integer.toString(type.getValue()).getBytes("UTF8"));
+                    os.write(' ');
+                    os.write(Integer.toString(children.size()).getBytes("UTF8"));
+                    os.write('\n');
+                    for (final DDF child : children) {
+                        child.serialize(os);
+                    }
+                    break;
+
+                default:
+                    break;
+            }
+        }
+        
+        return os;
+    }
 // Checkstyle: MethodLength|CyclomaticComplexity ON
+
+    /**
+     * A simple encoder for non-ASCII characters.
+     * 
+     * <p>Made this package-accessible for unit testing.</p>
+     * 
+     * @param os output stream
+     * @param bytes bytes to encode
+     * 
+     * @throws IOException if an error occurs
+     */
+    static void encode(@Nonnull final OutputStream os, @Nonnull final byte[] bytes) throws IOException {
+        for (final byte b : bytes) {
+            final int i = Byte.toUnsignedInt(b);
+            // 0x25 is the percent char itself.
+            if (i <= 0x20 || i >= 0x7F || i == 0x25) {
+                os.write('%');
+                os.write(hexchar(i >>> 4));
+                os.write(hexchar(i & 0x0F));
+            } else {
+                os.write(b);
+            }
+        }
+    }
+    
+    private static int hexchar(final int b) {
+        // 48 is '0' and 65 is 'A'
+        return (b <= 9) ? (48 + b) : (65 + b - 10);
+    }
     
 }
\ No newline at end of file
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 5270b08..f6f7350 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
@@ -19,6 +19,11 @@ package net.shibboleth.utilities.java.support.ddf;
 
 import static org.testng.Assert.*;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+
 import org.testng.annotations.Test;
 
 import net.shibboleth.utilities.java.support.collection.Pair;
@@ -90,6 +95,9 @@ public class DDFTest {
         assertTrue(obj.isfloat());
         assertEquals(obj.integer(), Integer.valueOf(42));
         assertEquals(obj.floating(), Double.valueOf(42.42));
+        
+        obj.unsafe_string("bar");
+        System.out.print(obj);
     }
     
     @Test
@@ -160,4 +168,63 @@ public class DDFTest {
         assertTrue(obj.getmember("foo2.foo3").string().equals("bar3"));
     }
     
+    @Test
+    public void testEncoder() throws IOException {
+        try (final ByteArrayOutputStream sink = new ByteArrayOutputStream()) {
+            DDF.encode(sink, "foo".getBytes("UTF8"));
+            assertEquals(sink.toString(), "foo");
+            sink.reset();
+            
+            DDF.encode(sink, "foo bar".getBytes("UTF8"));
+            assertEquals(sink.toString(), "foo%20bar");
+            sink.reset();
+            
+            DDF.encode(sink, "foo\nbar".getBytes("UTF8"));
+            assertEquals(sink.toString(), "foo%0Abar");
+            sink.reset();
+            
+            DDF.encode(sink, "foo☯️bar".getBytes("UTF8"));
+            assertEquals(sink.toString(), "foo%E2%98%AF%EF%B8%8Fbar");
+            sink.reset();
+            
+            // -128 corresponds to 128, which is the extended ASCII Euro symbol.
+            // This test demonstrates that round-tripping through such an encoding
+            // will preserve the original 0x80 hex value in that position in the string
+            // rather than converting through the UTF-8 representation.
+            final byte[] unsafe = {102, 111, 111, -128, 98, 97, 114};
+            DDF.encode(sink, new String(unsafe, "ISO-8859-1").getBytes("ISO-8859-1"));
+            assertEquals(sink.toString(), "foo%80bar");
+            sink.reset();
+        }
+    }
+    
+    @Test
+    public void testSerialize() throws IOException {
+        
+        try (final ByteArrayOutputStream sink = new ByteArrayOutputStream()) {
+            DDF obj = new DDF(null);
+            obj.serialize(sink);
+            assertEquals(sink.toByteArray(), testFile("empty-noname.ddf"));
+            sink.reset();
+            
+            obj.name("foo bar");
+            obj.serialize(sink);
+            assertEquals(sink.toByteArray(), testFile("empty-name.ddf"));
+            sink.reset();
+        }
+    }
+    
+    /**
+     * Convert test file contents to a byte array.
+     * 
+     * @param name file name
+     * 
+     * @return byte array
+     * 
+     * @throws IOException on error
+     */
+    private byte[] testFile(@Nonnull final String name) throws IOException {
+        return getClass().getResourceAsStream(name).readAllBytes();
+    }
+    
 }
\ No newline at end of file
diff --git a/src/test/resources/net/shibboleth/utilities/java/support/ddf/empty-name.ddf b/src/test/resources/net/shibboleth/utilities/java/support/ddf/empty-name.ddf
new file mode 100644
index 0000000..b40aabf
--- /dev/null
+++ b/src/test/resources/net/shibboleth/utilities/java/support/ddf/empty-name.ddf
@@ -0,0 +1 @@
+foo%20bar 0
diff --git a/src/test/resources/net/shibboleth/utilities/java/support/ddf/empty-noname.ddf b/src/test/resources/net/shibboleth/utilities/java/support/ddf/empty-noname.ddf
new file mode 100644
index 0000000..fa85f21
--- /dev/null
+++ b/src/test/resources/net/shibboleth/utilities/java/support/ddf/empty-noname.ddf
@@ -0,0 +1 @@
+. 0

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


More information about the commits mailing list