/*
 * Copyright 2017-2019 The OpenTracing 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.
 */
package io.opentracing.contrib.jdbc.parser;

import io.opentracing.contrib.jdbc.ConnectionInfo;

public class H2URLParser extends AbstractURLParser {

  private static final String LOCALHOST = "localhost";
  private static final int DEFAULT_PORT = 8084;
  /**
   * Flag that H2 running with file mode.
   */
  private static final String FILE_MODE_FLAG = "file";
  /**
   * Flag that H2 running with memory mode.
   */
  private static final String MEMORY_MODE_FLAG = "mem";
  private static final String H2_DB_TYPE = "h2";

  @Override
  protected URLLocation fetchDatabaseHostsIndexRange(String url) {
    int hostLabelStartIndex = url.indexOf("//");
    int hostLabelEndIndex = url.indexOf("/", hostLabelStartIndex + 2);
    return new URLLocation(hostLabelStartIndex + 2, hostLabelEndIndex);
  }

  @Override
  protected URLLocation fetchDatabaseNameIndexRange(String url) {
    int databaseStartTag = url.lastIndexOf("/");
    int databaseEndTag = url.indexOf(";");
    if (databaseEndTag == -1) {
      databaseEndTag = url.length();
    }
    return new URLLocation(databaseStartTag + 1, databaseEndTag);
  }

  @Override
  public ConnectionInfo parse(String url) {
    int[] databaseNameRangeIndex = fetchDatabaseNameRangeIndexFromURLForH2FileMode(url);
    if (databaseNameRangeIndex != null) {
      return new ConnectionInfo.Builder(LOCALHOST, -1).dbType(H2_DB_TYPE)
          .dbInstance(fetchDatabaseNameFromURL(url, databaseNameRangeIndex)).build();
    }

    databaseNameRangeIndex = fetchDatabaseNameRangeIndexFromURLForH2MemMode(url);
    if (databaseNameRangeIndex != null) {
      return new ConnectionInfo.Builder(LOCALHOST, -1).dbType(H2_DB_TYPE)
          .dbInstance(fetchDatabaseNameFromURL(url, databaseNameRangeIndex)).build();
    }

    String[] hostAndPort = fetchDatabaseHostsFromURL(url).split(":");
    if (hostAndPort.length == 1) {
      return new ConnectionInfo.Builder(hostAndPort[0], DEFAULT_PORT).dbType(H2_DB_TYPE)
          .dbInstance(fetchDatabaseNameFromURL(url)).build();
    } else {
      return new ConnectionInfo.Builder(hostAndPort[0], Integer.valueOf(hostAndPort[1]))
          .dbType(H2_DB_TYPE).dbInstance(fetchDatabaseNameFromURL(url)).build();
    }
  }

  /**
   * Fetch range index that the database name from connection url if H2 database running with file
   * mode.
   *
   * @return range index that the database name.
   */
  private int[] fetchDatabaseNameRangeIndexFromURLForH2FileMode(String url) {
    int fileLabelIndex = url.indexOf(FILE_MODE_FLAG);
    int parameterLabelIndex = url.indexOf(";", fileLabelIndex);
    if (parameterLabelIndex == -1) {
      parameterLabelIndex = url.length();
    }

    if (fileLabelIndex != -1) {
      return new int[]{fileLabelIndex + FILE_MODE_FLAG.length() + 1, parameterLabelIndex};
    } else {
      return null;
    }
  }

  /**
   * Fetch range index that the database name from connection url if H2 database running with memory
   * mode.
   *
   * @return range index that the database name.
   */
  private int[] fetchDatabaseNameRangeIndexFromURLForH2MemMode(String url) {
    int fileLabelIndex = url.indexOf(MEMORY_MODE_FLAG);
    int parameterLabelIndex = url.indexOf(";", fileLabelIndex);
    if (parameterLabelIndex == -1) {
      parameterLabelIndex = url.length();
    }

    if (fileLabelIndex != -1) {
      return new int[]{fileLabelIndex + MEMORY_MODE_FLAG.length() + 1, parameterLabelIndex};
    } else {
      return null;
    }
  }
}


/*
 * Copyright 2017-2019 The OpenTracing 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.
 */
package io.opentracing.contrib.jdbc.parser;

import io.opentracing.contrib.jdbc.ConnectionInfo;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public class URLParser {
  private static final Logger log = Logger.getLogger(URLParser.class.getName());

  private static final String MYSQL_JDBC_URL_PREFIX = "jdbc:mysql";
  private static final String ORACLE_JDBC_URL_PREFIX = "jdbc:oracle";
  private static final String H2_JDBC_URL_PREFIX = "jdbc:h2";
  private static final String POSTGRESQL_JDBC_URL_PREFIX = "jdbc:postgresql";
  private static final Map<String, ConnectionURLParser> parserRegister = new LinkedHashMap<String, ConnectionURLParser>();

  static {
    // put mysql parser firstly
    parserRegister.put(MYSQL_JDBC_URL_PREFIX, new MysqlURLParser());
    parserRegister.put(ORACLE_JDBC_URL_PREFIX, new OracleURLParser());
    parserRegister.put(H2_JDBC_URL_PREFIX, new H2URLParser());
    parserRegister.put(POSTGRESQL_JDBC_URL_PREFIX, new PostgreSQLURLParser());
  }

  /**
   * parse the url to the ConnectionInfo
   */
  public static ConnectionInfo parser(String url) {
    if (null == url) {
      return ConnectionInfo.UNKNOWN_CONNECTION_INFO;
    }
    String lowerCaseUrl = url.toLowerCase();
    ConnectionURLParser parser = findURLParser(lowerCaseUrl);
    if (parser == null) {
      return ConnectionInfo.UNKNOWN_CONNECTION_INFO;
    }
    try {
      return parser.parse(url);
    } catch (Exception e) {
      log.log(Level.WARNING, "error occurs when paring jdbc url");
    }
    return ConnectionInfo.UNKNOWN_CONNECTION_INFO;
  }

  private static ConnectionURLParser findURLParser(String lowerCaseUrl) {
    for (Map.Entry<String, ConnectionURLParser> entry : parserRegister.entrySet()) {
      if (lowerCaseUrl.startsWith(entry.getKey())) {
        return entry.getValue();
      }
    }
    return null;
  }

  /**
   * register new ConnectionURLParser. Can override existing parser.
   */
  public static void registerConnectionParser(String urlPrefix, ConnectionURLParser parser) {
    if (null == urlPrefix || parser == null) {
      throw new IllegalArgumentException("urlPrefix and parser can not be null");
    }
    parserRegister.put(urlPrefix.toLowerCase(), parser);
  }
}


/*
 * Copyright 2017-2019 The OpenTracing 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.
 */
package io.opentracing.contrib.jdbc.parser;

public abstract class AbstractURLParser implements ConnectionURLParser {

  /**
   * Fetch the index range that database host and port from connection url.
   *
   * @return index range that database hosts.
   */
  protected abstract URLLocation fetchDatabaseHostsIndexRange(final String url);

  /**
   * Fetch the index range that database name from connection url.
   *
   * @return index range that database name.
   */
  protected abstract URLLocation fetchDatabaseNameIndexRange(final String url);

  /**
   * Fetch database host(s) from connection url.
   *
   * @return database host(s).
   */
  protected String fetchDatabaseHostsFromURL(String url) {
    URLLocation hostsLocation = fetchDatabaseHostsIndexRange(url);
    return url.substring(hostsLocation.startIndex(), hostsLocation.endIndex());
  }

  /**
   * Fetch database name from connection url.
   *
   * @return database name.
   */
  protected String fetchDatabaseNameFromURL(String url) {
    URLLocation hostsLocation = fetchDatabaseNameIndexRange(url);
    return url.substring(hostsLocation.startIndex(), hostsLocation.endIndex());
  }

  /**
   * Fetch database name from connection url.
   *
   * @return database name.
   */
  protected String fetchDatabaseNameFromURL(String url, int[] indexRange) {
    return url.substring(indexRange[0], indexRange[1]);
  }

}


package com.coolerfall.daemon;

import android.content.Context;
import android.util.Log;

import java.io.File;
import java.io.IOException;

/**
 * Daemon: service daemon.
 *
 * @author Vincent Cheung
 * @since Jan. 19, 2015
 */
public class Daemon {
	private static final String TAG = Daemon.class.getSimpleName();

	private static final String BIN_DIR_NAME = "bin";
	private static final String DAEMON_BIN_NAME = "daemon";

	public static final int INTERVAL_ONE_MINUTE = 60;
	public static final int INTERVAL_ONE_HOUR = 3600;

	/**
	 * start daemon
	 */
	private static void start(Context context, Class<?> daemonClazzName, int interval) {
		String cmd = context.getDir(BIN_DIR_NAME, Context.MODE_PRIVATE)
			.getAbsolutePath() + File.separator + DAEMON_BIN_NAME;

		/* create the command string */
		StringBuilder cmdBuilder = new StringBuilder();
		cmdBuilder.append(cmd);
		cmdBuilder.append(" -p ");
		cmdBuilder.append(context.getPackageName());
		cmdBuilder.append(" -s ");
		cmdBuilder.append(daemonClazzName.getName());
		cmdBuilder.append(" -t ");
		cmdBuilder.append(interval);

		try {
			Runtime.getRuntime().exec(cmdBuilder.toString()).waitFor();
		} catch (IOException | InterruptedException e) {
			Log.e(TAG, "start daemon error: " + e.getMessage());
		}
	}

	/**
	 * Run daemon process.
	 *
	 * @param context            context
	 * @param daemonServiceClazz the name of daemon service class
	 * @param interval           the interval to check
	 */
	public static void run(final Context context, final Class<?> daemonServiceClazz,
	                       final int interval) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				Command.install(context, BIN_DIR_NAME, DAEMON_BIN_NAME);
				start(context, daemonServiceClazz, interval);
			}
		}).start();
	}
}


package com.coolerfall.daemon;

import android.content.Context;
import android.content.res.AssetManager;
import android.os.Build;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Some command here.
 *
 * @author Vincent Cheung
 * @since Jan. 22, 2015
 */
public class Command {
	private static final String TAG = Command.class.getSimpleName();

	/**
	 * copy file to destination
	 */
	private static void copyFile(File file, InputStream is, String mode)
		throws IOException, InterruptedException {
		final String abspath = file.getAbsolutePath();
		final FileOutputStream out = new FileOutputStream(file);
		byte buf[] = new byte[1024];
		int len;
		while ((len = is.read(buf)) > 0) {
			out.write(buf, 0, len);
		}

		out.close();
		is.close();

		Runtime.getRuntime().exec("chmod " + mode + " " + abspath).waitFor();
	}

	/**
	 * copy file in assets into destination file
	 *
	 * @param context        context
	 * @param assetsFilename file name in assets
	 * @param file           the file to copy to
	 * @param mode           mode of file
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public static void copyAssets(Context context, String assetsFilename, File file, String mode)
		throws IOException, InterruptedException {
		AssetManager manager = context.getAssets();
		final InputStream is = manager.open(assetsFilename);
		copyFile(file, is, mode);
	}

	/**
	 * Install specified binary into destination directory.
	 *
	 * @param context  context
	 * @param destDir  destionation directory
	 * @param filename filename of binary
	 * @return true if install successfully, otherwise return false
	 */
	@SuppressWarnings("deprecation")
	public static boolean install(Context context, String destDir, String filename) {
		String binaryDir = "armeabi";

		String abi = Build.CPU_ABI;
		if (abi.startsWith("armeabi-v7a")) {
			binaryDir = "armeabi-v7a";
		} else if (abi.startsWith("x86")) {
			binaryDir = "x86";
		}

		/* for different platform */
		String assetfilename = binaryDir + File.separator + filename;

		try {
			File f = new File(context.getDir(destDir, Context.MODE_PRIVATE), filename);
			if (f.exists()) {
				Log.d(TAG, "binary has existed");
				return false;
			}

			copyAssets(context, assetfilename, f, "0755");
			return true;
		} catch (Exception e) {
			Log.e(TAG, "installBinary failed: " + e.getMessage());
			return false;
		}
	}
}


package com.cedarsoftware.util.io;

import com.cedarsoftware.util.io.JsonReader.MissingFieldHandler;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

/**
 * This class is used to convert a source of Java Maps that were created from
 * the JsonParser.  These are in 'raw' form with no 'pointers'.  This code will
 * reconstruct the 'shape' of the graph by connecting @ref's to @ids.
 *
 * The subclasses that override this class can build an object graph using Java
 * classes or a Map-of-Map representation.  In both cases, the @ref value will
 * be replaced with the Object (or Map) that had the corresponding @id.
 *
 * @author John DeRegnaucourt (jdereg@gmail.com)
 *

 *         Copyright (c) Cedar Software LLC
 *

 *         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.*
 */
abstract class Resolver
{
    final Collection<UnresolvedReference> unresolvedRefs = new ArrayList<UnresolvedReference>();
    protected final JsonReader reader;
    private static final NullClass nullReader = new NullClass();
    final Map<Class, JsonReader.JsonClassReaderBase> readerCache = new HashMap<Class, JsonReader.JsonClassReaderBase>();
    private final Collection<Object[]> prettyMaps = new ArrayList<Object[]>();
    private final boolean useMaps;
    private final Object unknownClass;
    private final boolean failOnUnknownType;
    private final static Map<String, Class> coercedTypes = new LinkedHashMap<String, Class>();
    // store the missing field found during deserialization to notify any client after the complete resolution is done
    protected final Collection<Missingfields> missingFields = new ArrayList<Resolver.Missingfields>();

    static {
        coercedTypes.put("java.util.Arrays$ArrayList", ArrayList.class);
        coercedTypes.put("java.util.LinkedHashMap$LinkedKeySet", LinkedHashSet.class);
        coercedTypes.put("java.util.LinkedHashMap$LinkedValues", ArrayList.class);
        coercedTypes.put("java.util.HashMap$KeySet", HashSet.class);
        coercedTypes.put("java.util.HashMap$Values", ArrayList.class);
        coercedTypes.put("java.util.TreeMap$KeySet", TreeSet.class);
        coercedTypes.put("java.util.TreeMap$Values", ArrayList.class);
        coercedTypes.put("java.util.concurrent.ConcurrentHashMap$KeySet", LinkedHashSet.class);
        coercedTypes.put("java.util.concurrent.ConcurrentHashMap$KeySetView", LinkedHashSet.class);
        coercedTypes.put("java.util.concurrent.ConcurrentHashMap$Values", ArrayList.class);
        coercedTypes.put("java.util.concurrent.ConcurrentHashMap$ValuesView", ArrayList.class);
        coercedTypes.put("java.util.concurrent.ConcurrentSkipListMap$KeySet", LinkedHashSet.class);
        coercedTypes.put("java.util.concurrent.ConcurrentSkipListMap$Values", ArrayList.class);
        coercedTypes.put("java.util.IdentityHashMap$KeySet", LinkedHashSet.class);
        coercedTypes.put("java.util.IdentityHashMap$Values", ArrayList.class);
    }

    /**
     * UnresolvedReference is created to hold a logical pointer to a reference that
     * could not yet be loaded, as the @ref appears ahead of the referenced object's
     * definition.  This can point to a field reference or an array/Collection element reference.
     */
    static final class UnresolvedReference
    {
        private final JsonObject referencingObj;
        private String field;
        private final long refId;
        private int index = -1;

        UnresolvedReference(JsonObject referrer, String fld, long id)
        {
            referencingObj = referrer;
            field = fld;
            refId = id;
        }

        UnresolvedReference(JsonObject referrer, int idx, long id)
        {
            referencingObj = referrer;
            index = idx;
            refId = id;
        }
    }

    /**
     * stores missing fields information to notify client after the complete deserialization resolution
     */
    protected static class Missingfields
    {
        private Object target;
        private String fieldName;
        private Object value;

        public Missingfields(Object target, String fieldName, Object value)
        {
            this.target = target;
            this.fieldName = fieldName;
            this.value = value;
        }
    }

    /**
     * Dummy place-holder class exists only because ConcurrentHashMap cannot contain a
     * null value.  Instead, singleton instance of this class is placed where null values
     * are needed.
     */
    private static final class NullClass implements JsonReader.JsonClassReaderBase  { }

    protected Resolver(JsonReader reader)
    {
        this.reader = reader;
        Map<String, Object> optionalArgs = reader.getArgs();
        optionalArgs.put(JsonReader.OBJECT_RESOLVER, this);
        useMaps = Boolean.TRUE.equals(optionalArgs.get(JsonReader.USE_MAPS));
        unknownClass = optionalArgs.containsKey(JsonReader.UNKNOWN_OBJECT) ? optionalArgs.get(JsonReader.UNKNOWN_OBJECT) : null;
        failOnUnknownType = Boolean.TRUE.equals(optionalArgs.get(JsonReader.FAIL_ON_UNKNOWN_TYPE));
    }

    protected JsonReader getReader()
    {
        return reader;
    }

    /**
     * Walk a JsonObject (Map of String keys to values) and return the
     * Java object equivalent filled in as best as possible (everything
     * except unresolved reference fields or unresolved array/collection elements).
     *
     * @param root JsonObject reference to a Map-of-Maps representation of the JSON
     *             input after it has been completely read.
     * @return Properly constructed, typed, Java object graph built from a Map
     * of Maps representation (JsonObject root).
     */
    protected Object convertMapsToObjects(final JsonObject<String, Object> root)
    {
        final Deque<JsonObject<String, Object>> stack = new ArrayDeque<JsonObject<String, Object>>();
        stack.addFirst(root);

        while (!stack.isEmpty())
        {
            final JsonObject<String, Object> jsonObj = stack.removeFirst();

            if (jsonObj.isArray())
            {
                traverseArray(stack, jsonObj);
            }
            else if (jsonObj.isCollection())
            {
                traverseCollection(stack, jsonObj);
            }
            else if (jsonObj.isMap())
            {
                traverseMap(stack, jsonObj);
            }
            else
            {
                Object special;
                if ((special = readIfMatching(jsonObj, null, stack)) != null)
                {
                    jsonObj.target = special;
                }
                else
                {
                    traverseFields(stack, jsonObj);
                }
            }
        }
        return root.target;
    }

    protected abstract Object readIfMatching(final Object o, final Class compType, final Deque<JsonObject<String, Object>> stack);

    public abstract void traverseFields(Deque<JsonObject<String, Object>> stack, JsonObject<String, Object> jsonObj);

    protected abstract void traverseCollection(Deque<JsonObject<String, Object>> stack, JsonObject<String, Object> jsonObj);

    protected abstract void traverseArray(Deque<JsonObject<String, Object>> stack, JsonObject<String, Object> jsonObj);

    protected void cleanup()
    {
        patchUnresolvedReferences();
        rehashMaps();
        reader.getObjectsRead().clear();
        unresolvedRefs.clear();
        prettyMaps.clear();
        readerCache.clear();
        handleMissingFields();
    }

    // calls the missing field handler if any for each recorded missing field.
    private void handleMissingFields()
    {
        MissingFieldHandler missingFieldHandler = reader.getMissingFieldHandler();
        if (missingFieldHandler != null)
        {
            for (Missingfields mf : missingFields)
            {
                missingFieldHandler.fieldMissing(mf.target, mf.fieldName, mf.value);
            }
        }//else no handler so ignore.
    }

    /**
     * Process java.util.Map and it's derivatives.  These can be written specially
     * so that the serialization would not expose the derivative class internals
     * (internal fields of TreeMap for example).
     *
     * @param stack   a Stack (Deque) used to support graph traversal.
     * @param jsonObj a Map-of-Map representation of the JSON input stream.
     */
    protected void traverseMap(Deque<JsonObject<String, Object>> stack, JsonObject<String, Object> jsonObj)
    {
        // Convert @keys to a Collection of Java objects.
        convertMapToKeysItems(jsonObj);
        final Object[] keys = (Object[]) jsonObj.get("@keys");
        final Object[] items = jsonObj.getArray();

        if (keys == null || items == null)
        {
            if (keys != items)
            {
                throw new JsonIoException("Map written where one of @keys or @items is empty");
            }
            return;
        }

        final int size = keys.length;
        if (size != items.length)
        {
            throw new JsonIoException("Map written with @keys and @items entries of different sizes");
        }

        Object[] mapKeys = buildCollection(stack, keys, size);
        Object[] mapValues = buildCollection(stack, items, size);

        // Save these for later so that unresolved references inside keys or values
        // get patched first, and then build the Maps.
        prettyMaps.add(new Object[]{jsonObj, mapKeys, mapValues});
    }

    private static Object[] buildCollection(Deque<JsonObject<String, Object>> stack, Object[] items, int size)
    {
        final JsonObject<String, Object> jsonCollection = new JsonObject<String, Object>();
        jsonCollection.put("@items", items);
        final Object[] javaKeys = new Object[size];
        jsonCollection.target = javaKeys;
        stack.addFirst(jsonCollection);
        return javaKeys;
    }

    /**
     * Convert an input JsonObject map (known to represent a Map.class or derivative) that has regular keys and values
     * to have its keys placed into @keys, and its values placed into @items.
     *
     * @param map Map to convert
     */
    protected static void convertMapToKeysItems(final JsonObject<String, Object> map)
    {
        if (!map.containsKey("@keys") && !map.isReference())
        {
            final Object[] keys = new Object[map.size()];
            final Object[] values = new Object[map.size()];
            int i = 0;

            for (Object e : map.entrySet())
            {
                final Map.Entry entry = (Map.Entry) e;
                keys[i] = entry.getKey();
                values[i] = entry.getValue();
                i++;
            }
            String saveType = map.getType();
            map.clear();
            map.setType(saveType);
            map.put("@keys", keys);
            map.put("@items", values);
        }
    }

    /**
     * This method creates a Java Object instance based on the passed in parameters.
     * If the JsonObject contains a key '@type' then that is used, as the type was explicitly
     * set in the JSON stream.  If the key '@type' does not exist, then the passed in Class
     * is used to create the instance, handling creating an Array or regular Object
     * instance.
     *

     * The '@type' is not often specified in the JSON input stream, as in many
     * cases it can be inferred from a field reference or array component type.
     *
     * @param clazz   Instance will be create of this class.
     * @param jsonObj Map-of-Map representation of object to create.
     * @return a new Java object of the appropriate type (clazz) using the jsonObj to provide
     * enough hints to get the right class instantiated.  It is not populated when returned.
     */
    protected Object createJavaObjectInstance(Class clazz, JsonObject jsonObj)
    {
        final boolean useMapsLocal = useMaps;
        String type = jsonObj.type;

        // We can't set values to an Object, so well try to use the contained type instead
		if ("java.lang.Object".equals(type))
        {
			Object value = jsonObj.get("value");
        	if (jsonObj.keySet().size() == 1 && value != null)
            {
        		type = value.getClass().getName();
        	}
        }

        Object mate;

        // @type always takes precedence over inferred Java (clazz) type.
        if (type != null)
        {    // @type is explicitly set, use that as it always takes precedence
            Class c;
            try
            {
                c = MetaUtils.classForName(type, reader.getClassLoader(), failOnUnknownType);
            }
            catch (Exception e)
            {
                if (useMapsLocal)
                {
                    jsonObj.type = null;
                    jsonObj.target = null;
                    return jsonObj;
                }
                else
                {
                    String name = clazz == null ? "null" : clazz.getName();
                    throw new JsonIoException("Unable to create class: " + name, e);
                }
            }
            if (c.isArray())
            {    // Handle []
                Object[] items = jsonObj.getArray();
                int size = (items == null) ? 0 : items.length;
                if (c == char[].class)
                {
                    jsonObj.moveCharsToMate();
                    mate = jsonObj.target;
                }
                else
                {
                    mate = Array.newInstance(c.getComponentType(), size);
                }
            }
            else
            {    // Handle regular field.object reference
                if (MetaUtils.isPrimitive(c))
                {
                    mate = MetaUtils.convert(c, jsonObj.get("value"));
                }
                else if (c == Class.class)
                {
                    mate = MetaUtils.classForName((String) jsonObj.get("value"), reader.getClassLoader());
                }
                else if (c.isEnum())
                {
                    mate = getEnum(c, jsonObj);
                }
                else if (Enum.class.isAssignableFrom(c)) // anonymous subclass of an enum
                {
                    mate = getEnum(c.getSuperclass(), jsonObj);
                }
                else if (EnumSet.class.isAssignableFrom(c))
                {
                    mate = getEnumSet(c, jsonObj);
                }
                else if ((mate = coerceCertainTypes(c.getName())) != null)
                {   // if coerceCertainTypes() returns non-null, it did the work
                }
                else
                {
                    mate = newInstance(c, jsonObj);
                }
            }
        }
        else
        {    // @type, not specified, figure out appropriate type
            Object[] items = jsonObj.getArray();

            // if @items is specified, it must be an [] type.
            // if clazz.isArray(), then it must be an [] type.
            if (clazz.isArray() || (items != null && clazz == Object.class && !jsonObj.containsKey("@keys")))
            {
                int size = (items == null) ? 0 : items.length;
                mate = Array.newInstance(clazz.isArray() ? clazz.getComponentType() : Object.class, size);
            }
            else if (clazz.isEnum())
            {
                mate = getEnum(clazz, jsonObj);
            }
            else if (Enum.class.isAssignableFrom(clazz)) // anonymous subclass of an enum
            {
                mate = getEnum(clazz.getSuperclass(), jsonObj);
            }
            else if (EnumSet.class.isAssignableFrom(clazz)) // anonymous subclass of an enum
            {
                mate = getEnumSet(clazz, jsonObj);
            }
            else if ((mate = coerceCertainTypes(clazz.getName())) != null)
            {   // if coerceCertainTypes() returns non-null, it did the work
            }
            else if (clazz == Object.class && !useMapsLocal)
            {
                if (unknownClass == null)
                {
                    mate = new JsonObject();
                    ((JsonObject)mate).type = Map.class.getName();
                }
                else if (unknownClass instanceof String)
                {
                    mate = newInstance(MetaUtils.classForName(((String)unknownClass).trim(), reader.getClassLoader()), jsonObj);
                }
                else
                {
                    throw new JsonIoException("Unable to determine object type at column: " + jsonObj.col + ", line: " + jsonObj.line + ", content: " + jsonObj);
                }
            }
            else
            {
                mate = newInstance(clazz, jsonObj);
            }
        }
        jsonObj.target = mate;
        return jsonObj.target;
    }

    protected Object coerceCertainTypes(String type)
    {
        Class clazz = coercedTypes.get(type);
        if (clazz == null)
        {
            return null;
        }

        return MetaUtils.newInstance(clazz);
    }

    protected JsonObject getReferencedObj(Long ref)
    {
        JsonObject refObject = reader.getObjectsRead().get(ref);
        if (refObject == null)
        {
            throw new JsonIoException("Forward reference @ref: " + ref + ", but no object defined (@id) with that value");
        }
        return refObject;
    }

    protected JsonReader.JsonClassReaderBase getCustomReader(Class c)
    {
        JsonReader.JsonClassReaderBase reader = readerCache.get(c);
        if (reader == null)
        {
            reader = forceGetCustomReader(c);
            readerCache.put(c, reader);
        }
        return reader == nullReader ? null : reader;
    }

    private JsonReader.JsonClassReaderBase forceGetCustomReader(Class c)
    {
        JsonReader.JsonClassReaderBase closestReader = nullReader;
        int minDistance = Integer.MAX_VALUE;

        for (Map.Entry<Class, JsonReader.JsonClassReaderBase> entry : getReaders().entrySet())
        {
            Class clz = entry.getKey();
            if (clz == c)
            {
                return entry.getValue();
            }
            int distance = MetaUtils.getDistance(clz, c);
            if (distance < minDistance)
            {
                minDistance = distance;
                closestReader = entry.getValue();
            }
        }
        return closestReader;
    }

    /**
     * Fetch enum value (may need to try twice, due to potential 'name' field shadowing by enum subclasses
     */
    private Object getEnum(Class c, JsonObject jsonObj)
    {
        try
        {
            return Enum.valueOf(c, (String) jsonObj.get("name"));
        }
        catch (Exception e)
        {   // In case the enum class has it's own 'name' member variable (shadowing the 'name' variable on Enum)
            return Enum.valueOf(c, (String) jsonObj.get("java.lang.Enum.name"));
        }
    }

    /**
     * Create the EnumSet with its values (it must be created this way)
     */
    private Object getEnumSet(Class c, JsonObject<String, Object> jsonObj)
    {
        Object[] items = jsonObj.getArray();
        if (items == null || items.length == 0)
        {
            return newInstance(c, jsonObj);
        }
        JsonObject item = (JsonObject) items[0];
        String type = item.getType();
        Class enumClass = MetaUtils.classForName(type, reader.getClassLoader());
        EnumSet enumSet = null;
        for (Object objectItem : items)
        {
            item = (JsonObject) objectItem;
            Enum enumItem = (Enum) getEnum(enumClass, item);
            if (enumSet == null)
            {   // Lazy init the EnumSet
                enumSet = EnumSet.of(enumItem);
            }
            else
            {
                enumSet.add(enumItem);
            }
        }
        return enumSet;
    }

    /**
     * For all fields where the value was "@ref":"n" where 'n' was the id of an object
     * that had not yet been encountered in the stream, make the final substitution.
     */
    protected void patchUnresolvedReferences()
    {
        Iterator i = unresolvedRefs.iterator();
        while (i.hasNext())
        {
            UnresolvedReference ref = (UnresolvedReference) i.next();
            Object objToFix = ref.referencingObj.target;
            JsonObject objReferenced = reader.getObjectsRead().get(ref.refId);

            if (ref.index >= 0)
            {    // Fix []'s and Collections containing a forward reference.
                if (objToFix instanceof List)
                {   // Patch up Indexable Collections
                    List list = (List) objToFix;
                    list.set(ref.index, objReferenced.target);
                }
                else if (objToFix instanceof Collection)
                {   // Add element (since it was not indexable, add it to collection)
                    Collection col = (Collection) objToFix;
                    col.add(objReferenced.target);
                }
                else
                {
                    Array.set(objToFix, ref.index, objReferenced.target);        // patch array element here
                }
            }
            else
            {    // Fix field forward reference
                Field field = MetaUtils.getField(objToFix.getClass(), ref.field);
                if (field != null)
                {
                    try
                    {
                        field.set(objToFix, objReferenced.target);               // patch field here
                    }
                    catch (Exception e)
                    {
                        throw new JsonIoException("Error setting field while resolving references '" + field.getName() + "', @ref = " + ref.refId, e);
                    }
                }
            }

            i.remove();
        }
    }

    /**
     * Process Maps/Sets (fix up their internal indexing structure)
     * This is required because Maps hash items using hashCode(), which will
     * change between VMs.  Rehashing the map fixes this.
     *

     * If useMaps==true, then move @keys to keys and @items to values
     * and then drop these two entries from the map.
     *

     * This hashes both Sets and Maps because the JDK sets are implemented
     * as Maps.  If you have a custom built Set, this would not 'treat' it
     * and you would need to provider a custom reader for that set.
     */
    protected void rehashMaps()
    {
        final boolean useMapsLocal = useMaps;
        for (Object[] mapPieces : prettyMaps)
        {
            JsonObject jObj = (JsonObject) mapPieces[0];
            Object[] javaKeys, javaValues;
            Map map;

            if (useMapsLocal)
            {   // Make the @keys be the actual keys of the map.
                map = jObj;
                javaKeys = (Object[]) jObj.remove("@keys");
                javaValues = (Object[]) jObj.remove("@items");
            }
            else
            {
                map = (Map) jObj.target;
                javaKeys = (Object[]) mapPieces[1];
                javaValues = (Object[]) mapPieces[2];
                jObj.clear();
            }

            int j = 0;

            while (javaKeys != null && j < javaKeys.length)
            {
                map.put(javaKeys[j], javaValues[j]);
                j++;
            }
        }
    }

    // ========== Keep relationship knowledge below the line ==========
    public static Object newInstance(Class c, JsonObject jsonObject)
    {
        return JsonReader.newInstance(c, jsonObject);
    }

    protected Map<Class, JsonReader.JsonClassReaderBase> getReaders()
    {
        return reader.readers;
    }

    protected boolean notCustom(Class cls)
    {
        return reader.notCustom.contains(cls);
    }
}


package com.cedarsoftware.util.io;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * All custom readers for json-io subclass this class.  Special readers are not needed for handling
 * user-defined classes.  However, special readers are built/supplied by json-io for many of the
 * primitive types and other JDK classes simply to allow for a more concise form.
 *
 * @author John DeRegnaucourt (jdereg@gmail.com)
 *

 *         Copyright (c) Cedar Software LLC
 *

 *         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.*
 */
public class Readers
{
    private Readers () {}

    private static final String DAYS = "(monday|mon|tuesday|tues|tue|wednesday|wed|thursday|thur|thu|friday|fri|saturday|sat|sunday|sun)"; // longer before shorter matters
    private static final String MOS = "(January|Jan|February|Feb|March|Mar|April|Apr|May|June|Jun|July|Jul|August|Aug|September|Sept|Sep|October|Oct|November|Nov|December|Dec)";
    private static final Pattern datePattern1 = Pattern.compile("(\\d{4})[./-](\\d{1,2})[./-](\\d{1,2})");
    private static final Pattern datePattern2 = Pattern.compile("(\\d{1,2})[./-](\\d{1,2})[./-](\\d{4})");
    private static final Pattern datePattern3 = Pattern.compile(MOS + "[ ]*[,]?[ ]*(\\d{1,2})(st|nd|rd|th|)[ ]*[,]?[ ]*(\\d{4})", Pattern.CASE_INSENSITIVE);
    private static final Pattern datePattern4 = Pattern.compile("(\\d{1,2})(st|nd|rd|th|)[ ]*[,]?[ ]*" + MOS + "[ ]*[,]?[ ]*(\\d{4})", Pattern.CASE_INSENSITIVE);
    private static final Pattern datePattern5 = Pattern.compile("(\\d{4})[ ]*[,]?[ ]*" + MOS + "[ ]*[,]?[ ]*(\\d{1,2})(st|nd|rd|th|)", Pattern.CASE_INSENSITIVE);
    private static final Pattern datePattern6 = Pattern.compile(DAYS + "[ ]+" + MOS + "[ ]+(\\d{1,2})[ ]+(\\d{2}:\\d{2}:\\d{2})[ ]+[A-Z]{1,4}\\s+(\\d{4})", Pattern.CASE_INSENSITIVE);
    private static final Pattern timePattern1 = Pattern.compile("(\\d{2})[.:](\\d{2})[.:](\\d{2})[.](\\d{1,10})([+-]\\d{2}[:]?\\d{2}|Z)?");
    private static final Pattern timePattern2 = Pattern.compile("(\\d{2})[.:](\\d{2})[.:](\\d{2})([+-]\\d{2}[:]?\\d{2}|Z)?");
    private static final Pattern timePattern3 = Pattern.compile("(\\d{2})[.:](\\d{2})([+-]\\d{2}[:]?\\d{2}|Z)?");
    private static final Pattern dayPattern = Pattern.compile(DAYS, Pattern.CASE_INSENSITIVE);
    private static final Map<String, String> months = new LinkedHashMap<String, String>();

    static
    {
        // Month name to number map
        months.put("jan", "1");
        months.put("january", "1");
        months.put("feb", "2");
        months.put("february", "2");
        months.put("mar", "3");
        months.put("march", "3");
        months.put("apr", "4");
        months.put("april", "4");
        months.put("may", "5");
        months.put("jun", "6");
        months.put("june", "6");
        months.put("jul", "7");
        months.put("july", "7");
        months.put("aug", "8");
        months.put("august", "8");
        months.put("sep", "9");
        months.put("sept", "9");
        months.put("september", "9");
        months.put("oct", "10");
        months.put("october", "10");
        months.put("nov", "11");
        months.put("november", "11");
        months.put("dec", "12");
        months.put("december", "12");
    }

    public static class TimeZoneReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            JsonObject jObj = (JsonObject)o;
            Object zone = jObj.get("zone");
            if (zone == null)
            {
                throw new JsonIoException("java.util.TimeZone must specify 'zone' field");
            }
            jObj.target = TimeZone.getTimeZone((String) zone);
            return jObj.target;
        }
    }

    public static class LocaleReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            JsonObject jObj = (JsonObject) o;
            Object language = jObj.get("language");
            if (language == null)
            {
                throw new JsonIoException("java.util.Locale must specify 'language' field");
            }
            Object country = jObj.get("country");
            Object variant = jObj.get("variant");
            if (country == null)
            {
                jObj.target = new Locale((String) language);
                return jObj.target;
            }
            if (variant == null)
            {
                jObj.target = new Locale((String) language, (String) country);
                return jObj.target;
            }

            jObj.target = new Locale((String) language, (String) country, (String) variant);
            return jObj.target;
        }
    }

    public static class CalendarReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            String time = null;
            try
            {
                JsonObject jObj = (JsonObject) o;
                time = (String) jObj.get("time");
                if (time == null)
                {
                    throw new JsonIoException("Calendar missing 'time' field");
                }
                Date date = MetaUtils.dateFormat.get().parse(time);
                Class c;
                if (jObj.getTarget() != null)
                {
                    c = jObj.getTarget().getClass();
                }
                else
                {
                    Object type = jObj.type;
                    c = classForName((String) type, (ClassLoader)args.get(JsonReader.CLASSLOADER));
                }

                Calendar calendar = (Calendar) newInstance(c, jObj);
                calendar.setTime(date);
                jObj.setTarget(calendar);
                String zone = (String) jObj.get("zone");
                if (zone != null)
                {
                    calendar.setTimeZone(TimeZone.getTimeZone(zone));
                }
                return calendar;
            }
            catch(Exception e)
            {
                throw new JsonIoException("Failed to parse calendar, time: " + time);
            }
        }
    }

    public static class DateReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            if (o instanceof Long)
            {
                return new Date((Long) o);
            }
            else if (o instanceof String)
            {
                return parseDate((String) o);
            }
            else if (o instanceof JsonObject)
            {
                JsonObject jObj = (JsonObject) o;
                Object val = jObj.get("value");
                if (val instanceof Long)
                {
                    return new Date((Long) val);
                }
                else if (val instanceof String)
                {
                    return parseDate((String) val);
                }
                throw new JsonIoException("Unable to parse date: " + o);
            }
            else
            {
                throw new JsonIoException("Unable to parse date, encountered unknown object: " + o);
            }
        }

        static Date parseDate(String dateStr)
        {
            dateStr = dateStr.trim();
            if (dateStr.isEmpty())
            {
                return null;
            }

            // Determine which date pattern (Matcher) to use
            Matcher matcher = datePattern1.matcher(dateStr);

            String year, month = null, day, mon = null, remains;

            if (matcher.find())
            {
                year = matcher.group(1);
                month = matcher.group(2);
                day = matcher.group(3);
                remains = matcher.replaceFirst("");
            }
            else
            {
                matcher = datePattern2.matcher(dateStr);
                if (matcher.find())
                {
                    month = matcher.group(1);
                    day = matcher.group(2);
                    year = matcher.group(3);
                    remains = matcher.replaceFirst("");
                }
                else
                {
                    matcher = datePattern3.matcher(dateStr);
                    if (matcher.find())
                    {
                        mon = matcher.group(1);
                        day = matcher.group(2);
                        year = matcher.group(4);
                        remains = matcher.replaceFirst("");
                    }
                    else
                    {
                        matcher = datePattern4.matcher(dateStr);
                        if (matcher.find())
                        {
                            day = matcher.group(1);
                            mon = matcher.group(3);
                            year = matcher.group(4);
                            remains = matcher.replaceFirst("");
                        }
                        else
                        {
                            matcher = datePattern5.matcher(dateStr);
                            if (matcher.find())
                            {
                                year = matcher.group(1);
                                mon = matcher.group(2);
                                day = matcher.group(3);
                                remains = matcher.replaceFirst("");
                            }
                            else
                            {
                                matcher = datePattern6.matcher(dateStr);
                                if (!matcher.find())
                                {
                                    throw new JsonIoException("Unable to parse: " + dateStr);
                                }
                                year = matcher.group(5);
                                mon = matcher.group(2);
                                day = matcher.group(3);
                                remains = matcher.group(4);
                            }
                        }
                    }
                }
            }

            if (mon != null)
            {   // Month will always be in Map, because regex forces this.
                month = months.get(mon.trim().toLowerCase());
            }

            // Determine which date pattern (Matcher) to use
            String hour = null, min = null, sec = "00", milli = "0", tz = null;
            remains = remains.trim();
            matcher = timePattern1.matcher(remains);
            if (matcher.find())
            {
                hour = matcher.group(1);
                min = matcher.group(2);
                sec = matcher.group(3);
                milli = matcher.group(4);
                if (matcher.groupCount() > 4)
                {
                    tz = matcher.group(5);
                }
            }
            else
            {
                matcher = timePattern2.matcher(remains);
                if (matcher.find())
                {
                    hour = matcher.group(1);
                    min = matcher.group(2);
                    sec = matcher.group(3);
                    if (matcher.groupCount() > 3)
                    {
                        tz = matcher.group(4);
                    }
                }
                else
                {
                    matcher = timePattern3.matcher(remains);
                    if (matcher.find())
                    {
                        hour = matcher.group(1);
                        min = matcher.group(2);
                        if (matcher.groupCount() > 2)
                        {
                            tz = matcher.group(3);
                        }
                    }
                    else
                    {
                        matcher = null;
                    }
                }
            }

            if (matcher != null)
            {
                remains = matcher.replaceFirst("");
            }

            // Clear out day of week (mon, tue, wed, ...)
            if (remains != null && remains.length() > 0)
            {
                Matcher dayMatcher = dayPattern.matcher(remains);
                if (dayMatcher.find())
                {
                    remains = dayMatcher.replaceFirst("").trim();
                }
            }
            if (remains != null && remains.length() > 0)
            {
                remains = remains.trim();
                if (!remains.equals(",") && (!remains.equals("T")))
                {
                    throw new JsonIoException("Issue parsing data/time, other characters present: " + remains);
                }
            }

            Calendar c = Calendar.getInstance();
            c.clear();
            if (tz != null)
            {
                if ("z".equalsIgnoreCase(tz))
                {
                    c.setTimeZone(TimeZone.getTimeZone("GMT"));
                }
                else
                {
                    c.setTimeZone(TimeZone.getTimeZone("GMT" + tz));
                }
            }

            // Regex prevents these from ever failing to parse
            int y = Integer.parseInt(year);
            int m = Integer.parseInt(month) - 1;    // months are 0-based
            int d = Integer.parseInt(day);

            if (m < 0 || m > 11)
            {
                throw new JsonIoException("Month must be between 1 and 12 inclusive, date: " + dateStr);
            }
            if (d < 1 || d > 31)
            {
                throw new JsonIoException("Day must be between 1 and 31 inclusive, date: " + dateStr);
            }

            if (matcher == null)
            {   // no [valid] time portion
                c.set(y, m, d);
            }
            else
            {
                // Regex prevents these from ever failing to parse.
                int h = Integer.parseInt(hour);
                int mn = Integer.parseInt(min);
                int s = Integer.parseInt(sec);
                int ms = Integer.parseInt(milli);

                if (h > 23)
                {
                    throw new JsonIoException("Hour must be between 0 and 23 inclusive, time: " + dateStr);
                }
                if (mn > 59)
                {
                    throw new JsonIoException("Minute must be between 0 and 59 inclusive, time: " + dateStr);
                }
                if (s > 59)
                {
                    throw new JsonIoException("Second must be between 0 and 59 inclusive, time: " + dateStr);
                }

                // regex enforces millis to number
                c.set(y, m, d, h, mn, s);
                c.set(Calendar.MILLISECOND, ms);
            }
            return c.getTime();
        }
    }

    public static class SqlDateReader extends DateReader
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            return new java.sql.Date(((Date) super.read(o, stack, args)).getTime());
        }
    }

    public static class StringReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            if (o instanceof String)
            {
                return o;
            }

            if (MetaUtils.isPrimitive(o.getClass()))
            {
                return o.toString();
            }

            JsonObject jObj = (JsonObject) o;
            if (jObj.containsKey("value"))
            {
                jObj.target = jObj.get("value");
                return jObj.target;
            }
            throw new JsonIoException("String missing 'value' field");
        }
    }

    public static class ClassReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            if (o instanceof String)
            {
                return classForName((String) o, (ClassLoader)args.get(JsonReader.CLASSLOADER));
            }

            JsonObject jObj = (JsonObject) o;
            if (jObj.containsKey("value"))
            {
                jObj.target = classForName((String) jObj.get("value"), (ClassLoader)args.get(JsonReader.CLASSLOADER));
                return jObj.target;
            }
            throw new JsonIoException("Class missing 'value' field");
        }
    }

    public static class AtomicBooleanReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            Object value = o;
            value = getValueFromJsonObject(o, value, "AtomicBoolean");

            if (value instanceof String)
            {
                String state = (String) value;
                if ("".equals(state.trim()))
                {   // special case
                    return null;
                }
                return new AtomicBoolean("true".equalsIgnoreCase(state));
            }
            else if (value instanceof Boolean)
            {
                return new AtomicBoolean((Boolean) value);
            }
            else if (value instanceof Number && !(value instanceof Double) && !(value instanceof Float))
            {
                return new AtomicBoolean(((Number)value).longValue() != 0);
            }
            throw new JsonIoException("Unknown value in JSON assigned to AtomicBoolean, value type = " + value.getClass().getName());
        }
    }

    public static class AtomicIntegerReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            Object value = o;
            value = getValueFromJsonObject(o, value, "AtomicInteger");

            if (value instanceof String)
            {
                String num = (String) value;
                if ("".equals(num.trim()))
                {   // special case
                    return null;
                }
                return new AtomicInteger(Integer.parseInt(MetaUtils.removeLeadingAndTrailingQuotes(num)));
            }
            else if (value instanceof Number && !(value instanceof Double) && !(value instanceof Float))
            {
                return new AtomicInteger(((Number)value).intValue());
            }
            throw new JsonIoException("Unknown value in JSON assigned to AtomicInteger, value type = " + value.getClass().getName());
        }
    }

    public static class AtomicLongReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            Object value = o;
            value = getValueFromJsonObject(o, value, "AtomicLong");

            if (value instanceof String)
            {
                String num = (String) value;
                if ("".equals(num.trim()))
                {   // special case
                    return null;
                }
                return new AtomicLong(Long.parseLong(MetaUtils.removeLeadingAndTrailingQuotes(num)));
            }
            else if (value instanceof Number && !(value instanceof Double) && !(value instanceof Float))
            {
                return new AtomicLong(((Number)value).longValue());
            }
            throw new JsonIoException("Unknown value in JSON assigned to AtomicLong, value type = " + value.getClass().getName());
        }
    }

    private static Object getValueFromJsonObject(Object o, Object value, String typeName)
    {
        if (o instanceof JsonObject)
        {
            JsonObject jObj = (JsonObject) o;
            if (jObj.containsKey("value"))
            {
                value = jObj.get("value");
            }
            else
            {
                throw new JsonIoException(typeName + " defined as JSON {} object, missing 'value' field");
            }
        }
        return value;
    }

    public static class BigIntegerReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            JsonObject jObj = null;
            Object value = o;
            if (o instanceof JsonObject)
            {
                jObj = (JsonObject) o;
                if (jObj.containsKey("value"))
                {
                    value = jObj.get("value");
                }
                else
                {
                    throw new JsonIoException("BigInteger missing 'value' field");
                }
            }

            if (value instanceof JsonObject)
            {
                JsonObject valueObj = (JsonObject)value;
                if ("java.math.BigDecimal".equals(valueObj.type))
                {
                    BigDecimalReader reader = new BigDecimalReader();
                    value = reader.read(value, stack, args);
                }
                else if ("java.math.BigInteger".equals(valueObj.type))
                {
                    value = read(value, stack, args);
                }
                else
                {
                    return bigIntegerFrom(valueObj.get("value"));
                }
            }

            BigInteger x = bigIntegerFrom(value);
            if (jObj != null)
            {
                jObj.target = x;
            }

            return x;
        }
    }

    /**
     * @param value to be converted to BigInteger.  Can be a null which will return null.  Can be
     * BigInteger in which case it will be returned as-is.  Can also be String, BigDecimal,
     * Boolean (which will be returned as BigInteger.ZERO or .ONE), byte, short, int, long, float,
     * or double.  If an unknown type is passed in, an exception will be thrown.
     * @return a BigInteger from the given input.  A best attempt will be made to support
     * as many input types as possible.  For example, if the input is a Boolean, a BigInteger of
     * 1 or 0 will be returned.  If the input is a String "", a null will be returned.  If the
     * input is a Double, Float, or BigDecimal, a BigInteger will be returned that retains the
     * integer portion (fractional part is dropped).  The input can be a Byte, Short, Integer,
     * or Long.
     */
    public static BigInteger bigIntegerFrom(Object value)
    {
        if (value == null)
        {
            return null;
        }
        else if (value instanceof BigInteger)
        {
            return (BigInteger) value;
        }
        else if (value instanceof String)
        {
            String s = (String) value;
            if ("".equals(s.trim()))
            {   // Allows "" to be used to assign null to BigInteger field.
                return null;
            }
            try
            {
                return new BigInteger(MetaUtils.removeLeadingAndTrailingQuotes(s));
            }
            catch (Exception e)
            {
                throw new JsonIoException("Could not parse '" + value + "' as BigInteger.", e);
            }
        }
        else if (value instanceof BigDecimal)
        {
            BigDecimal bd = (BigDecimal) value;
            return bd.toBigInteger();
        }
        else if (value instanceof Boolean)
        {
            return (Boolean) value ? BigInteger.ONE : BigInteger.ZERO;
        }
        else if (value instanceof Double || value instanceof Float)
        {
            return new BigDecimal(((Number)value).doubleValue()).toBigInteger();
        }
        else if (value instanceof Long || value instanceof Integer ||
                value instanceof Short || value instanceof Byte)
        {
            return new BigInteger(value.toString());
        }
        throw new JsonIoException("Could not convert value: " + value.toString() + " to BigInteger.");
    }

    public static class BigDecimalReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            JsonObject jObj = null;
            Object value = o;
            if (o instanceof JsonObject)
            {
                jObj = (JsonObject) o;
                if (jObj.containsKey("value"))
                {
                    value = jObj.get("value");
                }
                else
                {
                    throw new JsonIoException("BigDecimal missing 'value' field");
                }
            }

            if (value instanceof JsonObject)
            {
                JsonObject valueObj = (JsonObject)value;
                if ("java.math.BigInteger".equals(valueObj.type))
                {
                    BigIntegerReader reader = new BigIntegerReader();
                    value = reader.read(value, stack, args);
                }
                else if ("java.math.BigDecimal".equals(valueObj.type))
                {
                    value = read(value, stack, args);
                }
                else
                {
                    return bigDecimalFrom(valueObj.get("value"));
                }
            }

            BigDecimal x = bigDecimalFrom(value);
            if (jObj != null)
            {
                jObj.target = x;
            }
            return x;
        }
    }

    /**
     * @param value to be converted to BigDecimal.  Can be a null which will return null.  Can be
     * BigDecimal in which case it will be returned as-is.  Can also be String, BigInteger,
     * Boolean (which will be returned as BigDecimal.ZERO or .ONE), byte, short, int, long, float,
     * or double.  If an unknown type is passed in, an exception will be thrown.
     *
     * @return a BigDecimal from the given input.  A best attempt will be made to support
     * as many input types as possible.  For example, if the input is a Boolean, a BigDecimal of
     * 1 or 0 will be returned.  If the input is a String "", a null will be returned. The input
     * can be a Byte, Short, Integer, Long, or BigInteger.
     */
    public static BigDecimal bigDecimalFrom(Object value)
    {
        if (value == null)
        {
            return null;
        }
        else if (value instanceof BigDecimal)
        {
            return (BigDecimal) value;
        }
        else if (value instanceof String)
        {
            String s = (String) value;
            if ("".equals(s.trim()))
            {
                return null;
            }
            try
            {
                return new BigDecimal(MetaUtils.removeLeadingAndTrailingQuotes(s));
            }
            catch (Exception e)
            {
                throw new JsonIoException("Could not parse '" + s + "' as BigDecimal.", e);
            }
        }
        else if (value instanceof BigInteger)
        {
            return new BigDecimal((BigInteger) value);
        }
        else if (value instanceof Boolean)
        {
            return (Boolean) value ? BigDecimal.ONE : BigDecimal.ZERO;
        }
        else if (value instanceof Long || value instanceof Integer || value instanceof Double ||
                value instanceof Short || value instanceof Byte || value instanceof Float)
        {
            return new BigDecimal(value.toString());
        }
        throw new JsonIoException("Could not convert value: " + value.toString() + " to BigInteger.");
    }

    public static class StringBuilderReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            if (o instanceof String)
            {
                return new StringBuilder((String) o);
            }

            JsonObject jObj = (JsonObject) o;
            if (jObj.containsKey("value"))
            {
                jObj.target = new StringBuilder((String) jObj.get("value"));
                return jObj.target;
            }
            throw new JsonIoException("StringBuilder missing 'value' field");
        }
    }

    public static class StringBufferReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            if (o instanceof String)
            {
                return new StringBuffer((String) o);
            }

            JsonObject jObj = (JsonObject) o;
            if (jObj.containsKey("value"))
            {
                jObj.target = new StringBuffer((String) jObj.get("value"));
                return jObj.target;
            }
            throw new JsonIoException("StringBuffer missing 'value' field");
        }
    }

    public static class TimestampReader implements JsonReader.JsonClassReaderEx
    {
        public Object read(Object o, Deque<JsonObject<String, Object>> stack, Map<String, Object> args)
        {
            JsonObject jObj = (JsonObject) o;
            Object time = jObj.get("time");
            if (time == null)
            {
                throw new JsonIoException("java.sql.Timestamp must specify 'time' field");
            }
            Object nanos = jObj.get("nanos");
            if (nanos == null)
            {
                jObj.target = new Timestamp(Long.valueOf((String) time));
                return jObj.target;
            }

            Timestamp tstamp = new Timestamp(Long.valueOf((String) time));
            tstamp.setNanos(Integer.valueOf((String) nanos));
            jObj.target = tstamp;
            return jObj.target;
        }
    }

    // ========== Maintain dependency knowledge in once place, down here =========
    static Class classForName(String name, ClassLoader classLoader)
    {
        return MetaUtils.classForName(name, classLoader);
    }

    static Object newInstance(Class c, JsonObject jsonObject)
    {
        return JsonReader.newInstance(c, jsonObject);
    }
}


