/* Copyright 2013-2015 www.snakerflow.com.
 *
 * 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 org.snaker.engine.access.transaction;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snaker.engine.SnakerException;
import org.snaker.engine.helper.AssertHelper;
import org.snaker.engine.helper.StringHelper;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * 事务拦截器，用于产生业务逻辑类的代理类
 * @author yuqs
 * @since 1.0
 */
public abstract class TransactionInterceptor implements MethodInterceptor {
	private static final Logger log = LoggerFactory.getLogger(TransactionInterceptor.class);
	/**
	 * 需要拦截的事务方法集合
	 */
	private static final List<String> txMethods = new ArrayList<String>();
	static {
		txMethods.add("start*");
		txMethods.add("execute*");
		txMethods.add("finish*");
		txMethods.add("terminate*");
		txMethods.add("take*");
		txMethods.add("create*");
		txMethods.add("save*");
		txMethods.add("delete*");
		txMethods.add("remove*");
		txMethods.add("update*");
		txMethods.add("deploy*");
		txMethods.add("undeploy*");
		txMethods.add("redeploy*");
		txMethods.add("complete*");
		txMethods.add("assign*");
		txMethods.add("withdraw*");
		txMethods.add("reject*");
		txMethods.add("add*");
		txMethods.add("cascade*");
		txMethods.add("get*");
	}

