public class

JSONObjWrapper

extends JSONObject
/*
 * Copyright (C) 2010 Stanford University
 *
 * 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.
 */


package edu.stanford.junction.addon;
import org.json.*;
import java.util.*;
import java.util.zip.*;
import org.jivesoftware.smack.util.Base64;
import java.io.*;

/** 
 * A wrapper for JSONObjects that provides hashCode and equals
 * methods based on the underlying JSONObject's 'id' property.
 * 
 * Useful when you want a HashSet/HashMap of JSONObjects, and 
 * each object has a unique 'id'.
 *
 * Also provides deep copy and compression routines.
 */
public class JSONObjWrapper extends JSONObject{
	

	private JSONObject self;


	public JSONObjWrapper(JSONObject obj){
		this.self = obj;
	}

	public JSONObject getRaw(){
		return self;
	}

	public int hashCode(){
		return (this.opt("id")).hashCode();
	}

	public boolean equals(Object other){
		return other.hashCode() == this.hashCode();
	}

	// Do a deep copy of this object.
	public static JSONObject copyJSONObject(JSONObject obj){
		JSONObject copy = new JSONObject();
		try{
			Iterator it = obj.keys();
			while(it.hasNext()){
				String key = (String)it.next();
				copy.put(key, copyValue(obj.get(key)));
			}
		}
		catch(JSONException e){}
		return copy;
	}

	public Object clone(){
		return new JSONObjWrapper(copyJSONObject(this));
	}

	protected static Object copyValue(Object val){
		try{
			if(val instanceof JSONObject){
				return copyJSONObject((JSONObject)val);
			}
			else if(val instanceof JSONArray){
				JSONArray newA = new JSONArray();
				JSONArray oldA = (JSONArray)val;
				for(int i = 0; i < oldA.length(); i++){
					newA.put(i, copyValue(oldA.get(i)));
				}
				return newA;
			}
			else return val;
		}
		catch(JSONException e){
			e.printStackTrace(System.err);
			return null;
		}
	}

	/** 
	 * Get the value object associated with a key.
	 *
	 * @param key   A key string.
	 * @return      The object associated with the key.
	 * @throws   JSONException if the key is not found.
	 */
	public Object get(String key) throws JSONException {
		return self.get(key);
	}
	
	
	/**
	 * Determine if the JSONObject contains a specific key.
	 * @param key   A key string.
	 * @return      true if the key exists in the JSONObject.
	 */
	public boolean has(String key) {
		return self.has(key);
	}
	
	
	/**
	 * Get an enumeration of the keys of the JSONObject.
	 *
	 * @return An iterator of the keys.
	 */
	public Iterator keys() {
		return self.keys();
	}
	
	
	/**
	 * Get the number of keys stored in the JSONObject.
	 *
	 * @return The number of keys in the JSONObject.
	 */
	public int length() {
		return self.length();
	}
	
	
	
	/**
	 * Get an optional value associated with a key.
	 * @param key   A key string.
	 * @return      An object which is the value, or null if there is no value.
	 */
	public Object opt(String key) {
		return self.opt(key);
	}
	
	
	
	/**
	 * Put a key/value pair in the JSONObject. If the value is null,
	 * then the key will be removed from the JSONObject if it is present.
	 * @param key   A key string.
	 * @param value An object which is the value. It should be of one of these
	 *  types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String,
	 *  or the JSONObject.NULL object.
	 * @return this.
	 * @throws JSONException If the value is non-finite number
	 *  or if the key is null.
	 */
	public JSONObject put(String key, Object value) throws JSONException{
		return self.put(key, value);
	}

