// Path: src/com/winterwell/es/ESUtils.java
// public class ESUtils {
// 
// 	
// 	/**
// 	 * See http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/
// 	 * mapping-core-types.html
// 	 * 
// 	 * @return an ElasticSearch type builder, which (being a Map) can be
// 	 *         submitted to PutMapping. This is just a convenience for <code>new ESType()</code>.
// 	 */
// 	public static ESType type() {
// 		return new ESType();
// 	}
// 
// 	/**
// 	See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#_reserved_characters
// 		 */
// 	public static String escapeWord(String s) {
// 		if (s==null) return null;
// 		String chars = "+-&|!(){}[]^\"~*?:\\/";
// 		String s2 = StrUtils.escape(s, chars, '\\');
// 		// Note "&" will also have to be url-encoded!
// 		return s2;
// 	}
// 	
// 	/**
// 	 * HACK convert an ES class into a json object.
// 	 * TODO replace these ES classes with something nicer.
// 	 * Preferably auto-generated from the ES source
// 	 * @param qb
// 	 * @return
// 	 */
// 	public static Map jobj(ToXContent qb) {
// 		String s = qb.toString();
// 		return FlexiGson.fromJSON(s);
// 	}
// 
// 	/**
// 	 * ElasticSearch gets upset by many keys (they explode the schema)
// 	 * so we can't store maps the obvious way.
// 	 * @param map
// 	 * @param k What to call the key-key in the output mini-maps, e.g. "key" or "k"
// 	 * @param v What to call the value-key in the output mini-maps, e.g. "value" or "v"
// 	 * @return a sort of association list, where each key:value mapping
// 	 * is turned into a {k:key, v:value} map in a list
// 	 */
// 	public static List<Map<String,Object>> assocListFromMap(Map<String, ?> map, String k, String v) {
// 		List<Map<String, Object>> list = Containers.apply(map.entrySet(), 
// 				e -> new ArrayMap(k, e.getKey(), v, e.getValue())
// 				);
// 		return list;
// 	}
// 
// 	public static <V> Map<String, V> mapFromAssocList(List<Map<String,Object>> topmapList, 
// 			String k, String v) 
// 	{
// 		Map<String, V> map = new HashMap();
// 		for (Map<String, Object> kv : topmapList) {
// 			map.put((String)kv.get(k), (V) kv.get(v));
// 		}
// 		return map;
// 	}
// 
// }
// 
// Path: src/shadoworg/elasticsearch/index/query/QueryBuilder/QueryBuilder.java
// @Deprecated
// public class QueryBuilder implements ToXContent {
// 
// }


import java.util.Map;
import com.winterwell.es.ESUtils;
import com.winterwell.utils.containers.ArrayMap;
import com.winterwell.utils.containers.Containers;
import com.winterwell.utils.web.IHasJson;
import shadoworg.elasticsearch.index.query.QueryBuilder.QueryBuilder;
package com.winterwell.es.client.query;




/**
 * Base class for query-builders. For common cases use the convenience methods in {@link ESQueryBuilders}
 * @author daniel
 *
 */
public class ESQueryBuilder implements IHasJson, Cloneable {

	@Override
	public ESQueryBuilder clone() {
		ESQueryBuilder clone = new ESQueryBuilder(new ArrayMap(jobj));
		return clone;
	}
	
	@Override
	public String toString() {
		boolean prelock = lock; // HACK - don't accidentally lock by calling toJson2
		// NB: this prelock/lock code is not thread safe, but since its only sanity-checking code, that's OK.
		String s = "ESQueryBuilder"+toJSONString();
		lock = prelock;
		return s;
	}
	
	Map jobj;
	protected transient boolean lock;
	/**
	 * Many queries have one top level key, and the sub-object is where the settings go 
	 */
	protected Map props;

	/**
	 * Direct access to the jobject map.
	 * @throws IllegalStateException see {@link #lockCheck()}
	 */
	public Map getUnderlyingMap() {
		lockCheck();
		return jobj;
	}
	
	/**
	 * 
	 * @param query Used directly! beware of side effects
	 */
	public ESQueryBuilder(Map query) {
		this.jobj = query;
		// Convenience hack
		if (query.size()==1) {
			this.props = (Map) Containers.first(jobj.values());	
		}
	}
	
// Path: src/com/winterwell/es/ESUtils.java
// public class ESUtils {
// 
// 	
// 	/**
// 	 * See http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/
// 	 * mapping-core-types.html
// 	 * 
// 	 * @return an ElasticSearch type builder, which (being a Map) can be
// 	 *         submitted to PutMapping. This is just a convenience for <code>new ESType()</code>.
// 	 */
// 	public static ESType type() {
// 		return new ESType();
// 	}
// 
// 	/**
// 	See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#_reserved_characters
// 		 */
// 	public static String escapeWord(String s) {
// 		if (s==null) return null;
// 		String chars = "+-&|!(){}[]^\"~*?:\\/";
// 		String s2 = StrUtils.escape(s, chars, '\\');
// 		// Note "&" will also have to be url-encoded!
// 		return s2;
// 	}
// 	
// 	/**
// 	 * HACK convert an ES class into a json object.
// 	 * TODO replace these ES classes with something nicer.
// 	 * Preferably auto-generated from the ES source
// 	 * @param qb
// 	 * @return
// 	 */
// 	public static Map jobj(ToXContent qb) {
// 		String s = qb.toString();
// 		return FlexiGson.fromJSON(s);
// 	}
// 
// 	/**
// 	 * ElasticSearch gets upset by many keys (they explode the schema)
// 	 * so we can't store maps the obvious way.
// 	 * @param map
// 	 * @param k What to call the key-key in the output mini-maps, e.g. "key" or "k"
// 	 * @param v What to call the value-key in the output mini-maps, e.g. "value" or "v"
// 	 * @return a sort of association list, where each key:value mapping
// 	 * is turned into a {k:key, v:value} map in a list
// 	 */
// 	public static List<Map<String,Object>> assocListFromMap(Map<String, ?> map, String k, String v) {
// 		List<Map<String, Object>> list = Containers.apply(map.entrySet(), 
// 				e -> new ArrayMap(k, e.getKey(), v, e.getValue())
// 				);
// 		return list;
// 	}
// 
// 	public static <V> Map<String, V> mapFromAssocList(List<Map<String,Object>> topmapList, 
// 			String k, String v) 
// 	{
// 		Map<String, V> map = new HashMap();
// 		for (Map<String, Object> kv : topmapList) {
// 			map.put((String)kv.get(k), (V) kv.get(v));
// 		}
// 		return map;
// 	}
// 
// }
// 
// Path: src/shadoworg/elasticsearch/index/query/QueryBuilder/QueryBuilder.java
// @Deprecated
// public class QueryBuilder implements ToXContent {
// 
// }


// Path: src/com/winterwell/es/client/query/ESQueryBuilder.java
import java.util.Map;
import com.winterwell.es.ESUtils;
import com.winterwell.utils.containers.ArrayMap;
import com.winterwell.utils.containers.Containers;
import com.winterwell.utils.web.IHasJson;
import shadoworg.elasticsearch.index.query.QueryBuilder.QueryBuilder;

package com.winterwell.es.client.query;




/**
 * Base class for query-builders. For common cases use the convenience methods in {@link ESQueryBuilders}
 * @author daniel
 *
 */
public class ESQueryBuilder implements IHasJson, Cloneable {

	@Override
	public ESQueryBuilder clone() {
		ESQueryBuilder clone = new ESQueryBuilder(new ArrayMap(jobj));
		return clone;
	}
	
	@Override
	public String toString() {
		boolean prelock = lock; // HACK - don't accidentally lock by calling toJson2
		// NB: this prelock/lock code is not thread safe, but since its only sanity-checking code, that's OK.
		String s = "ESQueryBuilder"+toJSONString();
		lock = prelock;
		return s;
	}
	
	Map jobj;
	protected transient boolean lock;
	/**
	 * Many queries have one top level key, and the sub-object is where the settings go 
	 */
	protected Map props;

	/**
	 * Direct access to the jobject map.
	 * @throws IllegalStateException see {@link #lockCheck()}
	 */
	public Map getUnderlyingMap() {
		lockCheck();
		return jobj;
	}
	
	/**
	 * 
	 * @param query Used directly! beware of side effects
	 */
	public ESQueryBuilder(Map query) {
		this.jobj = query;
		// Convenience hack
		if (query.size()==1) {
			this.props = (Map) Containers.first(jobj.values());	
		}
	}
	
	public ESQueryBuilder(QueryBuilder query) {