/*
 * Copyright (C) 2011 Miami-Dade County.
 *
 * 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.
 * 
 * Note: this file incorporates source code from 3d party entities. Such code 
 * is copyrighted by those entities as indicated below.
 */
package mjson;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;

/**
 * 
 * <p>
 * Represents a JSON (JavaScript Object Notation) entity. For more information about JSON, please see
 * <a href="http://www.json.org" target="_">http://www.json.org</a>.  
 * </p>
 *
 * <p>
 * A JSON entity can be one of several things: an object (set of name/Json entity pairs), an array (a list of
 * other JSON entities), a string, a number, a boolean or null. All of those are represented as <code>Json</code>
 * instances. Each of the different types of entities supports a different set of operations. However, this class
 * unifies all operations into a single interface so in Java one is always dealing with a single object type: this class. 
 * The approach effectively amounts to dynamic typing where using an unsupported operation won't be detected at
 * compile time, but will throw a runtime {@link UnsupportedOperationException}. It simplifies working with JSON
 * structures considerably and it leads to shorter at cleaner Java code. It makes much easier to work
 * with JSON structure without the need to convert to "proper" Java representation in the form of
 * POJOs and the like. When traversing a JSON, there's no need to type-cast at each step because there's
 * only one type: <code>Json</code>.   
 * </p>
 * 
 * <p>
 * One can examine the concrete type of a <code>Json</code> with one of the <code>isXXX</code> methods: 
 * {@link #isObject()}, {@link #isArray()},{@link #isNumber()},{@link #isBoolean()},{@link #isString()},
 * {@link #isNull()}.
 * </p>
 * 
 * <p>
 * The underlying representation of a given <code>Json</code> instance can be obtained by calling
 * the generic {@link #getValue()} method or one of the <code>asXXX</code> methods such 
 * as {@link #asBoolean()} or {@link #asString()} etc. 
 * JSON objects are represented as Java {@link Map}s while JSON arrays are represented as Java
 * {@link List}s. Because those are mutable aggregate structures, there are two versions of the 
 * corresponding <code>asXXX</code> methods: {@link #asMap()} which performs a deep copy of the underlying
 * map, unwrapping every nested Json entity to its Java representation and {@link #asJsonMap()} which
 * simply return the map reference. Similarly there are {@link #asList()} and {@link #asJsonList()}. 
 * </p>
 * 
 * <h3>Constructing and Modifying JSON Structures</h3>
 * 
 * <p>
 * There are several static factory methods in this class that allow you to create new
 * <code>Json</code> instances:
 * </p>
 *  
 * <table>
 * <tr><td>{@link #read(String)}</td>
 * <td>Parse a JSON string and return the resulting <code>Json</code> instance. The syntax
 * recognized is as defined in <a href="http://www.json.org">http://www.json.org</a>.
 * </td> 
 * </tr>
 * <tr><td>{@link #make(Object)}</td>
 * <td>Creates a Json instance based on the concrete type of the parameter. The types
 * recognized are null, numbers, primitives, String, Map, Collection, Java arrays
 * and <code>Json</code> itself.</td>
 * </tr>
 * <tr><td>{@link #nil()}</td>
 * <td>Return a <code>Json</code> instance representing JSON <code>null</code>.</td>
 * </tr>
 * <tr><td>{@link #object()}</td>
 * <td>Create and return an empty JSON object.</td>
 * </tr>
 * <tr><td>{@link #object(Object...)}</td>
 * <td>Create and return a JSON object populated with the key/value pairs
 * passed as an argument sequence. Each even parameter becomes a key (via 
 * <code>toString</code>) and each odd parameter is converted to a <code>Json</code>
 * value.</td>
 * </tr>   
 * <tr><td>{@link #array()}</td>
 * <td>Create and return an empty JSON array.</td>
 * </tr> 
 * <tr><td>{@link #array(Object...)}</td>
 * <td>Create and return a JSON array from the list of arguments.</td>
 * </tr> 
 * </table>
 * 
 * <p>
 * To customize how Json elements are represented and to provide your own version of the
 * {@link #make(Object)} method, you create an implementation of the {@link Factory} interface
 * and configure it either globally with the {@link #setGlobalFactory(Factory)} method or
 * on a per-thread basis with the {@link #attachFactory(Factory)}/{@link #detachFactory()} 
 * methods.
 * </p>
 * 
 * <p>
 * If a <code>Json</code> instance is an object, you can set its properties by
 * calling the {@link #set(String, Object)} method which will add a new property or replace an existing one.
 * Adding elements to an array <code>Json</code> is done with the {@link #add(Object)} method.
 * Removing elements by their index (or key) is done with the {@link #delAt(int)} (or
 * {@link #delAt(String)}) method. You can also remove an element from an array without
 * knowing its index with the {@link #remove(Object)} method. All these methods return the 
 * <code>Json</code> instance being manipulated so that method calls can be chained.
 * If you want to remove an element from an object or array and return the removed element
 * as a result of the operation, call {@link #atDel(int)} or {@link #atDel(String)} instead.  
 * </p>
 * 
 * <p>
 * If you want to add properties to an object in bulk or append a sequence of elements to array, 
 * use the {@link #with(Json, Json...opts)} method. When used on an object, this method expects another
 * object as its argument and it will copy all properties of that argument into itself. Similarly,
 * when called on array, the method expects another array and it will append all elements of its
 * argument to itself.
 * </p>
 * 
 * <p>
 * To make a clone of a Json object, use the {@link #dup()} method. This method will create a new 
 * object even for the immutable primitive Json types. Objects and arrays are cloned 
 * (i.e. duplicated) recursively.
 * </p>
 * 
 * <h3>Navigating JSON Structures</h3>
 * 
 * <p>
 * The {@link #at(int)} method returns the array element at the specified index and the
 * {@link #at(String)} method does the same for a property of an object instance. You can
 * use the {@link #at(String, Object)} version to create an object property with a default
 * value if it doesn't exist already.  
 * </p>
 * 
 * <p>
 * To test just whether a Json object has a given property, use the {@link #has(String)} method. To test
 * whether a given object property or an array elements is equal to a particular value, use the 
 * {@link #is(String, Object)} and {@link #is(int, Object)} methods respectively. Those methods return 
 * true if the given named property (or indexed element) is equal to the passed in Object as the second 
 * parameter. They return false if an object doesn't have the specified property or an index array is out 
 * of bounds. For example is(name, value) is equivalent to 'has(name) &amp;&amp; at(name).equals(make(value))'.
 * </p>
 * 
 * <p>
 * To help in navigating JSON structures, instances of this class contain a reference to the 
 * enclosing JSON entity (object or array) if any. The enclosing entity can be accessed 
 * with {@link #up()} method.
 * </p>
 * 
 * <p>
 * The combination of method chaining when modifying <code>Json</code> instances and
 * the ability to navigate "inside" a structure and then go back to the enclosing 
 * element lets one accomplish a lot in a single Java statement, without the need
 * of intermediary variables. Here for example how the following JSON structure can
 * be created in one statement using chained calls:
 * </p>
 * 
 * <pre><code>
 * {"menu": {
 * "id": "file",
 * "value": "File",
 * "popup": {
 *   "menuitem": [
 *     {"value": "New", "onclick": "CreateNewDoc()"},
 *     {"value": "Open", "onclick": "OpenDoc()"},
 *     {"value": "Close", "onclick": "CloseDoc()"}
 *   ]
 * }
 * "position": 0
 * }}
 * </code></pre>
 * 
 * <pre><code>
 * import mjson.Json;
 * import static mjson.Json.*;
 * ...
 * Json j = object()
 *  .at("menu", object())
 *    .set("id", "file") 
 *    .set("value", "File")
 *    .at("popup", object())
 *      .at("menuitem", array())
 *        .add(object("value", "New", "onclick", "CreateNewDoc()"))
 *        .add(object("value", "Open", "onclick", "OpenDoc()"))
 *        .add(object("value", "Close", "onclick", "CloseDoc()"))
 *        .up()
 *      .up()
 *    .set("position", 0)
 *  .up();       
 * ...
 * </code></pre>
 * 
 * <p>
 * If there's no danger of naming conflicts, a static import of the factory methods (<code>
 * import static json.Json.*;</code>) would reduce typing even further and make the code more
 * readable.
 * </p>
 *
 * <h3>Converting to String</h3>
 * 
 * <p>
 * To get a compact string representation, simply use the {@link #toString()} method. If you
 * want to wrap it in a JavaScript callback (for JSON with padding), use the {@link #pad(String)}
 * method.
 * </p>
 * 
 * <h3>Validating with JSON Schema</h3>
 * 
 * <p>
 * Since version 1.3, mJson supports JSON Schema, draft 4. A schema is represented by the internal
 * class {@link mjson.Json.Schema}. To perform a validation, you have a instantiate a <code>Json.Schema</code>
 * using the factory method {@link mjson.Json.Schema} and then call its <code>validate</code> method
 * on a JSON instance:
 * </p>
 *  
 * <pre><code>
 * import mjson.Json;
 * import static mjson.Json.*;
 * ...
 * Json inputJson = Json.read(inputString);
 * Json schema = Json.schema(new URI("http://mycompany.com/schemas/model"));
 * Json errors = schema.validate(inputJson);
 * for (Json error : errors.asJsonList())
 * 	   System.out.println("Validation error " + err);
 * </code></pre>
 * @author Borislav Iordanov
 * @version 1.4
 */
public class Json implements java.io.Serializable
{
	private static final long serialVersionUID = 1L;

	/**
	 * <p>
	 * This interface defines how <code>Json</code> instances are constructed. There is a 
	 * default implementation for each kind of <code>Json</code> value, but you can provide
	 * your own implementation. For example, you might want a different representation of 
	 * an object than a regular <code>HashMap</code>. Or you might want string comparison to be
	 * case insensitive.
	 * </p>
	 *
	 * <p>
	 * In addition, the {@link #make(Object)} method allows you plug-in your own mapping
	 * of arbitrary Java objects to <code>Json</code> instances. You might want to implement
	 * a Java Beans to JSON mapping or any other JSON serialization that makes sense in your 
	 * project.
	 * </p>
	 * 
	 * <p>
	 * To avoid implementing all methods in that interface, you can extend the {@link DefaultFactory}
	 * default implementation and simply overwrite the ones you're interested in.
	 * </p>
	 * 
	 * <p>
	 * The factory implementation used by the <code>Json</code> classes is specified simply by calling
	 * the {@link #setGlobalFactory(Factory)} method. The factory is a static, global variable by default.
	 * If you need different factories in different areas of a single application, you may attach them
	 * to different threads of execution using the {@link #attachFactory(Factory)}. Recall a separate 
	 * copy of static variables is made per ClassLoader, so for example in a web application context, that
	 * global factory can be different for each web application (as Java web servers usually use a separate
	 * class loader per application). Thread-local factories are really a provision for special cases.
	 * </p>
	 * 
	 * @author Borislav Iordanov
	 *
	 */
    public static interface Factory 
    {
    	/**
    	 * Construct and return an object representing JSON <code>null</code>. Implementations are
    	 * free to cache a return the same instance. The resulting value must return
    	 * <code>true</code> from <code>isNull()</code> and <code>null</code> from
    	 * <code>getValue()</code>.
    	 * 
    	 * @return The representation of a JSON <code>null</code> value.
    	 */
        Json nil();
        
        /**
         * Construct and return a JSON boolean. The resulting value must return
    	 * <code>true</code> from <code>isBoolean()</code> and the passed
    	 * in parameter from <code>getValue()</code>.
         * @param value The boolean value.
         * @return A JSON with <code>isBoolean() == true</code>. Implementations
         * are free to cache and return the same instance for true and false.
         */
        Json bool(boolean value);
        
        /**
         * Construct and return a JSON string. The resulting value must return
    	 * <code>true</code> from <code>isString()</code> and the passed
    	 * in parameter from <code>getValue()</code>.
         * @param value The string to wrap as a JSON value.
         * @return A JSON element with the given string as a value.
         */
        Json string(String value);
        
        /**
         * Construct and return a JSON number. The resulting value must return
    	 * <code>true</code> from <code>isNumber()</code> and the passed
    	 * in parameter from <code>getValue()</code>.
    	 *  
         * @param value The numeric value.
         * @return Json instance representing that value.
         */
        Json number(Number value);
        
        /**
         * Construct and return a JSON object. The resulting value must return
    	 * <code>true</code> from <code>isObject()</code> and an implementation
    	 * of <code>java.util.Map</code> from <code>getValue()</code>.
    	 * 
    	 * @return An empty JSON object.
         */
        Json object();

        /**
         * Construct and return a JSON object. The resulting value must return
    	 * <code>true</code> from <code>isArray()</code> and an implementation
    	 * of <code>java.util.List</code> from <code>getValue()</code>.
    	 * 
    	 * @return An empty JSON array.
         */
        Json array();
        
        /**
         * Construct and return a JSON object. The resulting value can be of any
         * JSON type. The method is responsible for examining the type of its
         * argument and performing an appropriate mapping to a <code>Json</code>
         * instance. 
         * 
         * @param anything An arbitray Java object from which to construct a <code>Json</code>
         * element.
         * @return The newly constructed <code>Json</code> instance.
         */        
        Json make(Object anything);
    }

    /**
     * <p>
     * Represents JSON schema - a specific data format that a JSON entity must
     * follow. The idea of a JSON schema is very similar to XML. Its main purpose 
     * is validating input.    
     * </p>
     * 
     *  <p>
     *  More information about the various JSON schema specifications can be 
     *  found at http://json-schema.org. JSON Schema is an  IETF draft (v4 currently) and 
     *  our implementation follows this set of specifications. A JSON schema is specified 
     *  as a JSON object that contains keywords defined by the specification. Here are
     *  a few introductory materials:
     *  <ul>
     *  <li>http://jsonary.com/documentation/json-schema/ - 
     *  a very well-written tutorial covering the whole standard</li>
     *  <li>http://spacetelescope.github.io/understanding-json-schema/ - 
     *  online book, tutorial (Python/Ruby based)</li>
     *  </ul>
     *  </p>
     * @author Borislav Iordanov
     *
     */
    public static interface Schema
    {    	
    	/**
    	 * <p>
    	 * Validate a JSON document according to this schema. The validations attempts to
    	 * proceed even in the face of errors. The return value is always a <code>Json.object</code>
    	 * containing the boolean property <code>ok</code>. When <code>ok</code> is <code>true</code>,
    	 * the return object contains nothing else. When it is <code>false</code>, the return object
    	 * contains a property <code>errors</code> which is an array of error messages for all
    	 * detected schema violations.
    	 * </p>
    	 * 
    	 * @param document The input document.
    	 * @return <code>{"ok":true}</code> or <code>{"ok":false, errors:["msg1", "msg2", ...]}</code>
    	 */
    	Json validate(Json document);
    	
    	/**
    	 * <p>Possible options are: <code>ignoreDefaults:true|false</code>.
    	 * </p>
    	 * @return A newly created <code>Json</code> conforming to this schema.
    	 */
    	//Json generate(Json options);
    }

    static String fetchContent(URL url)
    {
    	java.io.Reader reader = null;
    	try
    	{
	    	reader = new java.io.InputStreamReader((java.io.InputStream)url.getContent());
	    	StringBuilder content = new StringBuilder();
	    	char [] buf = new char[1024];
	    	for (int n = reader.read(buf); n > -1; n = reader.read(buf))
	    	    content.append(buf, 0, n);
//	    	System.out.println("last reaad: " + new StringBuilder(buf))
	    	return content.toString();
    	}
    	catch (Exception ex)
    	{
    		throw new RuntimeException(ex);
    	}
    	finally
    	{
    		if (reader != null) try { reader.close(); } catch (Throwable t) { }
        }
    }
    
    static Json resolvePointer(String pointerRepresentation, Json top)
    {
    	String [] parts = pointerRepresentation.split("/");
    	Json result = top;
    	for (String p : parts)
    	{
    		// TODO: unescaping and decoding
    		if (p.length() == 0)
    			continue;
    		p = p.replace("~1", "/").replace("~0", "~");
    		if (result.isArray())
    			result = result.at(Integer.parseInt(p));
    		else if (result.isObject())
    			result = result.at(p);
    		else
    			throw new RuntimeException("Can't resolve pointer " + pointerRepresentation + 
    					" on document " + top.toString(200));
    	}
    	return result;
    }
    
    static URI makeAbsolute(URI base, String ref) throws Exception
    {
    	URI refuri;
    	if (base != null && base.getAuthority() != null && !new URI(ref).isAbsolute())
    	{
    		StringBuilder sb = new StringBuilder();
    		if (base.getScheme() != null)
    			sb.append(base.getScheme()).append("://");
    		sb.append(base.getAuthority());
    		if (!ref.startsWith("/"))
    		{
    			if (ref.startsWith("#"))
    				sb.append(base.getPath());
    			else
    			{
	    			int slashIdx = base.getPath().lastIndexOf('/');
	    			sb.append(slashIdx == -1 ? base.getPath() : base.getPath().substring(0,  slashIdx)).append("/");
    			}
    		}
			refuri = new URI(sb.append(ref).toString());
    	}
    	else if (base != null)
    		refuri = base.resolve(ref);
    	else
    		refuri = new URI(ref);
   		return refuri;
    }
    
    static Json resolveRef(URI base, 
    					   Json refdoc, 
    					   URI refuri,  
    					   Map<String, Json> resolved, 
    					   Map<Json, Json> expanded,
    					   Function<URI, Json> uriResolver) throws Exception
    {
    	if (refuri.isAbsolute() &&
    		 (base == null || !base.isAbsolute() ||
    			!base.getScheme().equals(refuri.getScheme()) ||
	    		!Objects.equals(base.getHost(), refuri.getHost()) ||
	    		base.getPort() != refuri.getPort() ||
	    		!base.getPath().equals(refuri.getPath())))
    	{
    		URI docuri = null;
    		refuri = refuri.normalize();    		
    		if (refuri.getHost() == null)
    			docuri = new URI(refuri.getScheme() + ":" + refuri.getPath());
    		else 
    			docuri = new URI(refuri.getScheme() + "://" + refuri.getHost() + 
    				((refuri.getPort() > -1) ? ":" + refuri.getPort() : "") +
    				refuri.getPath());	    		
    		refdoc = uriResolver.apply(docuri);
    		refdoc = expandReferences(refdoc, refdoc, docuri, resolved, expanded, uriResolver);
    	}
    	if (refuri.getFragment() == null)
    		return refdoc;
    	else
    		return resolvePointer(refuri.getFragment(), refdoc);
    }
    
    /**
     * <p>
     * Replace all JSON references, as per the http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03 
     * specification, by their referants. 
     * </p>
     * @param json
     * @param duplicate
     * @param done
     * @return
     */
	static Json expandReferences(Json json, 
								 Json topdoc, 
								 URI base, 
								 Map<String, Json> resolved, 
								 Map<Json, Json> expanded,
								 Function<URI, Json> uriResolver) throws Exception
	{
		if (expanded.containsKey(json)) return json;
		if (json.isObject())
		{
			if (json.has("id") && json.at("id").isString()) // change scope of nest references
			{
				base = base.resolve(json.at("id").asString());
			}
			
			if (json.has("$ref"))
			{
				URI refuri = makeAbsolute(base, json.at("$ref").asString()); // base.resolve(json.at("$ref").asString());
				Json ref = resolved.get(refuri.toString());
				if (ref == null)
				{
					ref = resolveRef(base, topdoc, refuri, resolved, expanded, uriResolver);
					resolved.put(refuri.toString(), ref);					
					ref = expandReferences(ref, topdoc, base, resolved, expanded, uriResolver);
					resolved.put(refuri.toString(), ref);
				}
				json = ref;
			}
			else 
			{
				Json O = Json.object();
				for (Map.Entry<String, Json> e : json.asJsonMap().entrySet())
					O.set(e.getKey(), expandReferences(e.getValue(), topdoc, base, resolved, expanded, uriResolver));
				json.with(O, new Json[0]);
			}
		}
		else if (json.isArray())
		{
//			Json A = Json.array();
			for (int i = 0; i < json.asJsonList().size(); i++)
			{
				//A.add(expandReferences(j, topdoc, base, resolved));
				Json el = expandReferences(json.at(i), topdoc, base, resolved, expanded, uriResolver);
				json.set(i, el);				
			}
//			return A;
		}
		expanded.put(json,  json);
		return json;
	}
    
    static class DefaultSchema implements Schema
    {
    	static interface Instruction extends Function<Json, Json>{}

        static Json maybeError(Json errors, Json E)
    		{ return E == null ? errors : (errors == null ? Json.array() : errors).with(E, new Json[0]); }

    	// Anything is valid schema
    	static Instruction any = new Instruction() { public Json apply(Json param) { return null; } };
    	
    	// Type validation    	
    	class IsObject implements Instruction { public Json apply(Json param) 
    		{ return param.isObject() ? null : Json.make(param.toString(maxchars)); } }
    	class IsArray implements Instruction { public Json apply(Json param) 
    		{ return param.isArray() ? null : Json.make(param.toString(maxchars)); } }
    	class IsString implements Instruction { public Json apply(Json param) 
    		{ return param.isString() ? null : Json.make(param.toString(maxchars)); } }    	
    	class IsBoolean implements Instruction { public Json apply(Json param) 
			{ return param.isBoolean() ? null : Json.make(param.toString(maxchars)); } }
    	class IsNull implements Instruction { public Json apply(Json param) 
			{ return param.isNull() ? null : Json.make(param.toString(maxchars)); } }    	
    	class IsNumber implements Instruction { public Json apply(Json param) 
			{ return param.isNumber() ? null : Json.make(param.toString(maxchars)); } }    	    	
    	class IsInteger implements Instruction { public Json apply(Json param) 
			{ return param.isNumber() && ((Number)param.getValue()) instanceof Integer  ? null : Json.make(param.toString(maxchars)); } }    	
    	    	
