All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.upokecenter.cbor.CBORJson Maven / Gradle / Ivy

Go to download

A Java implementation of Concise Binary Object Representation (CBOR), a general-purpose binary data format defined in RFC 7049.

There is a newer version: 5.0.0-alpha1
Show newest version
package com.upokecenter.cbor;
/*
Written by Peter O. in 2014.
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
If you like this, you should donate to Peter O.
at: http://peteroupc.github.io/
 */

import java.util.*;

import com.upokecenter.util.*;
import com.upokecenter.numbers.*;

  final class CBORJson {
    private static final String Hex16 = "0123456789ABCDEF";
    // JSON parsing methods
    private static int SkipWhitespaceJSON(CharacterInputWithCount reader) {
      while (true) {
        int c = reader.ReadChar();
        if (c == -1 || (c != 0x20 && c != 0x0a && c != 0x0d && c != 0x09)) {
          return c;
        }
      }
    }

    private CharacterInputWithCount reader;
    private StringBuilder sb;

    private String NextJSONString() {
      int c;
      this.sb = (this.sb == null) ? (new StringBuilder()) : this.sb;
      this.sb.delete(0, this.sb.length());
      while (true) {
        c = this.reader.ReadChar();
        if (c == -1 || c < 0x20) {
          this.reader.RaiseError("Unterminated String");
        }
        switch (c) {
          case '\\':
            c = this.reader.ReadChar();
            switch (c) {
              case '\\':
                this.sb.append('\\');
                break;
              case '/':
                // Now allowed to be escaped under RFC 8259
                this.sb.append('/');
                break;
              case '\"':
                this.sb.append('\"');
                break;
              case 'b':
                this.sb.append('\b');
                break;
              case 'f':
                this.sb.append('\f');
                break;
              case 'n':
                this.sb.append('\n');
                break;
              case 'r':
                this.sb.append('\r');
                break;
              case 't':
                this.sb.append('\t');
                break;
              case 'u': { // Unicode escape
                  c = 0;
                  // Consists of 4 hex digits
                  for (int i = 0; i < 4; ++i) {
                    int ch = this.reader.ReadChar();
                    if (ch >= '0' && ch <= '9') {
                      c <<= 4;
                      c |= ch - '0';
                    } else if (ch >= 'A' && ch <= 'F') {
                      c <<= 4;
                      c |= ch + 10 - 'A';
                    } else if (ch >= 'a' && ch <= 'f') {
                      c <<= 4;
                      c |= ch + 10 - 'a';
                    } else {
                      this.reader.RaiseError(
                          "Invalid Unicode escaped character");
                    }
                  }
                  if ((c & 0xf800) != 0xd800) {
                    // Non-surrogate
                    this.sb.append((char)c);
                  } else if ((c & 0xfc00) == 0xd800) {
                    int ch = this.reader.ReadChar();
                    if (ch != '\\' || this.reader.ReadChar() != 'u') {
                      this.reader.RaiseError("Invalid escaped character");
                    }
                    int c2 = 0;
                    for (int i = 0; i < 4; ++i) {
                      ch = this.reader.ReadChar();
                      if (ch >= '0' && ch <= '9') {
                        c2 <<= 4;
                        c2 |= ch - '0';
                      } else if (ch >= 'A' && ch <= 'F') {
                        c2 <<= 4;
                        c2 |= ch + 10 - 'A';
                      } else if (ch >= 'a' && ch <= 'f') {
                        c2 <<= 4;
                        c2 |= ch + 10 - 'a';
                      } else {
  this.reader.RaiseError("Invalid Unicode escaped character");
                      }
                    }
                    if ((c2 & 0xfc00) != 0xdc00) {
                      this.reader.RaiseError("Unpaired surrogate code point");
                    } else {
                      this.sb.append((char)c);
                      this.sb.append((char)c2);
                    }
                  } else {
                    this.reader.RaiseError("Unpaired surrogate code point");
                  }
                  break;
                }
              default: {
                  this.reader.RaiseError("Invalid escaped character");
                  break;
                }
            }
            break;
          case 0x22: // double quote
            return this.sb.toString();
          default: {
              // NOTE: Assumes the character reader
              // throws an error on finding illegal surrogate
              // pairs in the String or invalid encoding
              // in the stream
              if ((c >> 16) == 0) {
                this.sb.append((char)c);
              } else {
                this.sb.append((char)((((c - 0x10000) >> 10) & 0x3ff) |
0xd800));
                this.sb.append((char)(((c - 0x10000) & 0x3ff) | 0xdc00));
              }
              break;
            }
        }
      }
    }

    private CBORObject NextJSONValue(
      int firstChar,
      int[] nextChar,
      int depth) {
      String str;
      int c = firstChar;
      CBORObject obj = null;
      if (c < 0) {
        this.reader.RaiseError("Unexpected end of data");
      }
      switch (c) {
        case '"': {
            // Parse a String
            // The tokenizer already checked the String for invalid
            // surrogate pairs, so just call the CBORObject
            // constructor directly
            obj = CBORObject.FromRaw(this.NextJSONString());
            nextChar[0] = SkipWhitespaceJSON(this.reader);
            return obj;
          }
        case '{': {
            // Parse an object
            obj = this.ParseJSONObject(depth + 1);
            nextChar[0] = SkipWhitespaceJSON(this.reader);
            return obj;
          }
        case '[': {
            // Parse an array
            obj = this.ParseJSONArray(depth + 1);
            nextChar[0] = SkipWhitespaceJSON(this.reader);
            return obj;
          }
        case 't': {
            // Parse true
            if (this.reader.ReadChar() != 'r' ||
                this.reader.ReadChar() != 'u' ||
                this.reader.ReadChar() != 'e') {
              this.reader.RaiseError("Value can't be parsed.");
            }
            nextChar[0] = SkipWhitespaceJSON(this.reader);
            return CBORObject.True;
          }
        case 'f': {
            // Parse false
            if (this.reader.ReadChar() != 'a' ||
              this.reader.ReadChar() != 'l' ||
              this.reader.ReadChar() != 's' || this.reader.ReadChar() != 'e') {
              this.reader.RaiseError("Value can't be parsed.");
            }
            nextChar[0] = SkipWhitespaceJSON(this.reader);
            return CBORObject.False;
          }
        case 'n': {
            // Parse null
            if (this.reader.ReadChar() != 'u' ||
              this.reader.ReadChar() != 'l' ||
              this.reader.ReadChar() != 'l') {
              this.reader.RaiseError("Value can't be parsed.");
            }
            nextChar[0] = SkipWhitespaceJSON(this.reader);
            return CBORObject.Null;
          }
        case '-': {
            // Parse a negative number
            boolean lengthTwo = true;
            c = this.reader.ReadChar();
            if (c < '0' || c > '9') {
              this.reader.RaiseError("JSON number can't be parsed.");
            }
            int cval = -(c - '0');
            int cstart = c;
            StringBuilder sb = null;
            c = this.reader.ReadChar();
            while (c == '-' || c == '+' || c == '.' || (c >= '0' && c <= '9') ||
                   c == 'e' || c == 'E') {
              if (lengthTwo) {
                sb = new StringBuilder();
                sb.append((char)'-');
                sb.append((char)cstart);
                lengthTwo = false;
              }
              sb.append((char)c);
              c = this.reader.ReadChar();
            }
            if (lengthTwo) {
              obj = cval == 0 ?
              CBORDataUtilities.ParseJSONNumber("-0", true, false, true) :
                CBORObject.FromObject(cval);
              } else {
              str = sb.toString();
              obj = CBORDataUtilities.ParseJSONNumber(str);
              if (obj == null) {
                this.reader.RaiseError("JSON number can't be parsed. " + str);
              }
            }
            if (c == -1 || (c != 0x20 && c != 0x0a && c != 0x0d && c != 0x09)) {
              nextChar[0] = c;
            } else {
              nextChar[0] = SkipWhitespaceJSON(this.reader);
            }
            return obj;
          }
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9': {
            // Parse a number
            boolean lengthOne = true;
            int cval = c - '0';
            int cstart = c;
            StringBuilder sb = null;
            c = this.reader.ReadChar();
            while (c == '-' || c == '+' || c == '.' || (c >= '0' && c <= '9') ||
                   c == 'e' || c == 'E') {
              if (lengthOne) {
                sb = new StringBuilder();
                sb.append((char)cstart);
                lengthOne = false;
              }
              sb.append((char)c);
              c = this.reader.ReadChar();
            }
            if (lengthOne) {
              obj = CBORObject.FromObject(cval);
            } else {
              str = sb.toString();
              obj = CBORDataUtilities.ParseJSONNumber(str);
              if (obj == null) {
                this.reader.RaiseError("JSON number can't be parsed. " + str);
              }
            }
            if (c == -1 || (c != 0x20 && c != 0x0a && c != 0x0d && c != 0x09)) {
              nextChar[0] = c;
            } else {
              nextChar[0] = SkipWhitespaceJSON(this.reader);
            }
            return obj;
          }
        default:
          this.reader.RaiseError("Value can't be parsed.");
          break;
      }
      return null;
    }

    private boolean noDuplicates;

    public CBORJson(CharacterInputWithCount reader, boolean noDuplicates) {
      this.reader = reader;
      this.sb = null;
      this.noDuplicates = noDuplicates;
    }

    public CBORObject ParseJSON(boolean objectOrArrayOnly, int[] nextchar) {
      int c;
      CBORObject ret;
      c = SkipWhitespaceJSON(this.reader);
      if (c == '[') {
        ret = this.ParseJSONArray(0);
        nextchar[0] = SkipWhitespaceJSON(this.reader);
        return ret;
      }
      if (c == '{') {
        ret = this.ParseJSONObject(0);
        nextchar[0] = SkipWhitespaceJSON(this.reader);
        return ret;
      }
      if (objectOrArrayOnly) {
        this.reader.RaiseError("A JSON Object must begin with '{' or '['");
      }
      return this.NextJSONValue(c, nextchar, 0);
    }

    static CBORObject ParseJSONValue(
      CharacterInputWithCount reader,
      boolean noDuplicates,
      boolean objectOrArrayOnly,
      int[] nextchar) {
      CBORJson cj = new CBORJson(reader, noDuplicates);
      return cj.ParseJSON(objectOrArrayOnly, nextchar);
    }

    private CBORObject ParseJSONObject(int depth) {
      // Assumes that the last character read was '{'
      if (depth > 1000) {
        this.reader.RaiseError("Too deeply nested");
      }
      int c;
      CBORObject key = null;
      CBORObject obj;
      int[] nextchar = new int[1];
      boolean seenComma = false;
      HashMap myHashMap = new HashMap();
      while (true) {
        c = SkipWhitespaceJSON(this.reader);
        switch (c) {
          case -1:
            this.reader.RaiseError("A JSON Object must end with '}'");
            break;
          case '}':
            if (seenComma) {
              // Situation like '{"0"=>1,}'
              this.reader.RaiseError("Trailing comma");
              return null;
            }
            return CBORObject.FromRaw(myHashMap);
          default: {
              // Read the next String
              if (c < 0) {
                this.reader.RaiseError("Unexpected end of data");
                return null;
              }
              if (c != '"') {
                this.reader.RaiseError("Expected a String as a key");
                return null;
              }
              // Parse a String that represents the Object's key
              // The tokenizer already checked the String for invalid
              // surrogate pairs, so just call the CBORObject
              // constructor directly
              obj = CBORObject.FromRaw(this.NextJSONString());
              key = obj;
              if (this.noDuplicates && myHashMap.containsKey(obj)) {
                this.reader.RaiseError("Key already exists: " + key);
                return null;
              }
              break;
            }
        }
        if (SkipWhitespaceJSON(this.reader) != ':') {
          this.reader.RaiseError("Expected a ':' after a key");
        }
        // NOTE: Will overwrite existing value
        myHashMap.put(key, this.NextJSONValue(
          SkipWhitespaceJSON(this.reader),
          nextchar,
          depth));
        switch (nextchar[0]) {
          case ',':
            seenComma = true;
            break;
          case '}':
            return CBORObject.FromRaw(myHashMap);
          default: this.reader.RaiseError("Expected a ',' or '}'");
            break;
        }
      }
    }

    CBORObject ParseJSONArray(int depth) {
      // Assumes that the last character read was '['
      if (depth > 1000) {
        this.reader.RaiseError("Too deeply nested");
      }
      ArrayList myArrayList = new ArrayList();
      boolean seenComma = false;
      int[] nextchar = new int[1];
      while (true) {
        int c = SkipWhitespaceJSON(this.reader);
        if (c == ']') {
          if (seenComma) {
            // Situation like '[0,1,]'
            this.reader.RaiseError("Trailing comma");
          }
          return CBORObject.FromRaw(myArrayList);
        }
        if (c == ',') {
          // Situation like '[,0,1,2]' or '[0,,1]'
          this.reader.RaiseError("Empty array element");
        }
        myArrayList.add(
          this.NextJSONValue(
            c,
            nextchar,
            depth));
        c = nextchar[0];
        switch (c) {
          case ',':
            seenComma = true;
            break;
          case ']':
            return CBORObject.FromRaw(myArrayList);
          default:
            this.reader.RaiseError("Expected a ',' or ']'");
            break;
        }
      }
    }

    static void WriteJSONStringUnquoted(
      String str,
      StringOutput sb,
      JSONOptions options) throws java.io.IOException {
      boolean first = true;
      for (int i = 0; i < str.length(); ++i) {
        char c = str.charAt(i);
        if (c == '\\' || c == '"') {
          if (first) {
            first = false;
            sb.WriteString(str, 0, i);
          }
          sb.WriteCodePoint((int)'\\');
          sb.WriteCodePoint((int)c);
        } else if (c < 0x20 || (c >= 0x7f && (c == 0x2028 || c == 0x2029 ||
                    (c >= 0x7f && c <= 0xa0) || c == 0xfeff || c == 0xfffe ||
                    c == 0xffff))) {
          // Control characters, and also the line and paragraph separators
          // which apparently can't appear in JavaScript (as opposed to
          // JSON) strings
          if (first) {
            first = false;
            sb.WriteString(str, 0, i);
          }
          if (c == 0x0d) {
            sb.WriteString("\\r");
          } else if (c == 0x0a) {
            sb.WriteString("\\n");
          } else if (c == 0x08) {
            sb.WriteString("\\b");
          } else if (c == 0x0c) {
            sb.WriteString("\\f");
          } else if (c == 0x09) {
            sb.WriteString("\\t");
          } else if (c == 0x85) {
            sb.WriteString("\\u0085");
          } else if (c >= 0x100) {
            sb.WriteString("\\u");
            sb.WriteCodePoint((int)Hex16.charAt((int)((c >> 12) & 15)));
            sb.WriteCodePoint((int)Hex16.charAt((int)((c >> 8) & 15)));
            sb.WriteCodePoint((int)Hex16.charAt((int)((c >> 4) & 15)));
            sb.WriteCodePoint((int)Hex16.charAt((int)(c & 15)));
          } else {
            sb.WriteString("\\u00");
            sb.WriteCodePoint((int)Hex16.charAt((int)(c >> 4)));
            sb.WriteCodePoint((int)Hex16.charAt((int)(c & 15)));
          }
        } else {
          if ((c & 0xfc00) == 0xd800) {
            if (i >= str.length() - 1 || (str.charAt(i + 1) & 0xfc00) != 0xdc00) {
              // NOTE: RFC 8259 doesn't prohibit any particular
              // error-handling behavior when a writer of JSON
              // receives a String with an unpaired surrogate.
              if (options.getReplaceSurrogates()) {
                if (first) {
                  first = false;
                  sb.WriteString(str, 0, i);
                }
                // Replace unpaired surrogate with U+FFFD
                c = (char)0xfffd;
              } else {
                throw new CBORException("Unpaired surrogate in String");
              }
            }
          }
          if (!first) {
            if ((c & 0xfc00) == 0xd800) {
              sb.WriteString(str, i, 2);
              ++i;
            } else {
              sb.WriteCodePoint((int)c);
            }
          }
        }
      }
      if (first) {
        sb.WriteString(str);
      }
    }

    static void WriteJSONToInternal(
      CBORObject obj,
      StringOutput writer,
      JSONOptions options) throws java.io.IOException {
      if (obj.isNumber()) {
        writer.WriteString(CBORNumber.FromCBORObject(obj).ToJSONString());
        return;
      }
      switch (obj.getType()) {
        case Integer:
        case FloatingPoint: {
            CBORObject untaggedObj = obj.Untag();
            writer.WriteString(
              CBORNumber.FromCBORObject(untaggedObj).ToJSONString());
            break;
        }
        case Boolean: {
            if (obj.isTrue()) {
              writer.WriteString("true");
              return;
            }
            if (obj.isFalse()) {
              writer.WriteString("false");
              return;
            }
            return;
        }
        case SimpleValue: {
            writer.WriteString("null");
            return;
        }
        case ByteString: {
            byte[] byteArray = obj.GetByteString();
            if (byteArray.length == 0) {
              writer.WriteString("\"\"");
              return;
            }
            writer.WriteCodePoint((int)'\"');
            if (obj.HasTag(22)) {
              // Base64 with padding
              Base64.WriteBase64(
                writer,
                byteArray,
                0,
                byteArray.length,
                true);
            } else if (obj.HasTag(23)) {
              // Write as base16
              for (int i = 0; i < byteArray.length; ++i) {
                writer.WriteCodePoint((int)Hex16.charAt((byteArray[i] >> 4) & 15));
                writer.WriteCodePoint((int)Hex16.charAt(byteArray[i] & 15));
              }
            } else {
              // Base64url no padding
              Base64.WriteBase64URL(
                writer,
                byteArray,
                0,
                byteArray.length,
                false);
            }
            writer.WriteCodePoint((int)'\"');
            break;
          }
        case TextString: {
            String thisString = obj.AsString();
            if (thisString.length() == 0) {
              writer.WriteString("\"\"");
              return;
            }
            writer.WriteCodePoint((int)'\"');
            WriteJSONStringUnquoted(thisString, writer, options);
            writer.WriteCodePoint((int)'\"');
            break;
          }
        case Array: {
            boolean first = true;
            writer.WriteCodePoint((int)'[');
            for (CBORObject i : obj.AsList()) {
              if (!first) {
                writer.WriteCodePoint((int)',');
              }
              WriteJSONToInternal(i, writer, options);
              first = false;
            }
            writer.WriteCodePoint((int)']');
            break;
          }
        case Map: {
            boolean first = true;
            boolean hasNonStringKeys = false;
            Map objMap = obj.AsMap();
            for (Map.Entry entry : objMap.entrySet()) {
              CBORObject key = entry.getKey();
              if (key.getType() != CBORType.TextString ||
              key.isTagged()) {
                // treat a non-text-String item or a tagged item
                // as having non-String keys
                hasNonStringKeys = true;
                break;
              }
            }
            if (!hasNonStringKeys) {
              writer.WriteCodePoint((int)'{');
              for (Map.Entry entry : objMap.entrySet()) {
                CBORObject key = entry.getKey();
                CBORObject value = entry.getValue();
                if (!first) {
                  writer.WriteCodePoint((int)',');
                }
                writer.WriteCodePoint((int)'\"');
                WriteJSONStringUnquoted(key.AsString(), writer, options);
                writer.WriteCodePoint((int)'\"');
                writer.WriteCodePoint((int)':');
                WriteJSONToInternal(value, writer, options);
                first = false;
              }
              writer.WriteCodePoint((int)'}');
            } else {
              // This map has non-String keys
              Map stringMap = new
                HashMap();
              // Copy to a map with String keys, since
              // some keys could be duplicates
              // when serialized to strings
              for (Map.Entry entry : objMap.entrySet()) {
                CBORObject key = entry.getKey();
                CBORObject value = entry.getValue();
                String str = (key.getType() == CBORType.TextString) ?
                  key.AsString() : key.ToJSONString();
                if (stringMap.containsKey(str)) {
                  throw new
              CBORException("Duplicate JSON String equivalents of map keys");
                }
                stringMap.put(str, value);
              }
              first = true;
              writer.WriteCodePoint((int)'{');
              for (Map.Entry entry : stringMap.entrySet()) {
                String key = entry.getKey();
                CBORObject value = entry.getValue();
                if (!first) {
                  writer.WriteCodePoint((int)',');
                }
                writer.WriteCodePoint((int)'\"');
                WriteJSONStringUnquoted((String)key, writer, options);
                writer.WriteCodePoint((int)'\"');
                writer.WriteCodePoint((int)':');
                WriteJSONToInternal(value, writer, options);
                first = false;
              }
              writer.WriteCodePoint((int)'}');
            }
            break;
          }
        default: throw new IllegalStateException("Unexpected item type");
      }
    }
  }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy