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

com.hfg.javascript.JsObjMap Maven / Gradle / Ivy

There is a newer version: 20240423
Show newest version
package com.hfg.javascript;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.hfg.util.CompareUtil;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.util.collection.OrderedMap;
import com.hfg.util.StringUtil;
import com.hfg.xml.XMLException;

//------------------------------------------------------------------------------
/**
 A Javascript object (Map) container useful for creating JSON.
 
JSON.org
JSON RFC: http://www.ietf.org/rfc/rfc4627.txt
@author J. Alex Taylor, hairyfatguy.com */ //------------------------------------------------------------------------------ // com.hfg Library // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com // [email protected] //------------------------------------------------------------------------------ public class JsObjMap extends OrderedMap implements JsCollection, Comparable { private Set mUnquotedValueKeys; private static final Pattern KEY_PATTERN = Pattern.compile("(.+?)\\s*:"); private static final Pattern UNQUOTED_VALUE_PATTERN = Pattern.compile("(.+?)\\s*[,}]"); //########################################################################### // CONSTRUCTORS //########################################################################### //-------------------------------------------------------------------------- public JsObjMap() { } //-------------------------------------------------------------------------- public JsObjMap(int inInitialCapacity) { super(inInitialCapacity); } //-------------------------------------------------------------------------- public JsObjMap(int inInitialCapacity, float inLoadFactor) { super(inInitialCapacity, inLoadFactor); } //-------------------------------------------------------------------------- public JsObjMap(CharSequence inJSONString) { if (StringUtil.isSet(inJSONString)) { parse(inJSONString); } } //-------------------------------------------------------------------------- public JsObjMap(Map inMap) { if (CollectionUtil.hasValues(inMap)) { for (Entry entry : inMap.entrySet()) { put(entry.getKey(), entry.getValue()); } } } //########################################################################### // PUBLIC METHODS //########################################################################### //-------------------------------------------------------------------------- @Override public String toString() { return toJavascript(); } //-------------------------------------------------------------------------- public String toJSON() { ByteArrayOutputStream outStream; try { outStream = new ByteArrayOutputStream(2048); toJSON(outStream); outStream.close(); } catch (Exception e) { throw new XMLException(e); } return outStream.toString(); } //--------------------------------------------------------------------------- public void toJSON(OutputStream inStream) { // JSON should be encoded with UTF-8. PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(inStream, StandardCharsets.UTF_8)), true); toJSON(writer); writer.flush(); } //-------------------------------------------------------------------------- public void toJSON(Writer inWriter) { try { inWriter.write("{ "); int i = 0; for (String key : keySet()) { if (i > 0) { inWriter.write(", "); } inWriter.write("\""); inWriter.write(JSONUtil.escapeString(key)); inWriter.write("\": "); if (mUnquotedValueKeys != null && mUnquotedValueKeys.contains(key)) { inWriter.write(get(key).toString()); } else { inWriter.write(composeValueJavascript(get(key))); } i++; } inWriter.write(" }"); } catch (IOException e) { throw new RuntimeException(e); } } //-------------------------------------------------------------------------- /** Produces a javascript serialization that will generally be the same as produced by toJSON() except when values have been added via the putUnquoted() method. */ public String toJavascript() { ByteArrayOutputStream outStream; try { outStream = new ByteArrayOutputStream(2048); toJavascript(outStream); outStream.close(); } catch (Exception e) { throw new XMLException(e); } return outStream.toString(); } //--------------------------------------------------------------------------- /** Produces a javascript serialization that will generally be the same as produced by toJSON() except when values have been added via the putUnquoted() method. */ public void toJavascript(OutputStream inStream) { PrintWriter writer = new PrintWriter(inStream); toJavascript(writer); writer.flush(); } //-------------------------------------------------------------------------- /** Produces a javascript serialization that will generally be the same as produced by toJSON() except when values have been added via the putUnquoted() method. */ public void toJavascript(Writer inWriter) { try { inWriter.write("{ "); int i = 0; for (String key : keySet()) { if (i > 0) { inWriter.write(", "); } inWriter.write("\""); inWriter.write(JSONUtil.escapeString(key)); inWriter.write("\": "); if (mUnquotedValueKeys != null && mUnquotedValueKeys.contains(key)) { inWriter.write(get(key).toString()); } else { inWriter.write(composeValueJavascript(get(key))); } i++; } inWriter.write(" }"); } catch (IOException e) { throw new RuntimeException(e); } } //-------------------------------------------------------------------------- @Override public boolean equals(Object inObj2) { return (0 == compareTo(inObj2)); } //-------------------------------------------------------------------------- @Override public int compareTo(Object inObj2) { int result = -1; if (inObj2 instanceof JsObjMap) { JsObjMap jsObj2 = (JsObjMap) inObj2; result = CompareUtil.compare(this, jsObj2); } return result; } //-------------------------------------------------------------------------- public String getString(String inKey) { Object value = get(inKey); return (value != null ? value.toString() : null); } //-------------------------------------------------------------------------- /** Puts the specified key / value pair into the map but does not quote the value when calling toJavascript() [Note that not quoting a string value means that the resulting serialization will no longer be valid JSON, but sending function definitions in a JSON-like format can be useful when working with javascript frameworks]. Numeric and boolean values should not be passed to this method as they will be handle correctly by the standard put() method. */ public void putUnquoted(String inKey, Object inValue) { put(inKey, inValue); if (null == mUnquotedValueKeys) { mUnquotedValueKeys = new HashSet<>(5); } mUnquotedValueKeys.add(inKey); } //-------------------------------------------------------------------------- // If the values comes in too generically, we may need to dispatch to one of our specific put() methods. public Object put(String inKey, Object inValue) { if (null == inKey) { throw new RuntimeException("A non-null value must be specified as the key!"); } Object result; if (inValue != null) { if (inValue instanceof JsArray) { result = put(inKey, (JsArray) inValue); } else if (inValue instanceof Collection) { result = put(inKey, (Collection) inValue); } else if (inValue instanceof Object[]) { result = put(inKey, (Object[]) inValue); } else { result = super.put(inKey, inValue); } } else { result = super.put(inKey, inValue); } return result; } //-------------------------------------------------------------------------- public Object put(String inKey, JsArray inValue) { if (null == inKey) { throw new RuntimeException("A non-null value must be specified as the key!"); } return super.put(inKey, inValue); } //-------------------------------------------------------------------------- public Object put(String inKey, Collection inValues) { if (null == inKey) { throw new RuntimeException("A non-null value must be specified as the key!"); } return super.put(inKey, inValues != null ? new JsArray(inValues) : null); } //-------------------------------------------------------------------------- public Object put(String inKey, Object[] inValues) { if (null == inKey) { throw new RuntimeException("A non-null value must be specified as the key!"); } return super.put(inKey, inValues != null ? new JsArray(inValues) : null); } //-------------------------------------------------------------------------- @Override public Object remove(Object inKey) { if (mUnquotedValueKeys != null) { mUnquotedValueKeys.remove(inKey); } return super.remove(inKey); } //########################################################################### // PRIVATE METHODS //########################################################################### //-------------------------------------------------------------------------- private String composeValueJSON(Object inValue) { String value = "null"; if (inValue != null) { if (inValue instanceof Number || inValue instanceof Boolean) { // Numeric and boolean values are written without quotes value = inValue.toString(); } else if (inValue instanceof JsArray) { value = ((JsArray) inValue).toJSON(); } else if (inValue instanceof JsObjMap) { value = ((JsObjMap) inValue).toJSON(); } else { // String values should be enclosed in double quotes value = "\"" + JSONUtil.escapeString(inValue.toString()) + "\""; } } return value; } //-------------------------------------------------------------------------- private String composeValueJavascript(Object inValue) { String value = "null"; if (inValue != null) { if (inValue instanceof Number || inValue instanceof Boolean) { value = inValue.toString(); } else if (inValue instanceof JsArray) { value = ((JsArray) inValue).toJavascript(); } else if (inValue instanceof JsObjMap) { value = ((JsObjMap) inValue).toJavascript(); } else { value = "\"" + JSONUtil.escapeString(inValue.toString()) + "\""; } } return value; } //-------------------------------------------------------------------------- private void parse(CharSequence inString) { String str = inString.toString().trim(); if (str.charAt(0) != '{' || str.charAt(str.length() - 1) != '}') { throw new RuntimeException("JSON map must be enclosed with {}!"); } int index = 1; String key = null; while (index < str.length() - 1) { char theChar = str.charAt(index); if (Character.isWhitespace(theChar)) { index++; } else if (theChar == ',') { index++; } else if (theChar == '[') { // Find the ending bracket if (null == key) { throw new RuntimeException("Poorly structured JSON! Array at index " + index + " in map does not have a key!"); } int depth = 1; char quote = ' '; boolean inEscape = false; boolean inValue = false; int end = index + 1; while (end < str.length() -1 && (depth != 1 || inValue || str.charAt(end) != ']')) { char theEndChar = str.charAt(end); if (theEndChar == '\\' && inValue) { inEscape = ! inEscape; } else if (inEscape) { inEscape = false; } if (!inEscape) { if (theEndChar == '\'' && str.charAt(end - 1) != '\\') { if (!inValue) { inValue = true; quote = '\''; } else if (quote == '\'') { inValue = false; } } else if (theEndChar == '"' && str.charAt(end - 1) != '\\') { if (!inValue) { inValue = true; quote = '"'; } else if (quote == '"') { inValue = false; } } else if (!inValue && theEndChar == '[') { depth++; } else if (!inValue && theEndChar == ']') { depth--; } } end++; } JsArray value = new JsArray(str.substring(index, end + 1)); put(key, value); key = null; index = end + 1; } else if (theChar == '{') { if (null == key) { throw new RuntimeException("Poorly structured JSON! Submap at index " + index + " in map does not have a key!"); } // Find the ending brace int depth = 1; char quote = ' '; boolean isInEscape = false; boolean isInValue = false; int end = index + 1; while (end < str.length() -1 && (depth != 1 || isInValue || str.charAt(end) != '}')) { char theEndChar = str.charAt(end); if (theEndChar == '\\' && isInValue) { isInEscape = ! isInEscape; } else if (isInEscape) { isInEscape = false; } if (isInEscape) { continue; } if (theEndChar == '\'' && (str.charAt(end -1) != '\\' || ! isInEscape)) { if (! isInValue) { isInValue = true; quote = '\''; } else if (quote == '\'') { isInValue = false; } } else if (theEndChar == '"' && (str.charAt(end -1) != '\\' || ! isInEscape)) { if (! isInValue) { isInValue = true; quote = '"'; } else if (quote == '"') { isInValue = false; } } else if (! isInValue && theEndChar == '{') { depth++; } else if (! isInValue && theEndChar == '}') { depth--; } end++; } JsObjMap value = new JsObjMap(str.substring(index, end + 1)); put(key, value); key = null; index = end + 1; } else if (key != null) { if (theChar == '"') { boolean inEscape = false; int valueStartIndex = index; StringBuilder valueBuffer = new StringBuilder(); // Continue until we find the next unescaped quote. while ((index++) < str.length() - 1 && ((theChar = str.charAt(index)) != '"') || inEscape) { if (theChar == '\\') { inEscape = !inEscape; } else if (inEscape) { inEscape = false; } // if (! inEscape) { valueBuffer.append(theChar); } } if (index == str.length() - 1) { throw new RuntimeException("Problem parsing value @ position " + valueStartIndex + " in JSON string!"); } index++; // consume the trailing quote put(key, JSONUtil.unescapeString(valueBuffer.toString())); // put(key, valueBuffer.toString()); } else { Matcher m = UNQUOTED_VALUE_PATTERN.matcher(str); if (m.find(index)) { String stringValue = JSONUtil.unescapeString(m.group(1)); index = m.end() - 1; put(key, JSONUtil.convertStringValueToObject(stringValue)); } } key = null; } else { // Parse the key name // Is it in quotes? if (theChar == '"') { boolean inEscape = false; int valueStartIndex = index; StringBuilder valueBuffer = new StringBuilder(); // Continue until we find the next unescaped quote. while ((index++) < str.length() - 1 && ((theChar = str.charAt(index)) != '"') || inEscape) { if (theChar == '\\') { inEscape = !inEscape; } else if (inEscape) { inEscape = false; } // if (! inEscape) { valueBuffer.append(theChar); } } if (index == str.length() - 1) { throw new RuntimeException("Problem parsing value @ position " + valueStartIndex + " in JSON string!"); } index++; // consume the trailing quote key = JSONUtil.unescapeString(valueBuffer.toString()); while (index < str.length() - 1 && str.charAt(index++) != ':') { } if (str.charAt(index - 1) != ':') { throw new RuntimeException("Problem with format of hash string: " + StringUtil.singleQuote(inString) + "!"); } } else { Matcher m = KEY_PATTERN.matcher(str); if (m.find(index)) { key = JSONUtil.unescapeString(StringUtil.unquote(m.group(1))); // key = StringUtil.unquote(m.group(1)); index = m.end(); } else { throw new RuntimeException("Problem with format of hash string: " + StringUtil.singleQuote(inString) + "!"); } } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy