Commits

Rhys ! committed 87eb827 Draft

better regex {de|en}coding and tests, plus new typed encode() methods on Encoder

Comments (0)

Files changed (5)

src/org/bert_rpc/Bert.java

     public static byte[] encode(Object value) throws EncodingException {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         try {
-            new Encoder(out).encode(value);
+            new Encoder(out).encodeObject(value);
             out.flush();
         } catch (IOException e) {
             throw new RuntimeException("Writing to array failed somehow: "

src/org/bert_rpc/Constants.java

 package org.bert_rpc;
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
 /**
  * BERT magic values.
  */
     public static final Atom $nil = new Atom("nil");
     public static final Atom $true = new Atom("true");
     public static final Atom $false = new Atom("false");
+
     public static final Atom $caseless = new Atom("caseless");
     public static final Atom $dotall = new Atom("dotall");
     public static final Atom $extended = new Atom("extended");
     public static final Atom $multiline = new Atom("multiline");
     public static final Atom $newline = new Atom("newline");
     public static final Atom $lf = new Atom("lf");
+
+    public static final Map<Object, Integer> REGEX_FLAGS =
+            new HashMap<Object, Integer>();
+    static {
+        Constants.REGEX_FLAGS.put(Constants.$caseless,
+                Pattern.CASE_INSENSITIVE);
+        Constants.REGEX_FLAGS.put(Constants.$dotall, Pattern.DOTALL);
+        Constants.REGEX_FLAGS.put(Constants.$extended, Pattern.COMMENTS);
+        Constants.REGEX_FLAGS.put(Constants.$multiline, Pattern.MULTILINE);
+        Constants.REGEX_FLAGS.put(new Tuple(Constants.$newline, Constants.$lf),
+                Pattern.UNIX_LINES);
+    }
 }

src/org/bert_rpc/Decoder.java

 
     private Object read() throws DecodingException, IOException {
         Object simple = this.readSimple();
-        if (simple instanceof Tuple &&
-                ((Tuple) simple).contents.length > 0 &&
-                Constants.$bert.equals(((Tuple) simple).contents[0])) {
-            return this.extractComplex((Tuple) simple);
-        } else {
-            return simple;
+        if (simple instanceof Tuple) {
+            Tuple tuple = (Tuple)simple;
+            if (tuple.contents.length > 0 &&
+                    Constants.$bert.equals(tuple.contents[0])) {
+                return this.extractComplex(tuple);
+            }
         }
+
+        return simple;
     }
 
     private Object extractComplex(Tuple tuple) throws DecodingException {
         List flags = (List)tuple.contents[3];
         int mask = 0;
         for (Object flag : flags) {
-            if (flag instanceof Atom) {
-                Atom atom = (Atom)flag;
-                if (atom.equals(Constants.$caseless)) {
-                    mask = mask | Pattern.CASE_INSENSITIVE;
-                } else if (atom.equals(Constants.$dotall)) {
-                    mask = mask | Pattern.DOTALL;
-                } else if (atom.equals(Constants.$extended)) {
-                    mask = mask | Pattern.COMMENTS;
-                } else if (atom.equals(Constants.$multiline)) {
-                    mask = mask | Pattern.MULTILINE;
-                } else if (atom.equals(Constants.$dotall)) {
-                    mask = mask | Pattern.DOTALL;
-                } else {
-                    throw new DecodingException("unsupported regex flag "
-                        + atom);
-                }
-            } else if (flag instanceof Tuple) {
-                Tuple flagTuple = (Tuple)flag;
-                if (flagTuple.contents.length == 2 &&
-                        Constants.$newline.equals(flagTuple.contents[0]) &&
-                        Constants.$lf.equals(flagTuple.contents[1])) {
-                    mask = mask | Pattern.UNIX_LINES;
-                } else {
-                    throw new DecodingException("unsupported regex flag "
-                        + flagTuple);
-                }
+            Integer maskComponent = Constants.REGEX_FLAGS.get(flag);
+            if (maskComponent != null) {
+                mask = (mask | (int)maskComponent);
             } else {
-                throw new DecodingException("unrecognised regex flag type "
-                        + flag.getClass().getName());
+                throw new DecodingException("unsupported regex flag "
+                        + flag);
             }
         }
 
         return Pattern.compile(
-                new String((byte[])tuple.contents[1], Charset.forName("UTF8")),
+                new String((byte[])tuple.contents[2], Charset.forName("UTF8")),
                 mask);
     }
 

src/org/bert_rpc/Encoder.java

         this.outputStream = new DataOutputStream(outputStream);
     }
 
-    public void encode(Object value) throws IOException, EncodingException {
+    public void encodeObject(Object object) throws IOException, EncodingException {
         this.outputStream.writeByte(Constants.MAGIC);
-        this.write(value);
+        this.write(object);
         this.outputStream.flush();
     }
 
         }
     }
 
+    public void encode(Tuple tuple) throws IOException, EncodingException {
+        this.outputStream.writeByte(Constants.MAGIC);
+        if (tuple == null) this.writeNull(); else this.writeTuple(tuple);
+        this.outputStream.flush();
+    }
+
     private void writeTuple(Tuple tuple)
             throws IOException, EncodingException {
         if (tuple.contents.length < 256) {
         }
     }
 
+    public void encode(Atom atom) throws IOException {
+        this.outputStream.writeByte(Constants.MAGIC);
+        if (atom == null) this.writeNull(); else this.writeAtom(atom);
+        this.outputStream.flush();
+    }
+
     private void writeAtom(Atom atom) throws IOException {
         this.outputStream.writeByte(Constants.ATOM);
         this.outputStream.writeShort(atom.identifier.length());
         this.outputStream.writeBytes(atom.identifier);
     }
 
+    public void encode(Map<Object, Object> map)
+            throws IOException, EncodingException {
+        this.outputStream.writeByte(Constants.MAGIC);
+        if (map == null) this.writeNull(); else this.writeMap(map);
+        this.outputStream.flush();
+    }
+
     private void writeMap(Map<Object, Object> map)
             throws IOException, EncodingException {
         List<Tuple> pairs = new ArrayList<Tuple>(map.size());
         this.writeTuple(new Tuple(Constants.$bert, Constants.$dict, pairs));
     }
 
-    private void writeDate(Date date) throws IOException, EncodingException {
+    public void encode(Date date) throws IOException {
+        this.outputStream.writeByte(Constants.MAGIC);
+        if (date == null) this.writeNull(); else this.writeDate(date);
+        this.outputStream.flush();
+    }
+
+    private void writeDate(Date date) throws IOException {
         long millis = date.getTime();
 
-        this.writeTuple(new Tuple(Constants.$bert, Constants.$time,
-                (int)Math.floor(millis / 1e9),
-                (int)Math.floor((millis % 1e9) / 1e3),
-                (int)Math.floor(millis % 1e3)));
+        try {
+            this.writeTuple(new Tuple(Constants.$bert, Constants.$time,
+                    (int)Math.floor(millis / 1e9),
+                    (int)Math.floor((millis % 1e9) / 1e3),
+                    (int)Math.floor(millis % 1e3)));
+        } catch (EncodingException e) {
+            throw new RuntimeException(
+                    "somehow failed to encode date (" + e.getMessage()
+                    + "). this is a bug in jbert.",
+                    e);
+        }
     }
 
-    private void writeInt(Integer value) throws IOException {
+    public void encode(Integer value) throws IOException {
+        if (value == null) this.encodeNull(); else this.encode((int)value);
+    }
+
+    public void encode(int value) throws IOException {
+        this.outputStream.writeByte(Constants.MAGIC);
+        this.writeInt(value);
+        this.outputStream.flush();
+    }
+
+    private void writeInt(int value) throws IOException {
         if (value >= 0 && value < 256) {
             this.outputStream.writeByte(Constants.SMALL_INT);
             this.outputStream.writeByte(value);
         }
     }
 
-    private void writeDouble(Double value) throws IOException {
+    public void encode(Double value) throws IOException {
+        if (value == null) this.encodeNull(); else this.encode((double)value);
+    }
+
+    public void encode(double value) throws IOException {
+        this.outputStream.writeByte(Constants.MAGIC);
+        this.writeDouble(value);
+        this.outputStream.flush();
+    }
+
+    private void writeDouble(double value) throws IOException {
         byte[] bytes = String.format("%15.15e", value).getBytes();
         this.outputStream.writeByte(Constants.FLOAT);
         this.outputStream.write(Arrays.copyOf(bytes, Constants.FLOAT_LENGTH));
     }
 
-    private void writeFloat(Float value) throws IOException {
+    public void encode(Float value) throws IOException {
+        if (value == null) this.encodeNull(); else this.encode((float)value);
+    }
+
+    public void encode(float value) throws IOException {
+        this.outputStream.writeByte(Constants.MAGIC);
+        this.writeFloat(value);
+        this.outputStream.flush();
+    }
+
+    private void writeFloat(float value) throws IOException {
         this.writeDouble((double) value);
     }
 
-    private void writeBoolean(Boolean value)
-            throws IOException, EncodingException {
-        this.writeTuple(
-                new Tuple(Constants.$bert, new Atom(value.toString())));
+    public void encode(Boolean value) throws IOException {
+        if (value == null) this.encodeNull(); else this.encode((boolean)value);
     }
 
-    private void writeNull() throws IOException, EncodingException {
-        this.writeTuple(new Tuple(Constants.$bert, Constants.$nil));
+    public void encode(boolean bool) throws IOException {
+        this.outputStream.writeByte(Constants.MAGIC);
+        this.writeBoolean(bool);
+        this.outputStream.flush();
+    }
+
+    private void writeBoolean(boolean bool)
+            throws IOException {
+        Atom representation;
+        if (bool) {
+            representation = Constants.$true;
+        } else {
+            representation = Constants.$false;
+        }
+
+        try {
+            this.writeTuple(new Tuple(Constants.$bert, representation));
+        } catch (EncodingException e) {
+            throw new RuntimeException("somehow failed to encode boolean (" +
+                    e.getMessage() + "). this is a bug in jbert.", e);
+        }
+    }
+
+    public void encodeNull() throws IOException {
+        this.outputStream.writeByte(Constants.MAGIC);
+        this.writeNull();
+        this.outputStream.flush();
+    }
+
+    private void writeNull() throws IOException {
+        try {
+            this.writeTuple(new Tuple(Constants.$bert, Constants.$nil));
+        } catch (EncodingException e) {
+            throw new RuntimeException(
+                    "somehow failed to encode null (" + e.getMessage() +
+                    "). this is a bug in jbert.",
+                    e);
+        }
+    }
+
+    public void encode(byte[] bytes) throws IOException {
+        this.outputStream.writeByte(Constants.MAGIC);
+        if (bytes == null) this.writeNull(); else this.writeBinary(bytes);
+        this.outputStream.flush();
     }
 
     private void writeBinary(byte[] bytes) throws IOException {
         this.outputStream.write(bytes);
     }
 
+    public void encode(List list) throws IOException, EncodingException {
+        this.outputStream.writeByte(Constants.MAGIC);
+        if (list == null) this.writeNull(); else this.writeList(list);
+        this.outputStream.flush();
+    }
+
     private void writeList(List list) throws IOException, EncodingException {
         if (list.size() == 0) {
             this.outputStream.writeByte(Constants.NIL);
         }
     }
 
+    public void encode(Pattern regex) throws IOException {
+        this.outputStream.writeByte(Constants.MAGIC);
+        if (regex == null) this.writeNull(); else this.writeRegex(regex);
+        this.outputStream.flush();
+    }
+
     private void writeRegex(Pattern regex)
-            throws IOException, EncodingException {
-        this.writeTuple(new Tuple(Constants.$bert, Constants.$regex,
-                regex.toString().getBytes("UTF8"), Collections.emptyList()));
+            throws IOException {
+        List<Object> flags = new ArrayList<Object>();
+        int runningMask = regex.flags();
+        for (Map.Entry<Object, Integer> p : Constants.REGEX_FLAGS.entrySet()) {
+            if ((runningMask & (int)p.getValue()) != 0) {
+                runningMask = runningMask ^ (int)p.getValue();
+                flags.add(p.getKey());
+            }
+        }
+
+        try {
+            this.writeTuple(new Tuple(Constants.$bert, Constants.$regex,
+                    regex.pattern().getBytes("UTF8"),
+                    flags));
+        } catch (EncodingException e) {
+            throw new RuntimeException("somehow failed to encode regex (" +
+                    e.getMessage() + "). this is a bug in jbert.", e);
+        }
     }
 }

test/src/org/bert_rpc/TestEncodeDecode.java

+package org.bert_rpc;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.regex.Pattern;
+
+public class TestEncodeDecode {
+    @Test
+    public void testRegexen() throws Exception {
+        Pattern expected = Pattern.compile("(foo|spam)|(bar|eggs)",
+                Pattern.MULTILINE | Pattern.UNIX_LINES);
+        Pattern result = (Pattern)Bert.decode(Bert.encode(expected));
+        Assert.assertEquals("encodes & decodes regex patterns",
+                expected.pattern(),
+                result.pattern());
+        Assert.assertEquals("encodes & decodes regex flags",
+                expected.flags(),
+                result.flags());
+    }
+}