	/**
	 * Put a key/boolean pair in the JSONObject.
	 *
	 * @param key   A key string.
	 * @param value A boolean which is the value.
	 * @return this.
	 * @throws JSONException If the key is null.
	 */
	public JSONObject put(String key, boolean value) throws JSONException {
		return self.put(key, value);
	}
 	
 	
	/**
	 * Put a key/double pair in the JSONObject.
	 *
	 * @param key   A key string.
	 * @param value A double which is the value.
	 * @return this.
	 * @throws JSONException If the key is null or if the number is invalid.
	 */
	public JSONObject put(String key, double value) throws JSONException {
		return self.put(key, value);
	}
 	
 	
	/**
	 * Put a key/int pair in the JSONObject.
	 *
	 * @param key   A key string.
	 * @param value An int which is the value.
	 * @return this.
	 * @throws JSONException If the key is null.
	 */
	public JSONObject put(String key, int value) throws JSONException {
		return self.put(key, value);
	}
 	
 	
	/**
	 * Put a key/long pair in the JSONObject.
	 *
	 * @param key   A key string.
	 * @param value A long which is the value.
	 * @return this.
	 * @throws JSONException If the key is null.
	 */
	public JSONObject put(String key, long value) throws JSONException {
		return self.put(key, value);
	}
 	
 	
	/**
	 * Remove a name and its value, if present.
	 * @param key The name to be removed.
	 * @return The value that was associated with the name,
	 * or null if there was no value.
	 */
	public Object remove(String key) {
		return self.remove(key);
	}

	public String toString() {
		return self.toString();
	}

	public String toString(int indentFactor) throws JSONException {
		return self.toString(indentFactor);
	}

	public static String compressObj(JSONObject obj){
		String str = obj.toString();
		String result = compressString(str);
		System.out.println("Compressed " + 
						   str.length() + " chars to " + 
						   result.length() + " chars.");
		return result;
	}

	public static JSONObject expandCompressedObj(String compressed){
		String json = decompressString(compressed);
		JSONObject obj = null;
		try{
			obj = new JSONObject(json);
		}
		catch(JSONException e){}
		return  obj;
	}


	private static String decompressString(String str){
		byte[] compressedData = Base64.decode(str);
		// Create the decompressor and give it the data to compress 
		Inflater decompressor = new Inflater(); 
		decompressor.setInput(compressedData); 
		// Create an expandable byte array to hold the decompressed data 
		ByteArrayOutputStream bos = new ByteArrayOutputStream(compressedData.length); 
		// Decompress the data 
		byte[] buf = new byte[1024]; 
		while (!decompressor.finished()) { 
			try { 
				int count = decompressor.inflate(buf); 
				bos.write(buf, 0, count); 
			} catch (DataFormatException e) { 
			} 
		} 
		
		try { 
			bos.close(); 
		} catch (IOException e) { } 

		// Get the decompressed data 
		byte[] decompressedData = bos.toByteArray(); 
		try{
			return new String(decompressedData, "UTF-8");
		}
		catch(UnsupportedEncodingException e){ 
			return new String(decompressedData);
		}
	}


	private static String compressString(String str){
		byte[] input;
		try{
			input = str.getBytes("UTF-8"); 
		}
		catch(UnsupportedEncodingException e){
			input = str.getBytes();
		}
		// Create the compressor with highest level of compression 
		Deflater compressor = new Deflater(); 
		compressor.setLevel(Deflater.BEST_COMPRESSION); 
		// Give the compressor the data to compress 
		compressor.setInput(input); 
		compressor.finish(); 
 
		// Create an expandable byte array to hold the compressed data. 
		// You cannot use an array that's the same size as the orginal because 
		// there is no guarantee that the compressed data will be smaller than 
		// the uncompressed data. 
		ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length); 
		// Compress the data 
		byte[] buf = new byte[1024];
		while (!compressor.finished()) { 
			int count = compressor.deflate(buf); 
			bos.write(buf, 0, count); 
		} 
		try { 
			bos.close(); 
		} 
		catch (IOException e) { } 
		// Get the compressed data 
		byte[] compressedData = bos.toByteArray(); 
		return Base64.encodeBytes(compressedData);
	}
	
}