	/**
	 * 使用Cglib产生业务类的代理
	 * @param clazz
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> T getProxy(Class<T> clazz) {
		return (T)Enhancer.create(clazz, this);
	}

	/**
	 * 方法执行的拦截器，拦截条件为：
	 * 1、数据库访问类是DBTransaction的实现类
	 * 2、方法名称匹配初始化的事务方法列表
	 */
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		Object result = null;
		TransactionStatus status = null;
		if(isMatch(method.getName())) {
			if(log.isDebugEnabled()) {
				log.debug("intercept method is[name="  + method.getName() + "]");
			}
			try {
				status = getTransaction();
				AssertHelper.notNull(status);
				//调用具体无事务支持的业务逻辑
				result = proxy.invokeSuper(obj, args);
				//如果整个执行过程无异常抛出，则提交TransactionStatus持有的transaction对象
				if(status.isNewTransaction()) {
					commit(status);
				}
			} catch (Exception e) {
				rollback(status);
				throw new SnakerException(e);
			}
		} else {
			if(log.isDebugEnabled()) {
				log.debug("****don't intercept method is[name="  + method.getName() + "]");
			}
			result = proxy.invokeSuper(obj, args);
		}
		return result;
	}

	/**
	 * 根据方法名称，匹配所有初始化的需要事务拦截的方法
	 * @param methodName
	 * @return
	 */
	private boolean isMatch(String methodName) {
		for(String pattern : txMethods) {
			if(StringHelper.simpleMatch(pattern, methodName)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 初始化事务拦截器，设置访问对象
	 * @param accessObject
	 */
	public abstract void initialize(Object accessObject);

	/**
	 * 返回当前事务状态
	 * @return
	 */
	protected abstract TransactionStatus getTransaction();

	/**
	 * 提交当前事务状态
	 * @param status
	 */
	protected abstract void commit(TransactionStatus status);

	/**
	 * 回滚当前事务状态
	 * @param status
	 */
	protected abstract void rollback(TransactionStatus status);
}


/* Copyright 2013-2015 www.snakerflow.com.
 *
 * 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 org.snaker.engine.core;

import java.util.List;
import java.util.Map;

import org.snaker.engine.Completion;
import org.snaker.engine.IOrderService;
import org.snaker.engine.SnakerEngine;
import org.snaker.engine.access.QueryFilter;
import org.snaker.engine.entity.*;
import org.snaker.engine.entity.Process;
import org.snaker.engine.helper.AssertHelper;
import org.snaker.engine.helper.DateHelper;
import org.snaker.engine.helper.JsonHelper;
import org.snaker.engine.helper.StringHelper;
import org.snaker.engine.model.ProcessModel;

/**
 * 流程实例业务类
 * @author yuqs
 * @since 1.0
 */
public class OrderService extends AccessService implements IOrderService {
	/**
	 * 创建活动实例
	 * @see org.snaker.engine.core.OrderService#createOrder(Process, String, Map, String, String)
	 */
	public Order createOrder(Process process, String operator, Map<String, Object> args) {
		return createOrder(process, operator, args, null, null);
	}

	/**
	 * 创建活动实例
	 */
	public Order createOrder(Process process, String operator, Map<String, Object> args,
			String parentId, String parentNodeName) {
		Order order = new Order();
		order.setId(StringHelper.getPrimaryKey());
		order.setParentId(parentId);
		order.setParentNodeName(parentNodeName);
		order.setCreateTime(DateHelper.getTime());
		order.setLastUpdateTime(order.getCreateTime());
		order.setCreator(operator);
		order.setLastUpdator(order.getCreator());
		order.setProcessId(process.getId());
		ProcessModel model = process.getModel();
		if(model != null && args != null) {
			if(StringHelper.isNotEmpty(model.getExpireTime())) {
				String expireTime = DateHelper.parseTime(args.get(model.getExpireTime()));
				order.setExpireTime(expireTime);
			}
            String orderNo = (String)args.get(SnakerEngine.ID);
            if(StringHelper.isNotEmpty(orderNo)) {
                order.setOrderNo(orderNo);
            } else {
                order.setOrderNo(model.getGenerator().generate(model));
            }
		}

		order.setVariable(JsonHelper.toJson(args));
		saveOrder(order);
		return order;
	}

    /**
     * 向活动实例临时添加全局变量数据
     * @param orderId 实例id
     * @param args 变量数据
     */
    public void addVariable(String orderId, Map<String, Object> args) {
        Order order = access().getOrder(orderId);
        Map<String, Object> data = order.getVariableMap();
        data.putAll(args);
        order.setVariable(JsonHelper.toJson(data));
        access().updateOrderVariable(order);
    }

    /**
	 * 创建实例的抄送
	 */
	public void createCCOrder(String orderId, String creator, String... actorIds) {
		for(String actorId : actorIds) {
			CCOrder ccorder = new CCOrder();
			ccorder.setOrderId(orderId);
			ccorder.setActorId(actorId);
            ccorder.setCreator(creator);
			ccorder.setStatus(STATE_ACTIVE);
            ccorder.setCreateTime(DateHelper.getTime());
			access().saveCCOrder(ccorder);
		}
	}

	/**
	 * 流程实例数据会保存至活动实例表、历史实例表
	 */
	public void saveOrder(Order order) {
		HistoryOrder history = new HistoryOrder(order);
		history.setOrderState(STATE_ACTIVE);
		access().saveOrder(order);
		access().saveHistory(history);
	}

	/**
	 * 更新活动实例的last_Updator、last_Update_Time、expire_Time、version、variable
	 */
	public void updateOrder(Order order) {
		access().updateOrder(order);
	}

	/**
	 * 更新抄送记录状态为已阅
	 */
	public void updateCCStatus(String orderId, String... actorIds) {
        List<CCOrder> ccorders = access().getCCOrder(orderId, actorIds);
        AssertHelper.notNull(ccorders);
        for(CCOrder ccorder : ccorders) {
            ccorder.setStatus(STATE_FINISH);
            ccorder.setFinishTime(DateHelper.getTime());
            access().updateCCOrder(ccorder);
        }
	}

	/**
	 * 删除指定的抄送记录
	 */
	public void deleteCCOrder(String orderId, String actorId) {
        List<CCOrder> ccorders = access().getCCOrder(orderId, actorId);
		AssertHelper.notNull(ccorders);
        for(CCOrder ccorder : ccorders) {
		    access().deleteCCOrder(ccorder);
        }
	}

	/**
	 * 删除活动流程实例数据，更新历史流程实例的状态、结束时间
	 */
	public void complete(String orderId) {
		Order order = access().getOrder(orderId);
		HistoryOrder history = access().getHistOrder(orderId);
		history.setOrderState(STATE_FINISH);
		history.setEndTime(DateHelper.getTime());

		access().updateHistory(history);
		access().deleteOrder(order);
        Completion completion = getCompletion();
        if(completion != null) {
            completion.complete(history);
        }
	}

	/**
	 * 强制中止流程实例
	 * @see org.snaker.engine.core.OrderService#terminate(String, String)
	 */
	public void terminate(String orderId) {
		terminate(orderId, null);
	}

	/**
	 * 强制中止活动实例,并强制完成活动任务
	 */
	public void terminate(String orderId, String operator) {
		SnakerEngine engine = ServiceContext.getEngine();
		List<Task> tasks = engine
				.query()
				.getActiveTasks(new QueryFilter().setOrderId(orderId));
		for(Task task : tasks) {
			engine.task().complete(task.getId(), operator);
		}
		Order order = access().getOrder(orderId);
		HistoryOrder history = new HistoryOrder(order);
		history.setOrderState(STATE_TERMINATION);
		history.setEndTime(DateHelper.getTime());

		access().updateHistory(history);
		access().deleteOrder(order);
        Completion completion = getCompletion();
        if(completion != null) {
            completion.complete(history);
        }
	}

    /**
     * 激活已完成的历史流程实例
     * @param orderId 实例id
     * @return 活动实例对象
     */
    public Order resume(String orderId) {
        HistoryOrder historyOrder = access().getHistOrder(orderId);
        Order order = historyOrder.undo();
        access().saveOrder(order);
        historyOrder.setOrderState(STATE_ACTIVE);
        access().updateHistory(historyOrder);

        SnakerEngine engine = ServiceContext.getEngine();
        List<HistoryTask> histTasks = access().getHistoryTasks(null,
                new QueryFilter().setOrderId(orderId));
        if(histTasks != null && !histTasks.isEmpty()) {
            HistoryTask histTask = histTasks.get(0);
            engine.task().resume(histTask.getId(), histTask.getOperator());
        }
        return order;
    }

	/**
	 * 级联删除指定流程实例的所有数据：
	 * 1.wf_order,wf_hist_order
	 * 2.wf_task,wf_hist_task
	 * 3.wf_task_actor,wf_hist_task_actor
	 * 4.wf_cc_order
	 * @param id 实例id
	 */
	public void cascadeRemove(String id) {
		HistoryOrder historyOrder = access().getHistOrder(id);
		AssertHelper.notNull(historyOrder);
		List<Task> activeTasks = access().getActiveTasks(null, new QueryFilter().setOrderId(id));
		List<HistoryTask> historyTasks = access().getHistoryTasks(null, new QueryFilter().setOrderId(id));
		for(Task task : activeTasks) {
			access().deleteTask(task);
		}
		for(HistoryTask historyTask : historyTasks) {
			access().deleteHistoryTask(historyTask);
		}
		List<CCOrder> ccOrders = access().getCCOrder(id);
		for(CCOrder ccOrder : ccOrders) {
			access().deleteCCOrder(ccOrder);
		}

		Order order = access().getOrder(id);
		access().deleteHistoryOrder(historyOrder);
		if(order != null) {
			access().deleteOrder(order);
		}
	}
}


/* Copyright 2013-2015 www.snakerflow.com.
 *
 * 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 org.snaker.engine.access;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Types;
import java.util.*;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snaker.engine.DBAccess;
import org.snaker.engine.SnakerException;
import org.snaker.engine.access.dialect.Dialect;
import org.snaker.engine.access.jdbc.JdbcHelper;
import org.snaker.engine.core.ServiceContext;
import org.snaker.engine.entity.CCOrder;
import org.snaker.engine.entity.HistoryOrder;
import org.snaker.engine.entity.HistoryTask;
import org.snaker.engine.entity.HistoryTaskActor;
import org.snaker.engine.entity.Order;
import org.snaker.engine.entity.Process;
import org.snaker.engine.entity.Surrogate;
import org.snaker.engine.entity.Task;
import org.snaker.engine.entity.TaskActor;
import org.snaker.engine.entity.WorkItem;
import org.snaker.engine.helper.ClassHelper;
import org.snaker.engine.helper.ConfigHelper;
import org.snaker.engine.helper.StringHelper;

/**
 * 抽象数据库访问类
 * 封装SQL语句的构造
 * @author yuqs
 * @since 1.0
 */
public abstract class AbstractDBAccess implements DBAccess {
    private static final Logger log = LoggerFactory.getLogger(AbstractDBAccess.class);
    protected static final String KEY_SQL = "SQL";
	protected static final String KEY_ARGS = "ARGS";
	protected static final String KEY_TYPE = "TYPE";
	protected static final String KEY_ENTITY = "ENTITY";

	protected static final String KEY_SU = "SU";
	protected static final String SAVE = "SAVE";
	protected static final String UPDATE = "UPDATE";

	protected static final String PROCESS_INSERT = "insert into wf_process (id,name,display_Name,type,instance_Url,state,version,create_Time,creator) values (?,?,?,?,?,?,?,?,?)";
	protected static final String PROCESS_UPDATE = "update wf_process set name=?, display_Name=?,state=?,instance_Url=?,create_Time=?,creator=? where id=? ";
	protected static final String PROCESS_DELETE = "delete from wf_process where id = ?";
	protected static final String PROCESS_UPDATE_BLOB = "update wf_process set content=? where id=?";
	protected static final String PROCESS_UPDATE_TYPE = "update wf_process set type=? where id=?";

	protected static final String ORDER_INSERT = "insert into wf_order (id,process_Id,creator,create_Time,parent_Id,parent_Node_Name,expire_Time,last_Update_Time,last_Updator,order_No,variable,version) values (?,?,?,?,?,?,?,?,?,?,?,?)";
	protected static final String ORDER_UPDATE = "update wf_order set last_Updator=?, last_Update_Time=?, variable = ?, expire_Time=?, version = version + 1 where id=? and version = ?";
	protected static final String ORDER_DELETE = "delete from wf_order where id = ?";
	protected static final String ORDER_HISTORY_INSERT = "insert into wf_hist_order (id,process_Id,order_State,creator,create_Time,end_Time,parent_Id,expire_Time,order_No,variable) values (?,?,?,?,?,?,?,?,?,?)";
	protected static final String ORDER_HISTORY_UPDATE = "update wf_hist_order set order_State = ?, end_Time = ?, variable = ? where id = ? ";
	protected static final String ORDER_HISTORY_DELETE = "delete from wf_hist_order where id = ?";

	protected static final String CCORDER_INSERT = "insert into wf_cc_order (order_Id, actor_Id, creator, create_Time, status) values (?, ?, ?, ?, ?)";
	protected static final String CCORDER_UPDATE = "update wf_cc_order set status = ?, finish_Time = ? where order_Id = ? and actor_Id = ?";
	protected static final String CCORDER_DELETE = "delete from wf_cc_order where order_Id = ? and actor_Id = ?";

	protected static final String TASK_INSERT = "insert into wf_task (id,order_Id,task_Name,display_Name,task_Type,perform_Type,operator,create_Time,finish_Time,expire_Time,action_Url,parent_Task_Id,variable,version) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
	protected static final String TASK_UPDATE = "update wf_task set finish_Time=?, operator=?, variable=?, expire_Time=?, action_Url=?, version = version + 1 where id=? and version = ?";
	protected static final String TASK_DELETE = "delete from wf_task where id = ?";
	protected static final String TASK_HISTORY_INSERT = "insert into wf_hist_task (id,order_Id,task_Name,display_Name,task_Type,perform_Type,task_State,operator,create_Time,finish_Time,expire_Time,action_Url,parent_Task_Id,variable) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
	protected static final String TASK_HISTORY_DELETE = "delete from wf_hist_task where id = ?";

	protected static final String TASK_ACTOR_INSERT = "insert into wf_task_actor (task_Id, actor_Id) values (?, ?)";
	protected static final String TASK_ACTOR_DELETE = "delete from wf_task_actor where task_Id = ?";
	protected static final String TASK_ACTOR_REDUCE = "delete from wf_task_actor where task_Id = ? and actor_Id = ?";
	protected static final String TASK_ACTOR_HISTORY_INSERT = "insert into wf_hist_task_actor (task_Id, actor_Id) values (?, ?)";
	protected static final String TASK_ACTOR_HISTORY_DELETE = "delete from wf_hist_task_actor where task_Id = ?";

	protected static final String QUERY_VERSION = "select max(version) from wf_process ";
	protected static final String QUERY_PROCESS = "select id,name,display_Name,type,instance_Url,state, content, version,create_Time,creator from wf_process ";
	protected static final String QUERY_ORDER = "select o.id,o.process_Id,o.creator,o.create_Time,o.parent_Id,o.parent_Node_Name,o.expire_Time,o.last_Update_Time,o.last_Updator,o.priority,o.order_No,o.variable, o.version from wf_order o ";
	protected static final String QUERY_TASK = "select id,order_Id,task_Name,display_Name,task_Type,perform_Type,operator,create_Time,finish_Time,expire_Time,action_Url,parent_Task_Id,variable, version from wf_task ";
	protected static final String QUERY_TASK_ACTOR = "select task_Id, actor_Id from wf_task_actor ";
	protected static final String QUERY_CCORDER = "select order_Id, actor_Id, creator, create_Time, finish_Time, status from wf_cc_order ";

	protected static final String QUERY_HIST_ORDER = "select o.id,o.process_Id,o.order_State,o.priority,o.creator,o.create_Time,o.end_Time,o.parent_Id,o.expire_Time,o.order_No,o.variable from wf_hist_order o ";
	protected static final String QUERY_HIST_TASK = "select id,order_Id,task_Name,display_Name,task_Type,perform_Type,task_State,operator,create_Time,finish_Time,expire_Time,action_Url,parent_Task_Id,variable from wf_hist_task ";
	protected static final String QUERY_HIST_TASK_ACTOR = "select task_Id, actor_Id from wf_hist_task_actor ";

	/**委托代理CRUD*/
	protected static final String SURROGATE_INSERT = "insert into wf_surrogate (id, process_Name, operator, surrogate, odate, sdate, edate, state) values (?,?,?,?,?,?,?,?)";
	protected static final String SURROGATE_UPDATE = "update wf_surrogate set process_Name=?, surrogate=?, odate=?, sdate=?, edate=?, state=? where id = ?";
	protected static final String SURROGATE_DELETE = "delete from wf_surrogate where id = ?";
	protected static final String SURROGATE_QUERY = "select id, process_Name, operator, surrogate, odate, sdate, edate, state from wf_surrogate";

	protected Dialect dialect;

	/**
	 * 是否为ORM框架，用以标识对象直接持久化
	 * @return boolean
	 */
	public abstract boolean isORM();
	/**
	 * 保存或更新对象
	 * isORM为true，则参数map只存放对象
	 * isORM为false，则参数map需要放入SQL、ARGS、TYPE
	 * @param map 需要持久化的数据
	 */
	public abstract void saveOrUpdate(Map<String, Object> map);

	public void initialize(Object accessObject) {

	}

	/**
	 * isORM为false，需要构造map传递给实现类
	 * @param sql 需要执行的sql语句
	 * @param args sql语句中的参数列表
	 * @param type sql语句中的参数类型
	 * @return 构造的map
	 */
	private Map<String, Object> buildMap(String sql, Object[] args, int[] type) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put(KEY_SQL, sql);
		map.put(KEY_ARGS, args);
		map.put(KEY_TYPE, type);
		return map;
	}

	/**
	 * isORM为true，只存放对象传递给orm框架
	 * @param entity 实体对象
     * @param su 保存或更新的标识
	 * @return 构造的map
	 */
	private Map<String, Object> buildMap(Object entity, String su) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put(KEY_ENTITY, entity);
		map.put(KEY_SU, su);
		return map;
	}

	/**
	 * 获取数据库方言
     * 根据数据库连接的DatabaseMetaData获取数据库厂商，自动适配具体的方言
     * 当数据库类型未提供支持时无法自动获取方言，建议通过配置完成
	 * @return 方言对象
	 */
	protected Dialect getDialect() {
        if(dialect != null) return dialect;
		dialect = ServiceContext.getContext().find(Dialect.class);
		if(dialect == null) {
			try {
				dialect = JdbcHelper.getDialect(getConnection());
			} catch (Exception e) {
				log.error("Unable to find the available dialect.Please configure dialect to snaker.xml");
			}
		}
		return dialect;
	}

	/**
	 * 由于process中涉及blob字段，未对各种框架统一，所以process操作交给具体的实现类处理
	 */
	public void saveProcess(Process process) {
		if(isORM()) {
			saveOrUpdate(buildMap(process, SAVE));
		} else {
			Object[] args = new Object[]{process.getId(), process.getName(), process.getDisplayName(), process.getType(),
					process.getInstanceUrl(), process.getState(), process.getVersion(), process.getCreateTime(), process.getCreator()};
			int[] type = new int[]{Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.INTEGER,
					Types.VARCHAR, Types.INTEGER, Types.INTEGER, Types.VARCHAR, Types.VARCHAR};
			saveOrUpdate(buildMap(PROCESS_INSERT, args, type));
		}
	}
	/**
	 * 由于process中涉及blob字段，未对各种框架统一，所以process操作交给具体的实现类处理
	 */
	public void updateProcess(Process process) {
		if(isORM()) {
			saveOrUpdate(buildMap(process, UPDATE));
		} else {
			Object[] args = new Object[]{process.getName(), process.getDisplayName(), process.getState(),
					process.getInstanceUrl(), process.getCreateTime(), process.getCreator(), process.getId()};
			int[] type = new int[]{Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR};
			saveOrUpdate(buildMap(PROCESS_UPDATE, args, type));
		}
	}

	public void deleteProcess(Process process) {
		if(!isORM()) {
			Object[] args = new Object[]{process.getId()};
			int[] type = new int[]{Types.VARCHAR};
			saveOrUpdate(buildMap(PROCESS_DELETE, args, type));
		}
	}

	public void updateProcessType(String id, String type) {
		if(isORM()) {
			Process process = getProcess(id);
			process.setType(type);
			saveOrUpdate(buildMap(process, UPDATE));
		} else {
			Object[] args = new Object[]{type, id};
			int[] types = new int[]{Types.VARCHAR, Types.VARCHAR};
			saveOrUpdate(buildMap(PROCESS_UPDATE_TYPE, args, types));
		}
	}

	public void saveTask(Task task) {
		if(isORM()) {
			saveOrUpdate(buildMap(task, SAVE));
		} else {
			Object[] args = new Object[]{task.getId(), task.getOrderId(), task.getTaskName(), task.getDisplayName(), task.getTaskType(),
					task.getPerformType(), task.getOperator(), task.getCreateTime(), task.getFinishTime(),
					task.getExpireTime(), task.getActionUrl(), task.getParentTaskId(), task.getVariable(), task.getVersion()};
			int[] type = new int[]{Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.INTEGER,
					Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
					Types.VARCHAR, Types.VARCHAR, Types.INTEGER};
			saveOrUpdate(buildMap(TASK_INSERT, args, type));
		}
	}

	public void saveOrder(Order order) {
		if(isORM()) {
			saveOrUpdate(buildMap(order, SAVE));
		} else {
			Object[] args = new Object[]{order.getId(), order.getProcessId(), order.getCreator(), order.getCreateTime(), order.getParentId(),
					order.getParentNodeName(), order.getExpireTime(), order.getLastUpdateTime(), order.getLastUpdator(), order.getOrderNo(),
                    order.getVariable(), order.getVersion()};
			int[] type = new int[]{Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
					Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.INTEGER};
			saveOrUpdate(buildMap(ORDER_INSERT, args, type));
		}
	}

	public void saveCCOrder(CCOrder ccorder) {
		if(isORM()) {
			saveOrUpdate(buildMap(ccorder, SAVE));
		} else {
			int[] type = new int[]{Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.INTEGER};
			saveOrUpdate(buildMap(CCORDER_INSERT, new Object[]{ccorder.getOrderId(), ccorder.getActorId(),
                    ccorder.getCreator(), ccorder.getCreateTime(), ccorder.getStatus()}, type));
		}
	}

	public void saveTaskActor(TaskActor taskActor) {
        if(isORM()) {
            saveOrUpdate(buildMap(taskActor, SAVE));
        } else {
            int[] type = new int[]{Types.VARCHAR, Types.VARCHAR};
            saveOrUpdate(buildMap(TASK_ACTOR_INSERT, new Object[]{taskActor.getTaskId(), taskActor.getActorId() }, type));
        }
    }

	public void updateTask(Task task) {
		if(isORM()) {
			saveOrUpdate(buildMap(task, UPDATE));
		} else {
			Object[] args = new Object[]{task.getFinishTime(), task.getOperator(), task.getVariable(), task.getExpireTime(), task.getActionUrl(), task.getId(), task.getVersion() };
			int[] type = new int[]{Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,  Types.VARCHAR, Types.VARCHAR,  Types.VARCHAR, Types.INTEGER};
			saveOrUpdate(buildMap(TASK_UPDATE, args, type));
		}
	}

	public void updateOrder(Order order) {
		if(isORM()) {
			saveOrUpdate(buildMap(order, UPDATE));
		} else {
			Object[] args = new Object[]{order.getLastUpdator(), order.getLastUpdateTime(), order.getVariable(), order.getExpireTime(), order.getId(), order.getVersion() };
			int[] type = new int[]{Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.INTEGER};
			saveOrUpdate(buildMap(ORDER_UPDATE, args, type));
		}
	}

	public void updateCCOrder(CCOrder ccorder) {
		if(isORM()) {
			saveOrUpdate(buildMap(ccorder, UPDATE));
		} else {
			Object[] args = new Object[]{ccorder.getStatus(), ccorder.getFinishTime(), ccorder.getOrderId(), ccorder.getActorId() };
			int[] type = new int[]{Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR};
			saveOrUpdate(buildMap(CCORDER_UPDATE, args, type));
		}
	}

	public void deleteTask(Task task) {
		if(!isORM()) {
			Object[] args = new Object[]{task.getId()};
			int[] type = new int[]{Types.VARCHAR};
			saveOrUpdate(buildMap(TASK_ACTOR_DELETE, args, type));
			saveOrUpdate(buildMap(TASK_DELETE, args, type));
		}
	}

	public void deleteOrder(Order order) {
		if(!isORM()) {
			int[] type = new int[]{Types.VARCHAR};
			saveOrUpdate(buildMap(ORDER_DELETE, new Object[]{order.getId()}, type));
		}
	}

	public void deleteCCOrder(CCOrder ccorder) {
		if(!isORM()) {
			int[] type = new int[]{Types.VARCHAR, Types.VARCHAR};
			saveOrUpdate(buildMap(CCORDER_DELETE, new Object[]{ccorder.getOrderId(), ccorder.getActorId()}, type));
		}
	}

	public void removeTaskActor(String taskId, String... actors) {
		if(!isORM()) {
			for(String actorId : actors) {
				int[] type = new int[]{Types.VARCHAR, Types.VARCHAR};
				saveOrUpdate(buildMap(TASK_ACTOR_REDUCE, new Object[]{taskId, actorId}, type));
			}
		}
	}

	public void saveHistory(HistoryOrder order) {
		if(isORM()) {
			saveOrUpdate(buildMap(order, SAVE));
		} else {
			Object[] args = new Object[]{order.getId(), order.getProcessId(), order.getOrderState(), order.getCreator(),
					order.getCreateTime(), order.getEndTime(), order.getParentId(), order.getExpireTime(), order.getOrderNo(), order.getVariable()};
			int[] type = new int[]{Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.VARCHAR,
					Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR};
			saveOrUpdate(buildMap(ORDER_HISTORY_INSERT, args, type));
		}
	}

	public void updateHistory(HistoryOrder order) {
		if(isORM()) {
			saveOrUpdate(buildMap(order, UPDATE));
		} else {
			Object[] args = new Object[]{order.getOrderState(), order.getEndTime(), order.getVariable(), order.getId()};
			int[] type = new int[]{Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR};
			saveOrUpdate(buildMap(ORDER_HISTORY_UPDATE, args, type));
		}
	}

	public void deleteHistoryOrder(HistoryOrder historyOrder) {
		if(!isORM()) {
			Object[] args = new Object[]{historyOrder.getId()};
			int[] type = new int[]{Types.VARCHAR};
			saveOrUpdate(buildMap(ORDER_HISTORY_DELETE, args, type));
		}
	}

	public void saveHistory(HistoryTask task) {
		if(isORM()) {
			saveOrUpdate(buildMap(task, SAVE));
            if(task.getActorIds() != null) {
                for(String actorId : task.getActorIds()) {
                    if(StringHelper.isEmpty(actorId)) continue;
                    HistoryTaskActor hist = new HistoryTaskActor();
                    hist.setActorId(actorId);
                    hist.setTaskId(task.getId());
                    saveOrUpdate(buildMap(hist, SAVE));
                }
            }
		} else {
			Object[] args = new Object[]{task.getId(), task.getOrderId(), task.getTaskName(), task.getDisplayName(), task.getTaskType(),
					task.getPerformType(), task.getTaskState(), task.getOperator(), task.getCreateTime(), task.getFinishTime(),
					task.getExpireTime(), task.getActionUrl(), task.getParentTaskId(), task.getVariable()};
			int[] type = new int[]{Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.INTEGER,
					Types.INTEGER, Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR};
			saveOrUpdate(buildMap(TASK_HISTORY_INSERT, args, type));
            if(task.getActorIds() != null) {
                for(String actorId : task.getActorIds()) {
                    if(StringHelper.isEmpty(actorId)) continue;
                    saveOrUpdate(buildMap(TASK_ACTOR_HISTORY_INSERT, new Object[]{task.getId(), actorId}, new int[]{Types.VARCHAR, Types.VARCHAR}));
                }
            }
		}
	}

	public void deleteHistoryTask(HistoryTask historyTask) {
		if(!isORM()) {
			Object[] args = new Object[]{historyTask.getId()};
			int[] type = new int[]{Types.VARCHAR};
			saveOrUpdate(buildMap(TASK_ACTOR_HISTORY_DELETE, args, type));
			saveOrUpdate(buildMap(TASK_HISTORY_DELETE, args, type));
		}
	}

    public void updateOrderVariable(Order order) {
        updateOrder(order);
        HistoryOrder hist = getHistOrder(order.getId());
        hist.setVariable(order.getVariable());
        updateHistory(hist);
    }

	public void saveSurrogate(Surrogate surrogate) {
		if(isORM()) {
			saveOrUpdate(buildMap(surrogate, SAVE));
		} else {
			Object[] args = new Object[]{surrogate.getId(), surrogate.getProcessName(), surrogate.getOperator(),
					surrogate.getSurrogate(), surrogate.getOdate(), surrogate.getSdate(), surrogate.getEdate(),
					surrogate.getState()};
			int[] type = new int[]{Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
					Types.VARCHAR, Types.INTEGER};
			saveOrUpdate(buildMap(SURROGATE_INSERT, args, type));
		}
	}

	public void updateSurrogate(Surrogate surrogate) {
		if(isORM()) {
			saveOrUpdate(buildMap(surrogate, UPDATE));
		} else {
			Object[] args = new Object[]{surrogate.getProcessName(), surrogate.getSurrogate(), surrogate.getOdate(),
					surrogate.getSdate(), surrogate.getEdate(), surrogate.getState(), surrogate.getId()};
			int[] type = new int[]{Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,Types.VARCHAR, Types.VARCHAR,
					Types.INTEGER, Types.VARCHAR};
			saveOrUpdate(buildMap(SURROGATE_UPDATE, args, type));
		}
	}

	public void deleteSurrogate(Surrogate surrogate) {
		if(!isORM()) {
			Object[] args = new Object[]{surrogate.getId()};
			int[] type = new int[]{Types.VARCHAR};
			saveOrUpdate(buildMap(SURROGATE_DELETE, args, type));
		}
	}

	public Surrogate getSurrogate(String id) {
		String where = " where id = ?";
		return queryObject(Surrogate.class, SURROGATE_QUERY + where, id);
	}

	public List<Surrogate> getSurrogate(Page<Surrogate> page, QueryFilter filter) {
		StringBuilder sql = new StringBuilder(SURROGATE_QUERY);
		sql.append(" where 1=1 and state = 1 ");
		List<Object> paramList = new ArrayList<Object>();
		if(filter.getNames() != null && filter.getNames().length > 0) {
			sql.append(" and process_Name in(");
			for(int i = 0; i < filter.getNames().length; i++) {
				sql.append("?,");
				paramList.add(filter.getNames()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
		if(filter.getOperators() != null && filter.getOperators().length > 0) {
			sql.append(" and operator in (");
			for(String actor : filter.getOperators()) {
				sql.append("?,");
				paramList.add(actor);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
		if(StringHelper.isNotEmpty(filter.getOperateTime())) {
			sql.append(" and sdate <= ? and edate >= ? ");
			paramList.add(filter.getOperateTime());
			paramList.add(filter.getOperateTime());
		}
        if(!filter.isOrderBySetted()) {
            filter.setOrder(QueryFilter.DESC);
            filter.setOrderBy("sdate");
        }
        return queryList(page, filter, Surrogate.class, sql.toString(), paramList.toArray());
	}

	public Task getTask(String taskId) {
		String where = " where id = ?";
		return queryObject(Task.class, QUERY_TASK + where, taskId);
	}

	public List<Task> getNextActiveTasks(String parentTaskId) {
		String where = " where parent_Task_Id = ?";
		return queryList(Task.class, QUERY_TASK + where, parentTaskId);
	}

	public List<Task> getNextActiveTasks(String orderId, String taskName, String parentTaskId) {
		String sql = QUERY_TASK + " where parent_task_id in ( select ht.id from wf_hist_task ht where ht.order_id=? and ht.task_name=? and ht.parent_task_id=? )";
		return queryList(Task.class, sql, orderId, taskName, parentTaskId);
	}

	public HistoryTask getHistTask(String taskId) {
		String where = " where id = ?";
		return queryObject(HistoryTask.class, QUERY_HIST_TASK + where, taskId);
	}

	public HistoryOrder getHistOrder(String orderId) {
		String where = " where id = ?";
		return queryObject(HistoryOrder.class, QUERY_HIST_ORDER + where, orderId);
	}

	public List<TaskActor> getTaskActorsByTaskId(String taskId) {
		String where = " where task_Id = ?";
		return queryList(TaskActor.class, QUERY_TASK_ACTOR + where, taskId);
	}

	public List<HistoryTaskActor> getHistTaskActorsByTaskId(String taskId) {
		String where = " where task_Id = ?";
		return queryList(HistoryTaskActor.class, QUERY_HIST_TASK_ACTOR + where, taskId);
	}

	public Order getOrder(String orderId) {
		String where = " where id = ?";
		return queryObject(Order.class, QUERY_ORDER + where, orderId);
	}

	public List<CCOrder> getCCOrder(String orderId, String... actorIds) {
        StringBuilder where = new StringBuilder(QUERY_CCORDER);
		where.append(" where 1 = 1 ");

		if(StringHelper.isNotEmpty(orderId)) {
			where.append(" and order_Id = ?");
		}
		if(actorIds != null && actorIds.length > 0) {
			where.append(" and actor_Id in (");
			where.append(StringUtils.repeat("?,", actorIds.length));
			where.deleteCharAt(where.length() - 1);
			where.append(") ");
		}
		return queryList(CCOrder.class, where.toString(), ArrayUtils.add(actorIds, 0, orderId));
	}

	public Process getProcess(String id) {
		String where = " where id = ?";
		return queryObject(Process.class, QUERY_PROCESS + where, id);
	}

	public List<Process> getProcesss(Page<Process> page, QueryFilter filter) {
        StringBuilder sql = new StringBuilder(QUERY_PROCESS);
		sql.append(" where 1=1 ");
		List<Object> paramList = new ArrayList<Object>();
		if(filter.getNames() != null && filter.getNames().length > 0) {
			sql.append(" and name in(");
			for(int i = 0; i < filter.getNames().length; i++) {
				sql.append("?,");
				paramList.add(filter.getNames()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
		if(filter.getVersion() != null) {
			sql.append(" and version = ? ");
			paramList.add(filter.getVersion());
		}
		if(filter.getState() != null) {
			sql.append(" and state = ? ");
			paramList.add(filter.getState());
		}
		if(StringHelper.isNotEmpty(filter.getDisplayName())) {
			sql.append(" and display_Name like ? ");
			paramList.add("%" + filter.getDisplayName() + "%");
		}
		if(StringHelper.isNotEmpty(filter.getProcessType())) {
			sql.append(" and type = ? ");
			paramList.add(filter.getProcessType());
		}
		if(filter.getOperators() != null && filter.getOperators().length > 0) {
			sql.append(" and creator in(");
			for(int i = 0; i < filter.getOperators().length; i++) {
				sql.append("?,");
				paramList.add(filter.getOperators()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
        if(!filter.isOrderBySetted()) {
            filter.setOrder(QueryFilter.ASC);
            filter.setOrderBy("name");
        }

		return queryList(page, filter, Process.class, sql.toString(), paramList.toArray());
	}

	public List<Order> getActiveOrders(Page<Order> page, QueryFilter filter) {
        StringBuilder sql = new StringBuilder(QUERY_ORDER);
        sql.append(" left join wf_process p on p.id = o.process_id ");
		sql.append(" where 1=1 ");
		List<Object> paramList = new ArrayList<Object>();
		if(filter.getOperators() != null && filter.getOperators().length > 0) {
			sql.append(" and o.creator in(");
			for(int i = 0; i < filter.getOperators().length; i++) {
				sql.append("?,");
				paramList.add(filter.getOperators()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
        if(filter.getNames() != null && filter.getNames().length > 0) {
            sql.append(" and p.name in(");
            for(int i = 0; i < filter.getNames().length; i++) {
                sql.append("?,");
                paramList.add(filter.getNames()[i]);
            }
            sql.deleteCharAt(sql.length() - 1);
            sql.append(") ");
        }
        if(StringHelper.isNotEmpty(filter.getProcessId())) {
            sql.append(" and o.process_Id = ? ");
            paramList.add(filter.getProcessId());
        }
        if(StringHelper.isNotEmpty(filter.getDisplayName())) {
            sql.append(" and p.display_Name like ?");
            paramList.add("%" + filter.getDisplayName() + "%");
        }
        if(StringHelper.isNotEmpty(filter.getProcessType())) {
            sql.append(" and p.type = ? ");
            paramList.add(filter.getProcessType());
        }
		if(StringHelper.isNotEmpty(filter.getParentId())) {
			sql.append(" and o.parent_Id = ? ");
			paramList.add(filter.getParentId());
		}
		if(filter.getExcludedIds() != null && filter.getExcludedIds().length > 0) {
			sql.append(" and o.id not in(");
			for(int i = 0; i < filter.getExcludedIds().length; i++) {
				sql.append("?,");
				paramList.add(filter.getExcludedIds()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
		if(StringHelper.isNotEmpty(filter.getCreateTimeStart())) {
			sql.append(" and o.create_Time >= ? ");
			paramList.add(filter.getCreateTimeStart());
		}
		if(StringHelper.isNotEmpty(filter.getCreateTimeEnd())) {
			sql.append(" and o.create_Time <= ? ");
			paramList.add(filter.getCreateTimeEnd());
		}
		if(StringHelper.isNotEmpty(filter.getOrderNo())) {
			sql.append(" and o.order_No = ? ");
			paramList.add(filter.getOrderNo());
		}

        if(!filter.isOrderBySetted()) {
            filter.setOrder(QueryFilter.DESC);
            filter.setOrderBy("o.create_Time");
        }
        return queryList(page, filter, Order.class, sql.toString(), paramList.toArray());
	}

	public List<Task> getActiveTasks(Page<Task> page, QueryFilter filter) {
        StringBuilder sql = new StringBuilder(QUERY_TASK);
		boolean isFetchActor = filter.getOperators() != null && filter.getOperators().length > 0;
		if(isFetchActor) {
			sql.append(" left join wf_task_actor ta on ta.task_id = id ");
		}
		sql.append(" where 1=1 ");
		List<Object> paramList = new ArrayList<Object>();
		if(StringHelper.isNotEmpty(filter.getOrderId())) {
			sql.append(" and order_Id = ? ");
			paramList.add(filter.getOrderId());
		}
		if(filter.getExcludedIds() != null && filter.getExcludedIds().length > 0) {
			sql.append(" and id not in(");
			for(int i = 0; i < filter.getExcludedIds().length; i++) {
				sql.append("?,");
				paramList.add(filter.getExcludedIds()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
		if(isFetchActor) {
			sql.append(" and ta.actor_Id in (");
			for(int i = 0; i < filter.getOperators().length; i++) {
				sql.append("?,");
				paramList.add(filter.getOperators()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
		if(filter.getNames() != null && filter.getNames().length > 0) {
			sql.append(" and task_Name in (");
			for(int i = 0; i < filter.getNames().length; i++) {
				sql.append("?,");
				paramList.add(filter.getNames()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
		if(StringHelper.isNotEmpty(filter.getCreateTimeStart())) {
			sql.append(" and create_Time >= ? ");
			paramList.add(filter.getCreateTimeStart());
		}
		if(StringHelper.isNotEmpty(filter.getCreateTimeEnd())) {
			sql.append(" and create_Time <= ? ");
			paramList.add(filter.getCreateTimeEnd());
		}
        if(!filter.isOrderBySetted()) {
            filter.setOrder(QueryFilter.DESC);
            filter.setOrderBy("create_Time");
        }
        return queryList(page, filter, Task.class, sql.toString(), paramList.toArray());
	}

	public List<HistoryOrder> getHistoryOrders(Page<HistoryOrder> page, QueryFilter filter) {
        StringBuilder sql = new StringBuilder(QUERY_HIST_ORDER);
        sql.append(" left join wf_process p on p.id = o.process_id ");
		sql.append(" where 1=1 ");
		List<Object> paramList = new ArrayList<Object>();
		if(filter.getOperators() != null && filter.getOperators().length > 0) {
			sql.append(" and o.creator in(");
			for(int i = 0; i < filter.getOperators().length; i++) {
				sql.append("?,");
				paramList.add(filter.getOperators()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
        if(filter.getNames() != null && filter.getNames().length > 0) {
            sql.append(" and p.name in(");
            for(int i = 0; i < filter.getNames().length; i++) {
                sql.append("?,");
                paramList.add(filter.getNames()[i]);
            }
            sql.deleteCharAt(sql.length() - 1);
            sql.append(") ");
        }
		if(StringHelper.isNotEmpty(filter.getProcessId())) {
			sql.append(" and o.process_Id = ? ");
			paramList.add(filter.getProcessId());
		}
        if(StringHelper.isNotEmpty(filter.getProcessType())) {
            sql.append(" and p.type = ? ");
            paramList.add(filter.getProcessType());
        }
        if(StringHelper.isNotEmpty(filter.getDisplayName())) {
            sql.append(" and p.display_Name like ?");
            paramList.add("%" + filter.getDisplayName() + "%");
        }
		if(StringHelper.isNotEmpty(filter.getParentId())) {
			sql.append(" and o.parent_Id = ? ");
			paramList.add(filter.getParentId());
		}
        if(filter.getState() != null) {
            sql.append(" and o.order_State = ? ");
            paramList.add(filter.getState());
        }
		if(StringHelper.isNotEmpty(filter.getCreateTimeStart())) {
			sql.append(" and o.create_Time >= ? ");
			paramList.add(filter.getCreateTimeStart());
		}
		if(StringHelper.isNotEmpty(filter.getCreateTimeEnd())) {
			sql.append(" and o.create_Time <= ? ");
			paramList.add(filter.getCreateTimeEnd());
		}
		if(StringHelper.isNotEmpty(filter.getOrderNo())) {
			sql.append(" and o.order_No = ? ");
			paramList.add(filter.getOrderNo());
		}
        if(!filter.isOrderBySetted()) {
            filter.setOrder(QueryFilter.DESC);
            filter.setOrderBy("o.create_Time");
        }
        return queryList(page, filter, HistoryOrder.class, sql.toString(), paramList.toArray());
	}

	public List<HistoryTask> getHistoryTasks(Page<HistoryTask> page, QueryFilter filter) {
        StringBuilder sql = new StringBuilder(QUERY_HIST_TASK);
		boolean isFetchActor = filter.getOperators() != null && filter.getOperators().length > 0;
		if(isFetchActor) {
			sql.append(" left join wf_hist_task_actor ta on ta.task_id = id ");
		}
		sql.append(" where 1=1 ");
		List<Object> paramList = new ArrayList<Object>();
		if(StringHelper.isNotEmpty(filter.getOrderId())) {
			sql.append(" and order_Id = ? ");
			paramList.add(filter.getOrderId());
		}
		if(isFetchActor) {
			sql.append(" and ta.actor_Id in (");
			for(int i = 0; i < filter.getOperators().length; i++) {
				sql.append("?,");
				paramList.add(filter.getOperators()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
		if(filter.getNames() != null && filter.getNames().length > 0) {
			sql.append(" and task_Name in (");
			for(int i = 0; i < filter.getNames().length; i++) {
				sql.append("?,");
				paramList.add(filter.getNames()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
		if(StringHelper.isNotEmpty(filter.getCreateTimeStart())) {
			sql.append(" and create_Time >= ? ");
			paramList.add(filter.getCreateTimeStart());
		}
		if(StringHelper.isNotEmpty(filter.getCreateTimeEnd())) {
			sql.append(" and create_Time <= ? ");
			paramList.add(filter.getCreateTimeEnd());
		}
        if(!filter.isOrderBySetted()) {
            filter.setOrder(QueryFilter.DESC);
            filter.setOrderBy("finish_Time");
        }
		return queryList(page, filter, HistoryTask.class, sql.toString(), paramList.toArray());
	}

	public List<WorkItem> getWorkItems(Page<WorkItem> page, QueryFilter filter) {
        StringBuilder sql = new StringBuilder();
		sql.append(" select distinct o.process_Id, t.order_Id, t.id as id, t.id as task_Id, p.display_Name as process_Name, p.instance_Url, o.parent_Id, o.creator, ");
		sql.append(" o.create_Time as order_Create_Time, o.expire_Time as order_Expire_Time, o.order_No, o.variable as order_Variable, ");
		sql.append(" t.display_Name as task_Name, t.task_Name as task_Key, t.task_Type, t.perform_Type, t.operator, t.action_Url, ");
		sql.append(" t.create_Time as task_Create_Time, t.finish_Time as task_End_Time, t.expire_Time as task_Expire_Time, t.variable as task_Variable ");
		sql.append(" from wf_task t ");
		sql.append(" left join wf_order o on t.order_id = o.id ");
		sql.append(" left join wf_task_actor ta on ta.task_id=t.id ");
		sql.append(" left join wf_process p on p.id = o.process_id ");
		sql.append(" where 1=1 ");

		/**
		 * 查询条件构造sql的where条件
		 */
		List<Object> paramList = new ArrayList<Object>();
		if(filter.getOperators() != null && filter.getOperators().length > 0) {
			sql.append(" and ta.actor_Id in (");
			for(String actor : filter.getOperators()) {
				sql.append("?,");
				paramList.add(actor);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}

		if(StringHelper.isNotEmpty(filter.getProcessId())) {
			sql.append(" and o.process_Id = ?");
			paramList.add(filter.getProcessId());
		}
		if(StringHelper.isNotEmpty(filter.getDisplayName())) {
			sql.append(" and p.display_Name like ?");
			paramList.add("%" + filter.getDisplayName() + "%");
		}
		if(StringHelper.isNotEmpty(filter.getParentId())) {
			sql.append(" and o.parent_Id = ? ");
			paramList.add(filter.getParentId());
		}
		if(StringHelper.isNotEmpty(filter.getOrderId())) {
			sql.append(" and t.order_id = ? ");
			paramList.add(filter.getOrderId());
		}
		if(filter.getNames() != null && filter.getNames().length > 0) {
			sql.append(" and t.task_Name in (");
			for(int i = 0; i < filter.getNames().length; i++) {
				sql.append("?,");
				paramList.add(filter.getNames()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
		if(filter.getTaskType() != null) {
			sql.append(" and t.task_Type = ? ");
			paramList.add(filter.getTaskType());
		}
		if(filter.getPerformType() != null) {
			sql.append(" and t.perform_Type = ? ");
			paramList.add(filter.getPerformType());
		}
		if(StringHelper.isNotEmpty(filter.getCreateTimeStart())) {
			sql.append(" and t.create_Time >= ? ");
			paramList.add(filter.getCreateTimeStart());
		}
		if(StringHelper.isNotEmpty(filter.getCreateTimeEnd())) {
			sql.append(" and t.create_Time <= ? ");
			paramList.add(filter.getCreateTimeEnd());
		}
		if(!filter.isOrderBySetted()) {
            filter.setOrder(QueryFilter.DESC);
            filter.setOrderBy("t.create_Time");
		}

		return queryList(page, filter, WorkItem.class, sql.toString(), paramList.toArray());
	}

	public List<HistoryOrder> getCCWorks(Page<HistoryOrder> page, QueryFilter filter) {
        StringBuilder sql = new StringBuilder();
		sql.append(" select id,process_Id,order_State,priority,cc.creator,cc.create_Time,end_Time,parent_Id,expire_Time,order_No,variable ");
		sql.append(" from wf_cc_order cc ");
		sql.append(" left join wf_hist_order o on cc.order_id = o.id ");
		sql.append(" where 1=1 ");

		/**
		 * 查询条件构造sql的where条件
		 */
		List<Object> paramList = new ArrayList<Object>();
		if(filter.getOperators() != null && filter.getOperators().length > 0) {
			sql.append(" and cc.actor_Id in(");
			for(int i = 0; i < filter.getOperators().length; i++) {
				sql.append("?,");
				paramList.add(filter.getOperators()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
		if(filter.getState() != null) {
			sql.append(" and cc.status = ? ");
			paramList.add(filter.getState());
		}
		if(StringHelper.isNotEmpty(filter.getProcessId())) {
			sql.append(" and process_Id = ? ");
			paramList.add(filter.getProcessId());
		}
		if(StringHelper.isNotEmpty(filter.getParentId())) {
			sql.append(" and parent_Id = ? ");
			paramList.add(filter.getParentId());
		}
		if(StringHelper.isNotEmpty(filter.getCreateTimeStart())) {
			sql.append(" and cc.create_Time >= ? ");
			paramList.add(filter.getCreateTimeStart());
		}
		if(StringHelper.isNotEmpty(filter.getCreateTimeEnd())) {
			sql.append(" and cc.create_Time <= ? ");
			paramList.add(filter.getCreateTimeEnd());
		}
		if(StringHelper.isNotEmpty(filter.getOrderNo())) {
			sql.append(" and order_No = ? ");
			paramList.add(filter.getOrderNo());
		}
        if(!filter.isOrderBySetted()) {
            filter.setOrder(QueryFilter.DESC);
            filter.setOrderBy("cc.create_Time");
        }
        return queryList(page, filter, HistoryOrder.class, sql.toString(), paramList.toArray());
	}

	public List<WorkItem> getHistoryWorkItems(Page<WorkItem> page, QueryFilter filter) {
        StringBuilder sql = new StringBuilder();
		sql.append(" select distinct o.process_Id, t.order_Id, t.id as id, t.id as task_Id, p.display_Name as process_Name, p.instance_Url, o.parent_Id, o.creator, ");
		sql.append(" o.create_Time as order_Create_Time, o.expire_Time as order_Expire_Time, o.order_No, o.variable as order_Variable, ");
		sql.append(" t.display_Name as task_Name, t.task_Name as task_Key, t.task_Type, t.perform_Type,t.operator, t.action_Url, ");
		sql.append(" t.create_Time as task_Create_Time, t.finish_Time as task_End_Time, t.expire_Time as task_Expire_Time, t.variable as task_Variable ");
		sql.append(" from wf_hist_task t ");
		sql.append(" left join wf_hist_order o on t.order_id = o.id ");
		sql.append(" left join wf_hist_task_actor ta on ta.task_id=t.id ");
		sql.append(" left join wf_process p on p.id = o.process_id ");
		sql.append(" where 1=1 ");
		/**
		 * 查询条件构造sql的where条件
		 */
		List<Object> paramList = new ArrayList<Object>();
		if(filter.getOperators() != null && filter.getOperators().length > 0) {
			sql.append(" and ta.actor_Id in (");
			for(String actor : filter.getOperators()) {
				sql.append("?,");
				paramList.add(actor);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}

		if(StringHelper.isNotEmpty(filter.getProcessId())) {
			sql.append(" and o.process_Id = ?");
			paramList.add(filter.getProcessId());
		}
		if(StringHelper.isNotEmpty(filter.getDisplayName())) {
			sql.append(" and p.display_Name like ?");
			paramList.add("%" + filter.getDisplayName() + "%");
		}
		if(StringHelper.isNotEmpty(filter.getParentId())) {
			sql.append(" and o.parent_Id = ? ");
			paramList.add(filter.getParentId());
		}
		if(StringHelper.isNotEmpty(filter.getOrderId())) {
			sql.append(" and t.order_id = ? ");
			paramList.add(filter.getOrderId());
		}
		if(filter.getNames() != null && filter.getNames().length > 0) {
			sql.append(" and t.task_Name in (");
			for(int i = 0; i < filter.getNames().length; i++) {
				sql.append("?,");
				paramList.add(filter.getNames()[i]);
			}
			sql.deleteCharAt(sql.length() - 1);
			sql.append(") ");
		}
		if(filter.getTaskType() != null) {
			sql.append(" and t.task_Type = ? ");
			paramList.add(filter.getTaskType());
		}
		if(filter.getPerformType() != null) {
			sql.append(" and t.perform_Type = ? ");
			paramList.add(filter.getPerformType());
		}
		if(StringHelper.isNotEmpty(filter.getCreateTimeStart())) {
			sql.append(" and t.create_Time >= ? ");
			paramList.add(filter.getCreateTimeStart());
		}
		if(StringHelper.isNotEmpty(filter.getCreateTimeEnd())) {
			sql.append(" and t.create_Time <= ? ");
			paramList.add(filter.getCreateTimeEnd());
		}

		if(!filter.isOrderBySetted()) {
            filter.setOrder(QueryFilter.DESC);
            filter.setOrderBy("t.create_Time");
		}
		return queryList(page, filter, WorkItem.class, sql.toString(), paramList.toArray());
	}

    public <T> List<T> queryList(Page<T> page, QueryFilter filter, Class<T> clazz, String sql, Object... args) {
        String orderby = StringHelper.buildPageOrder(filter.getOrder(), filter.getOrderBy());
        String querySQL = sql + orderby;
        if(page == null) {
            return queryList(clazz, querySQL, args);
        }
        String countSQL = "select count(1) from (" + sql + ") c ";
        querySQL = getDialect().getPageSql(querySQL, page);
        if(log.isDebugEnabled()) {
            log.debug("查询分页countSQL=\n" + countSQL);
            log.debug("查询分页querySQL=\n" + querySQL);
        }
        try {
            Object count = queryCount(countSQL, args);
            List<T> list = queryList(clazz, querySQL, args);
            if(list == null) list = Collections.emptyList();
            page.setResult(list);
            page.setTotalCount(ClassHelper.castLong(count));
            return list;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return Collections.emptyList();
        }
    }

    /**
     * 运行脚本
     */
    public void runScript() {
        String autoStr = ConfigHelper.getProperty("schema.auto");
        if(autoStr == null || !autoStr.equalsIgnoreCase("true")) {
            return;
        }
        Connection conn = null;
        try {
            conn = getConnection();
            if(JdbcHelper.isExec(conn)) {
                log.info("script has completed execution.skip this step");
                return;
            }
            String databaseType = JdbcHelper.getDatabaseType(conn);
            String schema = "db/core/schema-" + databaseType + ".sql";
            ScriptRunner runner = new ScriptRunner(conn, true);
            runner.runScript(schema);
        } catch (Exception e) {
            throw new SnakerException(e);
        } finally {
            try {
                JdbcHelper.close(conn);
            } catch (SQLException e) {
                //ignore
            }
        }
    }

    /**
     * 分页查询时，符合条件的总记录数
     * @param sql sql语句
     * @param args 参数数组
     * @return 总记录数
     */
    protected abstract Object queryCount(String sql, Object... args);

    /**
     * 获取数据库连接
     * @return Connection
     */
    protected abstract Connection getConnection() throws SQLException;
}


/* Copyright 2013-2015 www.snakerflow.com.
 *
 * 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 org.snaker.engine.helper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.codehaus.jackson.map.ObjectMapper;

/**
 * json处理帮助类
 * @author yuqs
 * @since 1.0
 */
public class JsonHelper {
	private static final Logger log = LoggerFactory.getLogger(JsonHelper.class);
	/**
	 * jackson的ObjectMapper对象
	 */
	private static ObjectMapper mapper = new ObjectMapper();
	/**
	 * 将对象转换为json字符串
	 * @param object
	 * @return
	 */
	public static String toJson(Object object) {
		if(object == null) return "";
		try {
			return mapper.writeValueAsString(object);
		} catch (Exception e) {
			log.warn("write to json string error:" + object, e);
			return "";
		}
	}

	/**
	 * 根据指定类型解析json字符串，并返回该类型的对象
	 * @param jsonString
	 * @param clazz
	 * @return
	 */
	public static <T> T fromJson(String jsonString, Class<T> clazz) {
		if (StringHelper.isEmpty(jsonString)) {
			return null;
		}

		try {
			return mapper.readValue(jsonString, clazz);
		} catch (Exception e) {
			log.warn("parse json string error:" + jsonString, e);
			return null;
		}
	}
}


/* Copyright 2013-2015 www.snakerflow.com.
 *
 * 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 org.snaker.engine.helper;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 配置属性帮助类
 * @author yuqs
 * @since 1.0
 */
public class ConfigHelper
{
    private static final transient Logger log = LoggerFactory.getLogger(ConfigHelper.class);

    /**
     * 常用配置属性文件名称.
     */
    private final static String PROPERTIES_FILENAME = "snaker.properties";
    /**
     * 配置属性对象静态化
     */
    private static Properties properties;

    public static Properties getProperties() {
        if(properties == null) {
            synchronized (ConfigHelper.class) {
                if(properties == null) {
                    loadProperties(PROPERTIES_FILENAME);
                }
            }
        }
        return properties;
	}

    /**
     * 根据key获取配置的字符串value值
     * @param key
     * @return
     */
    public static String getProperty(String key) {
        if (key == null) {
            return null;
        }
        return getProperties().getProperty(key);
    }

    /**
     * 根据key获取配置的数字value值
     * @param key
     * @return
     */
    public static int getNumerProperty(String key) {
        String value = getProperties().getProperty(key);
        if(NumberUtils.isNumber(value)) {
        	return Integer.parseInt(value);
        } else {
        	return 0;
        }
    }

    public static void loadProperties(Properties props) {
        properties = props;
    }

    /**
     * 根据指定的文件名称，从类路径中加载属性文件，构造Properties对象
     * @param filename 属性文件名称
     */
    public static void loadProperties(String filename) {
    	InputStream in = null;
        ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
        properties = new Properties();
        if (threadContextClassLoader != null) {
        	in = threadContextClassLoader.getResourceAsStream(filename);
        }
        if (in == null) {
        	in = ConfigHelper.class.getResourceAsStream(filename);
            if (in == null) {
                log.warn("No properties file found in the classpath by filename " + filename);
            }
        } else {
            try {
                properties.load(in);
                log.info("Properties read " + properties);
            } catch (Exception e) {
                log.error("Error reading from " + filename, e);
            } finally {
                try {
                    in.close();
                } catch (IOException e) {
                    log.warn("IOException while closing InputStream: " + e.getMessage());
                }
            }
        }
    }

}


package it.unimi.dsi.fastutil.io;

/*
 * Copyright (C) 2005-2017 Sebastiano Vigna
 *
 * 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.
 */

import it.unimi.dsi.fastutil.bytes.ByteArrays;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileChannel;
import java.util.EnumSet;

/** Lightweight, unsynchronized, aligned input stream buffering class with
 *  {@linkplain #skip(long) true skipping},
 *  {@linkplain MeasurableStream measurability},
 *  {@linkplain RepositionableStream repositionability}
 *  and {@linkplain #readLine(byte[], int, int, EnumSet) line reading} support.
 *
 *
This class provides buffering for input streams, but it does so with
 * purposes and an internal logic that are radically different from the ones
 * adopted in {@link java.io.BufferedInputStream}. The main features follow.
 *
 *

 *

There is no support for marking. All methods are unsychronized.
 *
 *

As an additional feature, this class implements the {@link
 * RepositionableStream} and {@link MeasurableStream} interfaces.
 * An instance of this class will try to cast
 * the underlying byte stream to a {@link RepositionableStream} and to fetch by
 * reflection the {@link java.nio.channels.FileChannel} underlying the given
 * output stream, in this order. If either reference can be successfully
 * fetched, you can use {@link #position(long)} to reposition the stream.
 * Much in the same way, an instance of this class will try to cast the
 * the underlying byte stream to a {@link MeasurableStream}, and if this
 * operation is successful, or if a {@link java.nio.channels.FileChannel} can
 * be detected, then {@link #position()} and {@link #length()} will work as expected.
 *
 *
 *

Due to erratic and unpredictable behaviour of {@link InputStream#skip(long)},
 * which does not correspond to its specification and which Sun refuses to fix
 * (see bug 6222822;
 * don't be fooled by the “closed, fixed” label),
 * this class peeks at the underlying stream and if it is {@link System#in} it uses
 * repeated reads instead of calling {@link InputStream#skip(long)} on the underlying stream; moreover,
 * skips and reads are tried alternately, so to guarantee that skipping
 * less bytes than requested can be caused only by reaching the end of file.
 *
 *

This class keeps also track of the number of bytes read so far, so
 * to be able to implement {@link MeasurableStream#position()}
 * independently of underlying input stream.
 *
 *

This class has limited support for
 * {@linkplain #readLine(byte[], int, int, EnumSet) “reading a line”}
 * (whatever that means) from the underlying input stream. You can choose the set of
 * {@linkplain FastBufferedInputStream.LineTerminator line terminators} that
 * delimit lines.
 *
 *

 *
 *
Warning: Since {@code fastutil} 6.0.0, this class detects
 * a implementations of {@link MeasurableStream} instead of subclasses {@code MeasurableInputStream} (which is deprecated).
 *
 * @since 4.4
 */

public class FastBufferedInputStream extends MeasurableInputStream implements RepositionableStream {

	/** The default size of the internal buffer in bytes (8Ki). */
	public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;

	/** An enumeration of the supported line terminators. */
	public static enum LineTerminator {
		/** A carriage return (CR, ASCII 13). */
		CR,
		/** A line feed (LF, ASCII 10). */
		LF,
		/** A carriage return followed by a line feed (CR/LF, ASCII 13/10). */
		CR_LF
	}

	/** A set containing all available line terminators. */
	public static final EnumSet<LineTerminator> ALL_TERMINATORS = EnumSet.allOf(LineTerminator.class);

	/** The underlying input stream. */
	protected InputStream is;

	/** The internal buffer. */
	protected byte buffer[];

	/** The current position in the buffer. */
	protected int pos;

	/** The number of bytes ever read (reset upon a call to {@link #position(long)}).
	 * In particular, this will always represent the index (in the underlying input stream)
	 * of the first available byte in the buffer. */
	protected long readBytes;

	/** The number of buffer bytes available starting from {@link #pos}. */
	protected int avail;

	/** The cached file channel underlying {@link #is}, if any. */
	private FileChannel fileChannel;

	/** {@link #is} cast to a positionable stream, if possible. */
	private RepositionableStream repositionableStream;

	/** {@link #is} cast to a measurable stream, if possible. */
	private MeasurableStream measurableStream;

	private static int ensureBufferSize(final int bufferSize) {
		if (bufferSize <= 0) throw new IllegalArgumentException("Illegal buffer size: " + bufferSize);
		return bufferSize;
	}

	/** Creates a new fast buffered input stream by wrapping a given input stream with a given buffer.
	 *
	 * @param is an input stream to wrap.
	 * @param buffer a buffer of positive length.
	 */
	public FastBufferedInputStream(final InputStream is, final byte[] buffer) {
		this.is = is;
		ensureBufferSize(buffer.length);
		this.buffer = buffer;

		if (is instanceof RepositionableStream) repositionableStream = (RepositionableStream)is;
		if (is instanceof MeasurableStream) measurableStream = (MeasurableStream)is;

		if (repositionableStream == null) {

			try {
				fileChannel = (FileChannel)(is.getClass().getMethod("getChannel", new Class<?>[] {})).invoke(is);
			}
			catch(IllegalAccessException e) {}
			catch(IllegalArgumentException e) {}
			catch(NoSuchMethodException e) {}
			catch(java.lang.reflect.InvocationTargetException e) {}
			catch(ClassCastException e) {}
		}
	}

	/** Creates a new fast buffered input stream by wrapping a given input stream with a given buffer size.
	 *
	 * @param is an input stream to wrap.
	 * @param bufferSize the size in bytes of the internal buffer (greater than zero).
	 */
	public FastBufferedInputStream(final InputStream is, final int bufferSize) {
		this(is, new byte[ensureBufferSize(bufferSize)]);
	}

	/** Creates a new fast buffered input stream by wrapping a given input stream with a buffer of {@link #DEFAULT_BUFFER_SIZE} bytes.
	 *
	 * @param is an input stream to wrap.
	 */
	public FastBufferedInputStream(final InputStream is) {
		this(is, DEFAULT_BUFFER_SIZE);
	}

	/** Checks whether no more bytes will be returned.
	 *
	 *
This method will refill the internal buffer.
	 *
	 * @return true if there are no characters in the internal buffer and
	 * the underlying reader is exhausted.
	 */

	protected boolean noMoreCharacters() throws IOException {
		if (avail == 0) {
			avail = is.read(buffer);
			if (avail <= 0) {
				avail = 0;
				return true;
			}
			pos = 0;
		}
		return false;
	}

	@Override
	public int read() throws IOException {
		if (noMoreCharacters()) return -1;
		avail--;
		readBytes++;
		return buffer[pos++] & 0xFF;
	}

	@Override
	public int read(final byte b[], final int offset, final int length) throws IOException {
		if (length <= avail) {
			System.arraycopy(buffer, pos, b, offset, length);
			pos += length;
			avail -= length;
			readBytes += length;
			return length;
		}

		final int head = avail;

		System.arraycopy(buffer, pos, b, offset, head);
		pos = avail = 0;
		readBytes += head;

		if (length > buffer.length) {
			// We read directly into the destination
			final int result = is.read(b, offset + head, length - head);
			if (result > 0) readBytes += result;
			return result < 0 ? (head == 0 ? -1 : head) : result + head;
		}

		if (noMoreCharacters()) return head == 0 ? -1 : head;

		final int toRead = Math.min(length - head, avail);
		readBytes += toRead;
		System.arraycopy(buffer, 0, b, offset + head, toRead);
		pos = toRead;
		avail -= toRead;

		// Note that head >= 0, and necessarily toRead > 0
		return toRead + head;
	}

	/** Reads a line into the given byte array using {@linkplain #ALL_TERMINATORS all terminators}.
	 *
	 * @param array byte array where the next line will be stored.
	 * @return the number of bytes actually placed in {@code array}, or -1 at end of file.
	 * @see #readLine(byte[], int, int, EnumSet)
	 */

	public int readLine(final byte[] array) throws IOException {
		return readLine(array, 0, array.length, ALL_TERMINATORS);
	}

	/** Reads a line into the given byte array.
	 *
	 * @param array byte array where the next line will be stored.
	 * @param terminators a set containing the line termination sequences that we want
	 * to consider as valid.
	 * @return the number of bytes actually placed in {@code array}, or -1 at end of file.
	 * @see #readLine(byte[], int, int, EnumSet)
	 */

	public int readLine(final byte[] array, final EnumSet<LineTerminator> terminators) throws IOException {
		return readLine(array, 0, array.length, terminators);
	}

	/** Reads a line into the given byte-array fragment	using {@linkplain #ALL_TERMINATORS all terminators}.
	 *
	 * @param array byte array where the next line will be stored.
	 * @param off the first byte to use in {@code array}.
	 * @param len the maximum number of bytes to read.
	 * @return the number of bytes actually placed in {@code array}, or -1 at end of file.
	 * @see #readLine(byte[], int, int, EnumSet)
	 */
	public int readLine(final byte[] array, final int off, final int len) throws IOException {
		return readLine(array, off, len, ALL_TERMINATORS);
	}

	/** Reads a line into the given byte-array fragment.
	 *
	 *
Reading lines (i.e., characters) out of a byte stream is not always sensible
	 * (methods available to that purpose in old versions of Java have been mercilessly deprecated).
	 * Nonetheless, in several situations, such as when decoding network protocols or headers
	 * known to be ASCII, it is very useful to be able to read a line from a byte stream.
	 *
	 *
This method will attempt to read the next line into {@code array} starting at {@code off},
	 * reading at most {@code len} bytes. The read, however, will be stopped by the end of file or
	 * when meeting a {@linkplain LineTerminator line terminator}. Of course, for this operation
	 * to be sensible the encoding of the text contained in the stream, if any, must not generate spurious
	 * carriage returns or line feeds. Note that the termination detection uses a maximisation
	 * criterion, so if you specify both {@link LineTerminator#CR} and
	 * {@link LineTerminator#CR_LF} meeting a pair CR/LF will consider the whole pair a terminator.
	 *
	 *
Terminators are not copied into array or included in the returned count. The
	 * returned integer can be used to check whether the line is complete: if it is smaller than
	 * {@code len}, then more bytes might be available, but note that this method (contrarily
	 * to {@link #read(byte[], int, int)}) can legitimately return zero when {@code len}
	 * is nonzero just because a terminator was found as the first character. Thus, the intended
	 * usage of this method is to call it on a given array, check whether {@code len} bytes
	 * have been read, and if so try again (possibly extending the array) until a number of read bytes
	 * strictly smaller than {@code len} (possibly, -1) is returned.
	 *
	 *
If you need to guarantee that a full line is read, use the following idiom:
	 *

	 * int start = off, len;
	 * while((len = fbis.readLine(array, start, array.length - start, terminators)) == array.length - start) {
	 *     start += len;
	 *     array = ByteArrays.grow(array, array.length + 1);
	 * }
	 *

	 *
	 *
At the end of the loop, the line will be placed in {@code array} starting at
	 * {@code off} (inclusive) and ending at {@code start + Math.max(len, 0)} (exclusive).
	 *
	 * @param array byte array where the next line will be stored.
	 * @param off the first byte to use in {@code array}.
	 * @param len the maximum number of bytes to read.
	 * @param terminators a set containing the line termination sequences that we want
	 * to consider as valid.
	 * @return the number of bytes actually placed in {@code array}, or -1 at end of file.
	 * Note that the returned number will be {@code len} if no line termination sequence
	 * specified in {@code terminators} has been met before scanning {@code len} byte,
	 * and if also we did not meet the end of file.
	 */

	public int readLine(final byte[] array, final int off, final int len, final EnumSet<LineTerminator> terminators) throws IOException {
		ByteArrays.ensureOffsetLength(array ,off, len);
		if (len == 0) return 0; // 0-length reads always return 0
		if (noMoreCharacters()) return -1;
		int i, k = 0, remaining = len, read = 0; // The number of bytes still to be read
		for(;;) {
			for(i = 0; i < avail && i < remaining && (k = buffer[pos + i]) != '\n' && k != '\r' ; i++);
			System.arraycopy(buffer, pos, array, off + read, i);
			pos += i;
			avail -= i;
			read += i;
			remaining -= i;
			if (remaining == 0) {
				readBytes += read;
				return read; // We did not stop because of a terminator
			}

			if (avail > 0) { // We met a terminator
				if (k == '\n') { // LF first
					pos++;
					avail--;
					if (terminators.contains(LineTerminator.LF)) {
						readBytes += read + 1;
						return read;
					}
					else {
						array[off + read++] = '\n';
						remaining--;
					}
				}
				else if (k == '\r') { // CR first
					pos++;
					avail--;

					if (terminators.contains(LineTerminator.CR_LF)) {
						if (avail > 0) {
							if (buffer[pos] == '\n') { // CR/LF with LF already in the buffer.
								pos ++;
								avail--;
								readBytes += read + 2;
								return read;
							}
						}
						else { // We must search for the LF.
							if (noMoreCharacters()) {
								// Not found a matching LF because of end of file, will return CR in buffer if not a terminator

								if (! terminators.contains(LineTerminator.CR)) {
									array[off + read++] = '\r';
									remaining--;
									readBytes += read;
								}
								else readBytes += read + 1;

								return read;
							}
							if (buffer[0] == '\n') {
								// Found matching LF, won't return terminators in the buffer
								pos++;
								avail--;
								readBytes += read + 2;
								return read;
							}
						}
					}

					if (terminators.contains(LineTerminator.CR)) {
						readBytes += read + 1;
						return read;
					}

					array[off + read++] = '\r';
					remaining--;
				}
			}
			else if (noMoreCharacters()) {
				readBytes += read;
				return read;
			}
		}
	}

	@Override
	public void position(long newPosition) throws IOException {

		final long position = readBytes;

		/** Note that this check will succeed also in the case of
		 * an empty buffer and position == newPosition. This behaviour is
		 * intentional, as it delays buffering to when it is actually
		 * necessary and avoids useless class the underlying stream. */

		if (newPosition <= position + avail && newPosition >= position - pos) {
			pos += newPosition - position;
			avail -= newPosition - position;
			readBytes = newPosition;
			return;
		}

		if (repositionableStream != null) repositionableStream.position(newPosition);
		else if (fileChannel != null) fileChannel.position(newPosition);
		else throw new UnsupportedOperationException("position() can only be called if the underlying byte stream implements the RepositionableStream interface or if the getChannel() method of the underlying byte stream exists and returns a FileChannel");
		readBytes = newPosition;

		avail = pos = 0;
	}

	@Override
	public long position() throws IOException {
		return readBytes;
	}

	/** Returns the length of the underlying input stream, if it is {@linkplain MeasurableStream measurable}.
	 *
	 * @return the length of the underlying input stream.
	 * @throws UnsupportedOperationException if the underlying input stream is not {@linkplain MeasurableStream measurable} and
	 * cannot provide a {@link FileChannel}.
	 */

	@Override
	public long length() throws IOException {
		if (measurableStream != null) return measurableStream.length();
		if (fileChannel != null) return fileChannel.size();
		throw new UnsupportedOperationException();
	}

	/** Skips the given amount of bytes by repeated reads.
	 *
	 * Warning: this method uses destructively the internal buffer.
	 *
	 * @param n the number of bytes to skip.
	 * @return the number of bytes actually skipped.
	 * @see InputStream#skip(long)
	 */

	private long skipByReading(final long n) throws IOException {
		long toSkip = n;
		int len;
		while(toSkip > 0) {
			len = is.read(buffer, 0, (int)Math.min(buffer.length, toSkip));
			if (len > 0) toSkip -= len;
			else break;
		}

		return n - toSkip;
	}

	/** Skips over and discards the given number of bytes of data from this fast buffered input stream.
	 *
	 *
As explained in the {@linkplain FastBufferedInputStream class documentation}, the semantics
	 * of {@link InputStream#skip(long)} is fatally flawed. This method provides additional semantics as follows:
	 * it will skip the provided number of bytes, unless the end of file has been reached.
	 *
	 *
Additionally, if the underlying input stream is {@link System#in} this method will use
	 * repeated reads instead of invoking {@link InputStream#skip(long)}.
	 *
	 * @param n the number of bytes to skip.
	 * @return the number of bytes actually skipped; it can be smaller than {@code n}
	 * only if the end of file has been reached.
	 * @see InputStream#skip(long)
	 */

	@Override
	public long skip(final long n) throws IOException {
		if (n <= avail) {
			final int m = (int)n;
			pos += m;
			avail -= m;
			readBytes += n;
			return n;
		}

		long toSkip = n - avail, result = 0;
		avail = 0;

		while (toSkip != 0 && (result = is == System.in ? skipByReading(toSkip) : is.skip(toSkip)) < toSkip) {
			if (result == 0) {
				if (is.read() == -1) break;
				toSkip--;
			}
			else toSkip -= result;
		}

		final long t = n - (toSkip - result);
		readBytes += t;
		return t;
	}

	@Override
	public int available() throws IOException {
		return (int)Math.min(is.available() + (long)avail, Integer.MAX_VALUE);
	}

	@Override
	public void close() throws IOException {
		if (is == null) return;
		if (is != System.in) is.close();
		is = null;
		buffer = null;
	}

	/** Resets the internal logic of this fast buffered input stream, clearing the buffer.
	 *
	 *
All buffering information is discarded, and the number of bytes read so far
	 * (and thus, also the {@linkplain #position() current position})
	 * is adjusted to reflect this fact.
	 *
	 *
This method is mainly useful for re-reading
	 * files that have been overwritten externally.
	 */

	public void flush() {
		if (is == null) return;
		readBytes += avail;
		avail = pos = 0;
	}

	/** Resets the internal logic of this fast buffered input stream.
	 *
	 * @deprecated As of {@code fastutil} 5.0.4, replaced by {@link #flush()}. The old
	 * semantics of this method does not contradict {@link InputStream}'s contract, as
	 * the semantics of {@link #reset()} is undefined if {@link InputStream#markSupported()}
	 * returns false. On the other hand, the name was really a poor choice.
	 */
	@Override
	@Deprecated
	public void reset() {
		flush();
	}
}


package it.unimi.dsi.fastutil;

/*
 * Copyright (C) 2002-2017 Sebastiano Vigna
 *
 * 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.
 */

import it.unimi.dsi.fastutil.ints.IntComparator;

import java.util.ArrayList;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

/** A class providing static methods and objects that do useful things with arrays.
 *
 *
In addition to commodity methods, this class contains {@link Swapper}-based implementations
 * of {@linkplain #quickSort(int, int, IntComparator, Swapper) quicksort} and of
 * a stable, in-place {@linkplain #mergeSort(int, int, IntComparator, Swapper) mergesort}. These
 * generic sorting methods can be used to sort any kind of list, but they find their natural
 * usage, for instance, in sorting arrays in parallel.
 *
 * @see Arrays
 */

public class Arrays {

	private Arrays() {}

	/** This is a safe value used by {@link ArrayList} (as of Java 7) to avoid
	 *  throwing {@link OutOfMemoryError} on some JVMs. We adopt the same value. */
	public static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

	/** Ensures that a range given by its first (inclusive) and last (exclusive) elements fits an array of given length.
	 *
	 *
This method may be used whenever an array range check is needed.
	 *
	 * @param arrayLength an array length.
	 * @param from a start index (inclusive).
	 * @param to an end index (inclusive).
	 * @throws IllegalArgumentException if {@code from} is greater than {@code to}.
	 * @throws ArrayIndexOutOfBoundsException if {@code from} or {@code to} are greater than {@code arrayLength} or negative.
	 */
	public static void ensureFromTo(final int arrayLength, final int from, final int to) {
		if (from < 0) throw new ArrayIndexOutOfBoundsException("Start index (" + from + ") is negative");
		if (from > to) throw new IllegalArgumentException("Start index (" + from + ") is greater than end index (" + to + ")");
		if (to > arrayLength) throw new ArrayIndexOutOfBoundsException("End index (" + to + ") is greater than array length (" + arrayLength + ")");
	}

	/** Ensures that a range given by an offset and a length fits an array of given length.
	 *
	 *
This method may be used whenever an array range check is needed.
	 *
	 * @param arrayLength an array length.
	 * @param offset a start index for the fragment
	 * @param length a length (the number of elements in the fragment).
	 * @throws IllegalArgumentException if {@code length} is negative.
	 * @throws ArrayIndexOutOfBoundsException if {@code offset} is negative or {@code offset}+{@code length} is greater than {@code arrayLength}.
	 */
	public static void ensureOffsetLength(final int arrayLength, final int offset, final int length) {
		if (offset < 0) throw new ArrayIndexOutOfBoundsException("Offset (" + offset + ") is negative");
		if (length < 0) throw new IllegalArgumentException("Length (" + length + ") is negative");
		if (offset + length > arrayLength) throw new ArrayIndexOutOfBoundsException("Last index (" + (offset + length) + ") is greater than array length (" + arrayLength + ")");
	}

	/**
	 * Transforms two consecutive sorted ranges into a single sorted range. The initial ranges are
	 * {@code [first..middle)} and {@code [middle..last)}, and the resulting range is
	 * {@code [first..last)}. Elements in the first input range will precede equal elements in
	 * the second.
	 */
	private static void inPlaceMerge(final int from, int mid, final int to, final IntComparator comp, final Swapper swapper) {
		if (from >= mid || mid >= to) return;
		if (to - from == 2) {
			if (comp.compare(mid, from) < 0) swapper.swap(from, mid);
			return;
		}

		int firstCut;
		int secondCut;

		if (mid - from > to - mid) {
			firstCut = from + (mid - from) / 2;
			secondCut = lowerBound(mid, to, firstCut, comp);
		}
		else {
			secondCut = mid + (to - mid) / 2;
			firstCut = upperBound(from, mid, secondCut, comp);
		}

		int first2 = firstCut;
		int middle2 = mid;
		int last2 = secondCut;
		if (middle2 != first2 && middle2 != last2) {
			int first1 = first2;
			int last1 = middle2;
			while (first1 < --last1)
				swapper.swap(first1++, last1);
			first1 = middle2;
			last1 = last2;
			while (first1 < --last1)
				swapper.swap(first1++, last1);
			first1 = first2;
			last1 = last2;
			while (first1 < --last1)
				swapper.swap(first1++, last1);
		}

		mid = firstCut + (secondCut - mid);
		inPlaceMerge(from, firstCut, mid, comp, swapper);
		inPlaceMerge(mid, secondCut, to, comp, swapper);
	}

	/**
	 * Performs a binary search on an already-sorted range: finds the first position where an
	 * element can be inserted without violating the ordering. Sorting is by a user-supplied
	 * comparison function.
	 *
	 * @param from the index of the first element (inclusive) to be included in the binary search.
	 * @param to the index of the last element (exclusive) to be included in the binary search.
	 * @param pos the position of the element to be searched for.
	 * @param comp the comparison function.
	 * @return the largest index i such that, for every j in the range {@code [first..i)},
	 * {@code comp.compare(j, pos)} is {@code true}.
	 */
	private static int lowerBound(int from, final int to, final int pos, final IntComparator comp) {
		// if (comp==null) throw new NullPointerException();
		int len = to - from;
		while (len > 0) {
			int half = len / 2;
			int middle = from + half;
			if (comp.compare(middle, pos) < 0) {
				from = middle + 1;
				len -= half + 1;
			}
			else {
				len = half;
			}
		}
		return from;
	}

	/**
	 * Performs a binary search on an already sorted range: finds the last position where an element
	 * can be inserted without violating the ordering. Sorting is by a user-supplied comparison
	 * function.
	 *
	 * @param from the index of the first element (inclusive) to be included in the binary search.
	 * @param to the index of the last element (exclusive) to be included in the binary search.
	 * @param pos the position of the element to be searched for.
	 * @param comp the comparison function.
	 * @return The largest index i such that, for every j in the range {@code [first..i)},
	 * {@code comp.compare(pos, j)} is {@code false}.
	 */
	private static int upperBound(int from, final int mid, final int pos, final IntComparator comp) {
		// if (comp==null) throw new NullPointerException();
		int len = mid - from;
		while (len > 0) {
			int half = len / 2;
			int middle = from + half;
			if (comp.compare(pos, middle) < 0) {
				len = half;
			}
			else {
				from = middle + 1;
				len -= half + 1;
			}
		}
		return from;
	}

	/**
	 * Returns the index of the median of the three indexed chars.
	 */
	private static int med3(final int a, final int b, final int c, final IntComparator comp) {
		int ab = comp.compare(a, b);
		int ac = comp.compare(a, c);
		int bc = comp.compare(b, c);
		return (ab < 0 ?
				(bc < 0 ? b : ac < 0 ? c : a) :
				(bc > 0 ? b : ac > 0 ? c : a));
	}

	 private static final int MERGESORT_NO_REC = 16;

	 /** Sorts the specified range of elements using the specified swapper and according to the order induced by the specified
	 * comparator using mergesort.
	 *
	 *
This sort is guaranteed to be stable: equal elements will not be reordered as a result
	 * of the sort. The sorting algorithm is an in-place mergesort that is significantly slower than a
	 * standard mergesort, as its running time is O(<var>n</var> (log <var>n</var>)2), but it does not allocate additional memory; as a result, it can be
	 * used as a generic sorting algorithm.
	 *
	 * @param from the index of the first element (inclusive) to be sorted.
	 * @param to the index of the last element (exclusive) to be sorted.
	 * @param c the comparator to determine the order of the generic data (arguments are positions).
	 * @param swapper an object that knows how to swap the elements at any two positions.
	 */
	public static void mergeSort(final int from, final int to, final IntComparator c, final Swapper swapper) {
		/*
		 * We retain the same method signature as quickSort. Given only a comparator and swapper we
		 * do not know how to copy and move elements from/to temporary arrays. Hence, in contrast to
		 * the JDK mergesorts this is an "in-place" mergesort, i.e. does not allocate any temporary
		 * arrays. A non-inplace mergesort would perhaps be faster in most cases, but would require
		 * non-intuitive delegate objects...
		 */
		final int length = to - from;

		// Insertion sort on smallest arrays
		if (length < MERGESORT_NO_REC) {
			for (int i = from; i < to; i++) {
				for (int j = i; j > from && (c.compare(j - 1, j) > 0); j--) {
					swapper.swap(j, j - 1);
				}
			}
			return;
		}

		// Recursively sort halves
		int mid = (from + to) >>> 1;
		mergeSort(from, mid, c, swapper);
		mergeSort(mid, to, c, swapper);

		// If list is already sorted, nothing left to do. This is an
		// optimization that results in faster sorts for nearly ordered lists.
		if (c.compare(mid - 1, mid) <= 0) return;

		// Merge sorted halves
		inPlaceMerge(from, mid, to, c, swapper);
	}

	/** Swaps two sequences of elements using a provided swapper.
	 *
	 * @param swapper the swapper.
	 * @param a a position in {@code x}.
	 * @param b another position in {@code x}.
	 * @param n the number of elements to exchange starting at {@code a} and {@code b}.
	 */
	protected static void swap(final Swapper swapper, int a, int b, final int n) {
		for (int i = 0; i < n; i++, a++, b++) swapper.swap(a, b);
	}

	private static final int QUICKSORT_NO_REC = 16;
	private static final int PARALLEL_QUICKSORT_NO_FORK = 8192;
	private static final int QUICKSORT_MEDIAN_OF_9 = 128;

	protected static class ForkJoinGenericQuickSort extends RecursiveAction {
		private static final long serialVersionUID = 1L;
		private final int from;
		private final int to;
		private final IntComparator comp;
		private final Swapper swapper;

		public ForkJoinGenericQuickSort(final int from, final int to, final IntComparator comp, final Swapper swapper) {
			this.from = from;
			this.to = to;
			this.comp = comp;
			this.swapper = swapper;
		}

		@Override
		protected void compute() {
			final int len = to - from;
			if (len < PARALLEL_QUICKSORT_NO_FORK) {
				quickSort(from, to, comp, swapper);
				return;
			}
			// Choose a partition element, v
			int m = from + len / 2;
			int l = from;
			int n = to - 1;
			int s = len / 8;
			l = med3(l, l + s, l + 2 * s, comp);
			m = med3(m - s, m, m + s, comp);
			n = med3(n - 2 * s, n - s, n, comp);
			m = med3(l, m, n, comp);
			// Establish Invariant: v* (<v)* (>v)* v*
			int a = from, b = a, c = to - 1, d = c;
			while (true) {
				int comparison;
				while (b <= c && ((comparison = comp.compare(b, m)) <= 0)) {
					if (comparison == 0) {
						// Fix reference to pivot if necessary
						if (a == m) m = b;
						else if (b == m) m = a;
						swapper.swap(a++, b);
					}
					b++;
				}
				while (c >= b && ((comparison = comp.compare(c, m)) >= 0)) {
					if (comparison == 0) {
						// Fix reference to pivot if necessary
						if (c == m) m = d;
						else if (d == m) m = c;
						swapper.swap(c, d--);
					}
					c--;
				}
				if (b > c) break;
				// Fix reference to pivot if necessary
				if (b == m) m = d;
				else if (c == m) m = c;
				swapper.swap(b++, c--);
			}

			// Swap partition elements back to middle
			s = Math.min(a - from, b - a);
			swap(swapper, from, b - s, s);
			s = Math.min(d - c, to - d - 1);
			swap(swapper, b, to - s, s);

			// Recursively sort non-partition-elements
			int t;
			s = b - a;
			t = d - c;
			if (s > 1 && t > 1) invokeAll(new ForkJoinGenericQuickSort(from, from + s, comp, swapper), new ForkJoinGenericQuickSort(to - t, to, comp, swapper));
			else if (s > 1) invokeAll(new ForkJoinGenericQuickSort(from, from + s, comp, swapper));
			else invokeAll(new ForkJoinGenericQuickSort(to - t, to, comp, swapper));
		}
	}

	/** Sorts the specified range of elements using the specified swapper and according to the order induced by the specified
	 * comparator using a parallel quicksort.
	 *
	 *
The sorting algorithm is a tuned quicksort adapted from Jon L. Bentley and M. Douglas
	 * McIlroy, “Engineering a Sort Function”, Software: Practice and Experience, 23(11), pages
	 * 1249−1265, 1993.
	 *
	 *
This implementation uses a {@link ForkJoinPool} executor service with {@link Runtime#availableProcessors()} parallel threads.
	 *
	 * @param from the index of the first element (inclusive) to be sorted.
	 * @param to the index of the last element (exclusive) to be sorted.
	 * @param comp the comparator to determine the order of the generic data.
	 * @param swapper an object that knows how to swap the elements at any two positions.
	 *
	 */
	public static void parallelQuickSort(final int from, final int to, final IntComparator comp, final Swapper swapper) {
		final ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
		pool.invoke(new ForkJoinGenericQuickSort(from, to, comp, swapper));
		pool.shutdown();
	}

	/** Sorts the specified range of elements using the specified swapper and according to the order induced by the specified
	 * comparator using parallel quicksort.
	 *
	 *
The sorting algorithm is a tuned quicksort adapted from Jon L. Bentley and M. Douglas
	 * McIlroy, “Engineering a Sort Function”, Software: Practice and Experience, 23(11), pages
	 * 1249−1265, 1993.
	 *
	 *
This implementation uses a {@link ForkJoinPool} executor service with {@link Runtime#availableProcessors()} parallel threads.
	 *
	 * @param from the index of the first element (inclusive) to be sorted.
	 * @param to the index of the last element (exclusive) to be sorted.
	 * @param comp the comparator to determine the order of the generic data.
	 * @param swapper an object that knows how to swap the elements at any two positions.
	 *
	 */
	public static void quickSort(final int from, final int to, final IntComparator comp, final Swapper swapper) {
		final int len = to - from;
		// Insertion sort on smallest arrays
		if (len < QUICKSORT_NO_REC) {
			for (int i = from; i < to; i++)
				for (int j = i; j > from && (comp.compare(j - 1, j) > 0); j--) {
					swapper.swap(j, j - 1);
				}
			return;
		}

		// Choose a partition element, v
		int m = from + len / 2; // Small arrays, middle element
		int l = from;
		int n = to - 1;
		if (len > QUICKSORT_MEDIAN_OF_9) { // Big arrays, pseudomedian of 9
			int s = len / 8;
			l = med3(l, l + s, l + 2 * s, comp);
			m = med3(m - s, m, m + s, comp);
			n = med3(n - 2 * s, n - s, n, comp);
		}
		m = med3(l, m, n, comp); // Mid-size, med of 3
		// int v = x[m];

		int a = from;
		int b = a;
		int c = to - 1;
		// Establish Invariant: v* (<v)* (>v)* v*
		int d = c;
		while (true) {
			int comparison;
			while (b <= c && ((comparison = comp.compare(b, m)) <= 0)) {
				if (comparison == 0) {
					// Fix reference to pivot if necessary
					if (a == m) m = b;
					else if (b == m) m = a;
					swapper.swap(a++, b);
				}
				b++;
			}
			while (c >= b && ((comparison = comp.compare(c, m)) >= 0)) {
				if (comparison == 0) {
					// Fix reference to pivot if necessary
					if (c == m) m = d;
					else if (d == m) m = c;
					swapper.swap(c, d--);
				}
				c--;
			}
			if (b > c) break;
			// Fix reference to pivot if necessary
			if (b == m) m = d;
			else if (c == m) m = c;
			swapper.swap(b++, c--);
		}

		// Swap partition elements back to middle
		int s;
		s = Math.min(a - from, b - a);
		swap(swapper, from, b - s, s);
		s = Math.min(d - c, to - d - 1);
		swap(swapper, b, to - s, s);

		// Recursively sort non-partition-elements
		if ((s = b - a) > 1) quickSort(from, from + s, comp, swapper);
		if ((s = d - c) > 1) quickSort(to - s, to, comp, swapper);
	}
}