    	class CheckString implements Instruction
    	{
    		int min = 0, max = Integer.MAX_VALUE;
    		Pattern pattern;
    		
    		public Json apply(Json param)
    		{
    			Json errors = null;
    			if (!param.isString()) return errors;    			
    			String s = param.asString();
    	        final int size = s.codePointCount(0, s.length());
    			if (size < min || size > max)
					errors = maybeError(errors,Json.make("String  " + param.toString(maxchars) + 
							" has length outside of the permitted range [" + min + "," + max + "]."));
    			if (pattern != null && !pattern.matcher(s).matches())
					errors = maybeError(errors,Json.make("String  " + param.toString(maxchars) + 
							" does not match regex " + pattern.toString()));    				
    			return errors;
    		}	
    	}

    	class CheckNumber implements Instruction
    	{
    		double min = Double.NaN, max = Double.NaN, multipleOf = Double.NaN;
    		boolean exclusiveMin = false, exclusiveMax = false;
    		public Json apply(Json param)
    		{
    			Json errors = null;
    			if (!param.isNumber()) return errors;    			
    			double value = param.asDouble();
    			if (!Double.isNaN(min) && (value < min || exclusiveMin && value == min))
    				errors = maybeError(errors,Json.make("Number " + param + " is below allowed minimum " + min));    				
    			if (!Double.isNaN(max) && (value > max || exclusiveMax && value == max))
        			errors = maybeError(errors,Json.make("Number " + param + " is above allowed maximum " + max));
    			if (!Double.isNaN(multipleOf) && (value / multipleOf) % 1 != 0)
    				errors = maybeError(errors,Json.make("Number " + param + " is not a multiple of  " + multipleOf));    				
    			return errors;
    		}
    	}
    	
       	class CheckArray implements Instruction
       	{
       		int min = 0, max = Integer.MAX_VALUE;
       		Boolean uniqueitems = null;
       		Instruction additionalSchema = any;
       		Instruction schema;
       		ArrayList<Instruction> schemas;
       		
    		public Json apply(Json param)
    		{
    			Json errors = null;
    			if (!param.isArray()) return errors;
				if (schema == null && schemas == null && additionalSchema == null) // no schema specified
					return errors;    			
    			int size = param.asJsonList().size();
    			for (int i = 0; i < size; i++)
    			{
    				Instruction S = schema != null ? schema
    						: (schemas != null && i < schemas.size()) ? schemas.get(i) : additionalSchema;
    				if (S == null)
    					errors = maybeError(errors,Json.make("Additional items are not permitted: " + 
    									param.at(i) + " in " + param.toString(maxchars)));
    				else
    					errors = maybeError(errors, S.apply(param.at(i)));
    				if (uniqueitems != null && uniqueitems && param.asJsonList().lastIndexOf(param.at(i)) > i)
    					errors = maybeError(errors,Json.make("Element " + param.at(i) + " is duplicate in array."));
    			}
    			if (size < min || size > max)
					errors = maybeError(errors,Json.make("Array  " + param.toString(maxchars) + 
							" has number of elements outside of the permitted range [" + min + "," + max + "]."));    				
    			return errors;
    		}
       	}
       	
       	class CheckPropertyPresent implements Instruction
       	{
       		String propname;
       		public CheckPropertyPresent(String propname) { this.propname = propname; }
       		public Json apply(Json param)
       		{
       			if (!param.isObject()) return null;
       			if (param.has(propname)) return null;
       			else return Json.array().add(Json.make("Required property " + propname + 
       					" missing from object " + param.toString(maxchars)));
       		}
       	}
       	
    	class CheckObject implements Instruction
    	{
    		int min = 0, max = Integer.MAX_VALUE;
    		HashSet<String> checked = new HashSet<String>();
    		Instruction additionalSchema = any;
    		ArrayList<Instruction> props = new ArrayList<Instruction>();
    		ArrayList<Instruction> patternProps = new ArrayList<Instruction>();
    		
