JStylerObj.java

package net.minidev.json;

/*
 *    Copyright 2011-2024 JSON-SMART authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.io.IOException;

/**
 * protected class used to stored Internal methods
 *
 * @author Uriel Chemouni <uchemouni@gmail.com>
 */
class JStylerObj {

  public static final MPSimple MP_SIMPLE = new MPSimple();
  public static final MPTrue MP_TRUE = new MPTrue();
  public static final MPAgressive MP_AGGRESIVE = new MPAgressive();

  public static final EscapeLT ESCAPE_LT = new EscapeLT();
  public static final Escape4Web ESCAPE4Web = new Escape4Web();

  public static interface MustProtect {
    public boolean mustBeProtect(String s);
  }

  private static class MPTrue implements MustProtect {
    public boolean mustBeProtect(String s) {
      return true;
    }
  }

  private static class MPSimple implements MustProtect {
    /**
     * can a String can be store without enclosing quotes. ie: should not contain any special json
     * char
     *
     * @param s
     * @return
     */
    public boolean mustBeProtect(final String s) {
      if (s == null) return false;
      int len = s.length();
      if (len == 0) return true;
      if (s.trim() != s) return true;

      char ch = s.charAt(0);
      if (ch >= '0' && ch <= '9' || ch == '-') return true;

      for (int i = 0; i < len; i++) {
        ch = s.charAt(i);
        if (isSpace(ch)) return true;
        if (isSpecial(ch)) return true;
        if (isSpecialChar(ch)) return true;
        if (isUnicode(ch)) return true;
      }
      // keyword check
      if (isKeyword(s)) return true;
      return false;
    }
  }

  private static class MPAgressive implements MustProtect {
    public boolean mustBeProtect(final String s) {
      if (s == null) return false;
      int len = s.length();
      // protect empty String
      if (len == 0) return true;

      // protect trimable String
      if (s.trim() != s) return true;

      // json special char
      char ch = s.charAt(0);
      if (isSpecial(ch) || isUnicode(ch)) return true;

      for (int i = 1; i < len; i++) {
        ch = s.charAt(i);
        if (isSpecialClose(ch) || isUnicode(ch)) return true;
      }
      // keyWord must be protect
      if (isKeyword(s)) return true;
      // Digit like text must be protect
      ch = s.charAt(0);
      // only test String if First Ch is a digit
      if (ch >= '0' && ch <= '9' || ch == '-') {
        int p = 1;
        // skip first digits
        for (; p < len; p++) {
          ch = s.charAt(p);
          if (ch < '0' || ch > '9') break;
        }
        // int/long
        if (p == len) return true;
        // Floating point
        if (ch == '.') {
          p++;
        }
        // Skip digits
        for (; p < len; p++) {
          ch = s.charAt(p);
          if (ch < '0' || ch > '9') break;
        }
        if (p == len) return true; // can be read as an floating number
        // Double
        if (ch == 'E' || ch == 'e') {
          p++;
          if (p == len) // no power data not a digits
          return false;
          ch = s.charAt(p);
          if (ch == '+' || ch == '-') {
            p++;
            ch = s.charAt(p);
          }
        }
        if (p == len) // no power data => not a digit
        return false;

        for (; p < len; p++) {
          ch = s.charAt(p);
          if (ch < '0' || ch > '9') break;
        }
        // floating point With power of data.
        if (p == len) return true;
        return false;
      }
      return false;
    }
  }

  public static boolean isSpace(char c) {
    return (c == '\r' || c == '\n' || c == '\t' || c == ' ');
  }

  public static boolean isSpecialChar(char c) {
    return (c == '\b' || c == '\f' || c == '\n');
  }

  public static boolean isSpecialOpen(char c) {
    return (c == '{' || c == '[' || c == ',' || c == ':');
  }

  public static boolean isSpecialClose(char c) {
    return (c == '}' || c == ']' || c == ',' || c == ':');
  }

  public static boolean isSpecial(char c) {
    return (c == '{' || c == '[' || c == ',' || c == '}' || c == ']' || c == ':' || c == '\''
        || c == '"');
  }

  public static boolean isUnicode(char c) {
    // ANSI control char
    return ((c >= '\u0000' && c <= '\u001F')
        ||
        // DEL or unicode ctrl
        (c >= '\u007F' && c <= '\u009F')
        ||
        // '\u00A0' No-breakable space ?
        // En Quad .. more
        (c >= '\u2000' && c <= '\u20FF'));
  }

  public static boolean isKeyword(String s) {
    if (s.length() < 3) return false;
    char c = s.charAt(0);
    if (c == 'n') return s.equals("null");
    if (c == 't') return s.equals("true");
    if (c == 'f') return s.equals("false");
    if (c == 'N') return s.equals("NaN");
    return false;
  }

  public static interface StringProtector {
    public void escape(String s, Appendable out);
  }

  private static class EscapeLT implements StringProtector {
    /**
     * Escape special chars form String except /
     *
     * @param s - Must not be null.
     * @param out
     */
    public void escape(String s, Appendable out) {
      try {
        int len = s.length();
        for (int i = 0; i < len; i++) {
          char ch = s.charAt(i);
          switch (ch) {
            case '"':
              out.append("\\\"");
              break;
            case '\\':
              out.append("\\\\");
              break;
            case '\b':
              out.append("\\b");
              break;
            case '\f':
              out.append("\\f");
              break;
            case '\n':
              out.append("\\n");
              break;
            case '\r':
              out.append("\\r");
              break;
            case '\t':
              out.append("\\t");
              break;
            default:
              // Reference:
              // http://www.unicode.org/versions/Unicode5.1.0/
              if ((ch >= '\u0000' && ch <= '\u001F')
                  || (ch >= '\u007F' && ch <= '\u009F')
                  || (ch >= '\u2000' && ch <= '\u20FF')) {
                out.append("\\u");
                String hex = "0123456789ABCDEF";
                out.append(hex.charAt(ch >> 12 & 0x000F));
                out.append(hex.charAt(ch >> 8 & 0x000F));
                out.append(hex.charAt(ch >> 4 & 0x000F));
                out.append(hex.charAt(ch >> 0 & 0x000F));
              } else {
                out.append(ch);
              }
          }
        }
      } catch (IOException e) {
        throw new RuntimeException("Impossible Exception");
      }
    }
  }

  private static class Escape4Web implements StringProtector {

    /**
     * Escape special chars form String including /
     *
     * @param s - Must not be null.
     * @param sb
     */
    public void escape(String s, Appendable sb) {
      try {
        int len = s.length();
        for (int i = 0; i < len; i++) {
          char ch = s.charAt(i);
          switch (ch) {
            case '"':
              sb.append("\\\"");
              break;
            case '\\':
              sb.append("\\\\");
              break;
            case '\b':
              sb.append("\\b");
              break;
            case '\f':
              sb.append("\\f");
              break;
            case '\n':
              sb.append("\\n");
              break;
            case '\r':
              sb.append("\\r");
              break;
            case '\t':
              sb.append("\\t");
              break;
            case '/':
              sb.append("\\/");
              break;
            default:
              // Reference:
              // http://www.unicode.org/versions/Unicode5.1.0/
              if ((ch >= '\u0000' && ch <= '\u001F')
                  || (ch >= '\u007F' && ch <= '\u009F')
                  || (ch >= '\u2000' && ch <= '\u20FF')) {
                sb.append("\\u");
                String hex = "0123456789ABCDEF";
                sb.append(hex.charAt(ch >> 12 & 0x0F));
                sb.append(hex.charAt(ch >> 8 & 0x0F));
                sb.append(hex.charAt(ch >> 4 & 0x0F));
                sb.append(hex.charAt(ch >> 0 & 0x0F));
              } else {
                sb.append(ch);
              }
          }
        }
      } catch (IOException e) {
        throw new RuntimeException("Impossible Error");
      }
    }
  }
}