package com.cedarsoftware.util.io;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.Deque;
import java.util.List;
import java.util.Map;

/**
 *
The MapResolver converts the raw Maps created from the JsonParser to higher
 * quality Maps representing the implied object graph.  It does this by replacing
 * @ref values with the Map indicated by the @id key with the same value.
 *

 * This approach 'wires' the original object graph.  During the resolution process,
 * if 'peer' classes can be found for given Maps (for example, an @type entry is
 * available which indicates the class that would have been associated to the Map,
 * then the associated class is consulted to help 'improve' the quality of the primitive
 * values within the map fields.  For example, if the peer class indicated that a field
 * was of type 'short', and the Map had a long value (JSON only returns long's for integer
 * types), then the long would be converted to a short.
 *

 * The final Map representation is a very high-quality graph that represents the original
 * JSON graph.  It can be passed as input to JsonWriter, and the JsonWriter will write
 * out the equivalent JSON to what was originally read.  This technique allows json-io to
 * be used on a machine that does not have any of the Java classes from the original graph,
 * read it in a JSON graph (any JSON graph), return the equivalent maps, allow mutations of
 * those maps, and finally this graph can be written out.
 *

 * @author John DeRegnaucourt (jdereg@gmail.com)
 *

 *         Copyright (c) Cedar Software LLC
 *

 *         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.
 */