        	// Object validation
        	class CheckProperty implements Instruction 
        	{ 
        		String name;
        		Instruction schema; 
        		public CheckProperty(String name, Instruction schema) 
        			{ this.name = name; this.schema = schema; }
        		public Json apply(Json param)
        		{    	
        			Json value = param.at(name);
        			if (value == null)
        				return null;
        			else
        			{
        				checked.add(name);
        				return schema.apply(param.at(name));
        			}
        		} 
        	}
        	
        	class CheckPatternProperty implements Instruction 
        	{ 
        		Pattern pattern;
        		Instruction schema; 
        		public CheckPatternProperty(String pattern, Instruction schema) 
        			{ this.pattern = Pattern.compile(pattern); this.schema = schema; }
        		public Json apply(Json param)
        		{    	
        			Json errors = null;
        			for (Map.Entry<String, Json> e : param.asJsonMap().entrySet())
        				if (pattern.matcher(e.getKey()).find())
        				{
        					errors = maybeError(errors, schema.apply(e.getValue()));
        					checked.add(e.getKey());
        				}
        			return errors;
        		} 
        	}
    		
    		public Json apply(Json param)
    		{
    			Json errors = null;
    			if (!param.isObject()) return errors;
    			checked.clear();
    			for (Instruction I : props)
    				errors = maybeError(errors, I.apply(param));
    			for (Instruction I : patternProps)
    				errors = maybeError(errors, I.apply(param));    			    			
    			if (additionalSchema != any) for (Map.Entry<String, Json> e : param.asJsonMap().entrySet())
    				if (!checked.contains(e.getKey()))
        				errors = maybeError(errors, additionalSchema == null ? 
        							Json.make("Extra property '" + e.getKey() + 
        									  "', schema doesn't allow any properties not explicitly defined:" + 
        									  param.toString(maxchars)) 
        							: additionalSchema.apply(e.getValue()));    	
    			if (param.asJsonMap().size() < min)
    				errors = maybeError(errors, Json.make("Object " + param.toString(maxchars) + 
    							" has fewer than the permitted " + min + "  number of properties."));
    			if (param.asJsonMap().size() > max)
    				errors = maybeError(errors, Json.make("Object " + param.toString(maxchars) + 
    							" has more than the permitted " + min + "  number of properties."));
    			return errors;
    		}
    	}
    	
    	class Sequence implements Instruction
    	{
    		ArrayList<Instruction> seq = new ArrayList<Instruction>();
    		public Json apply(Json param)
    		{
    			Json errors = null;
    			for (Instruction I : seq)
    				errors = maybeError(errors, I.apply(param));
    			return errors;
    		}
    		public Sequence add(Instruction I) { seq.add(I); return this; } 
    	}
    	
    	class CheckType implements Instruction
    	{
    		Json types;
    		public CheckType(Json types) { this.types = types; }
    		public Json apply(Json param)
    		{
    			String ptype = param.isString() ? "string" :
    						   param.isObject() ? "object" :
    						   param.isArray() ? "array" :
    						   param.isNumber() ? "number" :
    						   param.isNull() ? "null" : "boolean";
    			for (Json type : types.asJsonList())
    				if (type.asString().equals(ptype)) 
    					return null;
    				else if (type.asString().equals("integer") && 
    						 param.isNumber() && 
    						 param.asDouble() % 1 == 0)
    					return null;
   				return Json.array().add(Json.make("Type mistmatch for " + param.toString(maxchars) + 
   								", allowed types: " + types));
    		}    		
    	}
    	
    	class CheckEnum implements Instruction
    	{
    		Json theenum;
    		public CheckEnum(Json theenum) { this.theenum = theenum; }
    		public Json apply(Json param)
    		{    			
    			for (Json option : theenum.asJsonList())
    				if (param.equals(option))
    					return null;
    			return Json.array().add("Element " + param.toString(maxchars) + 
    					" doesn't match any of enumerated possibilities " + theenum);    			
    		}    		
    	}
    	
    	class CheckAny implements Instruction
    	{
    		ArrayList<Instruction> alternates = new ArrayList<Instruction>();
    		Json schema;
    		public Json apply(Json param)
    		{    			
    			for (Instruction I : alternates)    				
    				if (I.apply(param) == null)
    					return null;