public class MapResolver extends Resolver
{
    protected MapResolver(JsonReader reader)
    {
        super(reader);
    }

    protected Object readIfMatching(Object o, Class compType, Deque<JsonObject<String, Object>> stack)
    {
        // No custom reader support for maps
        return null;
    }

    /**
     * Walk the JsonObject fields and perform necessary substitutions so that all references matched up.
     * This code patches @ref and @id pairings up, in the 'Map of Map' mode.  Where the JSON may contain
     * an @id of an object which can have more than one @ref to it, this code will make sure that each
     * @ref (value side of the Map associated to a given field name) will be pointer to the appropriate Map
     * instance.
     * @param stack   Stack (Deque) used for graph traversal.
     * @param jsonObj a Map-of-Map representation of the current object being examined (containing all fields).
     */
    public void traverseFields(final Deque<JsonObject<String, Object>> stack, final JsonObject<String, Object> jsonObj)
    {
        final Object target = jsonObj.target;
        for (Map.Entry<String, Object> e : jsonObj.entrySet())
        {
            final String fieldName = e.getKey();
            final Field field = (target != null) ? MetaUtils.getField(target.getClass(), fieldName) : null;
            final Object rhs = e.getValue();

            if (rhs == null)
            {
                jsonObj.put(fieldName, null);
            }
            else if (rhs == JsonParser.EMPTY_OBJECT)
            {
                jsonObj.put(fieldName, new JsonObject());
            }
            else if (rhs.getClass().isArray())
            {   // RHS is an array
                // Trace the contents of the array (so references inside the array and into the array work)
                JsonObject<String, Object> jsonArray = new JsonObject<String, Object>();
                jsonArray.put("@items", rhs);
                stack.addFirst(jsonArray);

                // Assign the array directly to the Map key (field name)
                jsonObj.put(fieldName, rhs);
            }
            else if (rhs instanceof JsonObject)
            {
                JsonObject<String, Object> jObj = (JsonObject) rhs;

                if (field != null && MetaUtils.isLogicalPrimitive(field.getType()))
                {
                    jObj.put("value", MetaUtils.convert(field.getType(), jObj.get("value")));
                    continue;
                }
                Long refId = jObj.getReferenceId();

                if (refId != null)
                {    // Correct field references
                    JsonObject refObject = getReferencedObj(refId);
                    jsonObj.put(fieldName, refObject);    // Update Map-of-Maps reference
                }
                else
                {
                    stack.addFirst(jObj);
                }
            }
            else if (field != null)
            {   // The code below is 'upgrading' the RHS values in the passed in JsonObject Map
                // by using the @type class name (when specified and exists), to coerce the vanilla
                // JSON values into the proper types defined by the class listed in @type.  This is
                // a cool feature of json-io, that even when reading a map-of-maps JSON file, it will
                // improve the final types of values in the maps RHS, to be of the field type that
                // was optionally specified in @type.
                final Class fieldType = field.getType();
                if (MetaUtils.isPrimitive(fieldType) || BigDecimal.class.equals(fieldType) || BigInteger.class.equals(fieldType) || Date.class.equals(fieldType))
                {
                    jsonObj.put(fieldName, MetaUtils.convert(fieldType, rhs));
                }
                else if (rhs instanceof String)
                {
                    if (fieldType != String.class && fieldType != StringBuilder.class && fieldType != StringBuffer.class)
                    {
                        if ("".equals(((String)rhs).trim()))
                        {   // Allow "" to null out a non-String field on the inbound JSON
                            jsonObj.put(fieldName, null);
                        }
                    }
                }
            }
        }
        jsonObj.target = null;  // don't waste space (used for typed return, not for Map return)
    }

    /**
     * Process java.util.Collection and it's derivatives.  Collections are written specially
     * so that the serialization does not expose the Collection's internal structure, for
     * example a TreeSet.  All entries are processed, except unresolved references, which
     * are filled in later.  For an indexable collection, the unresolved references are set
     * back into the proper element location.  For non-indexable collections (Sets), the
     * unresolved references are added via .add().
     * @param stack   a Stack (Deque) used to support graph traversal.
     * @param jsonObj a Map-of-Map representation of the JSON input stream.
     */
    protected void traverseCollection(final Deque<JsonObject<String, Object>> stack, final JsonObject<String, Object> jsonObj)
    {
        final Object[] items = jsonObj.getArray();
        if (items == null || items.length == 0)
        {
            return;
        }

        int idx = 0;
        final List copy = new ArrayList(items.length);

        for (Object element : items)
        {
            if (element == JsonParser.EMPTY_OBJECT)
            {
                copy.add(new JsonObject());
                continue;
            }

            copy.add(element);

            if (element instanceof Object[])
            {   // array element inside Collection
                JsonObject<String, Object> jsonObject = new JsonObject<String, Object>();
                jsonObject.put("@items", element);
                stack.addFirst(jsonObject);
            }
            else if (element instanceof JsonObject)
            {
                JsonObject<String, Object> jsonObject = (JsonObject<String, Object>) element;
                Long refId = jsonObject.getReferenceId();

                if (refId != null)
                {    // connect reference
                    JsonObject refObject = getReferencedObj(refId);
                    copy.set(idx, refObject);
                }
                else
                {
                    stack.addFirst(jsonObject);
                }
            }
            idx++;
        }
        jsonObj.target = null;  // don't waste space (used for typed return, not generic Map return)

        for (int i=0; i < items.length; i++)
        {
            items[i] = copy.get(i);
        }
    }

    protected void traverseArray(Deque<JsonObject<String, Object>> stack, JsonObject<String, Object> jsonObj)
    {
        traverseCollection(stack, jsonObj);
    }
}