package org.hy.common.thread;

import java.util.ArrayList;
import java.util.List;

import org.hy.common.Busway;
import org.hy.common.Date;
import org.hy.common.Execute;
import org.hy.common.Help;
import org.hy.common.StringHelp;
import org.hy.common.XJavaID;
import org.hy.common.net.ClientSocket;
import org.hy.common.xml.XJava;

import com.greenpineyu.fel.FelEngine;
import com.greenpineyu.fel.FelEngineImpl;
import com.greenpineyu.fel.context.FelContext;
import com.greenpineyu.fel.context.MapContext;





/**
 * 任务配置信息(支持云服务任务的执行)
 * 
 * @author      ZhengWei(HY)
 * @createDate  2013-12-16
 * @version     v1.0  
 *              v2.0  2014-07-21:融合XJava、任务池、线程池的功能
 *              v3.0  2015-11-03:是否在初始时(即添加到Jobs时),就执行一次任务
 *              v4.0  2016-07-08:支持轮询间隔:秒
 *              v5.0  2017-07-03:添加:1. 最后一次执行时间的记录。
 *                                     2. toString()方法中显示出下次的执行时间
 *              v5.1  2018-04-11  添加:执行次数的统计属性
 *              v5.2  2018-05-22  添加:执行历史日志
 *              v5.3  2018-08-21  修改:将xjavaID改为xid与XSQLNode统一,同时防止与接口 org.hy.common.XJavaID 中的方法冲突。
 *              v6.0  2018-11-29  添加1:开始时间组,即开始时间可以有多个。
 *                                      可实现一项任务在多个时间点上周期执行,并且只须配置一个Job,而非多个Job。
 *                                      注:此功能对 "间隔类型:秒、分" 是无效的(只取最小时间为开始时间)
 *                                添加2:在条件判定为True时,才允许执行任务。并预定义了占位符的标准。
 *                                      可实现如下场景:某任务每天8~18点间周期执行。
 *                                建议人:邹德福、张德宏
 *              v7.0  2019-02-17  添加:执行云服务上的任务。
 *                                     只须额外配置云服务的IP:端口即可。
 *                                     当然,云服务要开启通讯的,见 https://github.com/HY-Org/hy.common.net
 *              v8.0  2019-03-02  添加:按年份间隔的时间类型(都用到年周期执行的,哈哈)。
 *                                建议人:王力
 *              v9.0  2019-03-06  添加:执行日志输出到控制台及二次预防重复执行的可能。
 */
public class Job extends Task<Object> implements Comparable<Job> ,XJavaID
{
    /** 间隔类型: 秒 */
    public  static final int       $IntervalType_Second = -2;
    
    /** 间隔类型: 分钟 */
    public  static final int       $IntervalType_Minute = 60;
    
    /** 间隔类型: 小时 */
    public  static final int       $IntervalType_Hour   = 60 * $IntervalType_Minute;
    
    /** 间隔类型: 天 */
    public  static final int       $IntervalType_Day    = 24 * $IntervalType_Hour;
    
    /** 间隔类型: 周 */
    public  static final int       $IntervalType_Week   = 7  * $IntervalType_Day;
    
    /** 间隔类型: 月 */
    public  static final int       $IntervalType_Month  = 1;
    
    /** 间隔类型: 年 */
    public  static final int       $IntervalType_Year   = 2;
    
    /** 间隔类型: 手工执行 */
    public  static final int       $IntervalType_Manual = -1;
    
    
    /** 是否允许执行的条件中的表达式中,预定义占位符有:年份 */
    public  static final String    $Condition_Y         = "Y";
    
    /** 是否允许执行的条件中的表达式中,预定义占位符有:月份 */
    public  static final String    $Condition_M         = "M";

    /** 是否允许执行的条件中的表达式中,预定义占位符有:天 */
    public  static final String    $Condition_D         = "D";
    
    /** 是否允许执行的条件中的表达式中,预定义占位符有:小时(24小时制) */
    public  static final String    $Condition_H         = "H";
    
    /** 是否允许执行的条件中的表达式中,预定义占位符有:分钟 */
    public  static final String    $Condition_MI        = "MI";
    
    /** 是否允许执行的条件中的表达式中,预定义占位符有:秒 */
    public  static final String    $Condition_S         = "S";
    
    /** 是否允许执行的条件中的表达式中,预定义占位符有:年月日,格式为YYYYMMDD 样式的整数类型。整数类型是为了方便比较 */
    public  static final String    $Condition_YMD       = "YMD";
    
    /** 表达式引擎 */
    private static final FelEngine $FelEngine           = new FelEngineImpl();
    
    
    private static       long      $SerialNo            = 0;
    
    
    /** XJava池中对象的ID标识 */
    private String         xjavaID;
    
    /** 任务编号 */
    private String         code;
    
    /** 任务配置名称 */
    private String         name;
    
    /** 
     * 运行的线程数
     * taskCount=1表示单例,否则时间点一到,无论上次执行的任务是否完成,都将运行一个新的任务。
     * 此属性要与this.code配合使用,this.code做为惟一标记
     */
    private int            taskCount;
    
    /** 间隔类型 */
    private int            intervalType;
    
    /** 间隔长度 */
    private int            intervalLen;
    
    /** 开始时间组。多个开始时间用分号分隔。多个开始时间对 "间隔类型:秒、分" 是无效的(只取最小时间为开始时间) */
    private List<Date>     startTimes;
    
    /** 下次时间 */
    private Date           nextTime;
    
    /** 下次时间组。 */
    private List<Date>     nextTimes;
    
    /** 最后一次的执行时间 */
    private Date           lastTime;
    
    /** 
     * 允许执行的条件。
     * 
     *  表达式中,预定义占位符有(占位符不区分大小写):
     *    :Y    表示年份
     *    :M    表示月份
     *    :D    表示天
     *    :H    表示小时(24小时制)
     *    :MI   表示分钟
     *    :S    表示秒
     *    :YMD  表示年月日,格式为YYYYMMDD 样式的整数类型。整数类型是为了方便比较
     */
    private String         condition;
    
    /** 云计算服务器的地址端口。格式为:IP:Port */
    private String         cloudServer;
    
    /** 云计算服务器的对象 */
    private ClientSocket   cloudSocket;
    
    /** XJava对象标识 */
    private String         xid;
    
    /** 执行的方法名 */
    private String         methodName;
    
    /** 描述 */
    private String         desc;
    
    /** 是否在初始时(即添加到Jobs时),就执行一次任务(默认:不执行) */
    private boolean        isInitExecute;
    
    /** 当 isInitExecute = true 时,这个任务是串行立刻执行? 还是多线程池执行(默认:延时执行) */
    private boolean        isAtOnceExecute;
    
    private Jobs           jobs;
    
    /** 执行次数 */
    private long           runCount;
    
    /** 执行日志。记录最后1440次内的执行时间 */
    private Busway<String> runLogs;
    
    
    
    private synchronized long GetSerialNo()
    {
        return ++$SerialNo;
    }
    
    
    
    public Job()
    {
        super("$JOB$");
        
        this.startTimes      = new ArrayList<Date>();
        this.startTimes.add(Date.getNowTime().getNextHour().getFirstTimeOfHour());
        this.nextTime        = null;
        this.nextTimes       = null;
        this.intervalType    = $IntervalType_Manual;
        this.intervalLen     = 1; 
        this.taskCount       = 1;
        this.isInitExecute   = false;
        this.isAtOnceExecute = false;
        this.lastTime        = null;
        this.runCount        = 0;
        this.runLogs         = new Busway<String>(1440);
    }
    
    
    
    /**
     * 设置XJava池中对象的ID标识。此方法不用用户调用设置值,是自动的。
     * 
     * @param i_XJavaID
     */
    public void setXJavaID(String i_XJavaID)
    {
        this.xjavaID = i_XJavaID;
    }
    
    
    
    /**
     * 获取XJava池中对象的ID标识。
     * 
     * @return
     */
    public String getXJavaID()
    {
        return this.xjavaID;
    }
    
    
    
    /**
     * 获取任务描述
     * 
     * @return
     */
    public String getTaskDesc()
    {
        return Help.NVL(this.getDesc() ,Help.NVL(this.getName() ,this.getCode()));
    }
    
    
    
    /**
     * 执行任务的方法
     */
    public void execute()
    {
        if ( Help.isNull(this.xid) )
        {
            throw new NullPointerException("Job.getXid() is null.");
        }
        
        if ( Help.isNull(this.methodName) )
        {
            throw new NullPointerException("Job.getMethodName() is null."); 
        }
        
        try
        {
            Date    v_Now     = new Date();
            boolean v_IsAllow = false;
            
            if ( this.lastTime == null )
            {
                v_IsAllow     = true;
                this.lastTime = v_Now;
            }
            else if ( this.intervalType == Job.$IntervalType_Second 
                   || this.intervalType == Job.$IntervalType_Manual
                   || this.jobs         == null)
            {
                v_IsAllow     = true;
                this.lastTime = v_Now;
            }
            // 第二个地方再次预防,第一个地方在Jobs.execute()中。
            // 又添加一次预防的原因是:这里离 this.lastTime = new Date(); 最近。
            // 预防因主机系统时间不精确,时间同步机制异常(如来回调整时间、时间跳跃、时间波动等),
            // 造成定时任务重复执行的可能。  ZhengWei(HY) Add 2019-03-06
            else if ( !this.getLastTime().equalsYMDHM(v_Now) && this.getLastTime().differ(v_Now) < 0 )
            {
                v_IsAllow     = true;
                this.lastTime = v_Now;
            }
            // 手工执行时
            else if ( this.jobs != null && !this.jobs.isMonitor(this) )
            {
                v_IsAllow     = true;
                this.lastTime = v_Now;
            }
            
            if ( v_IsAllow )
            {
                this.runCount++;
                this.runLogs.put(this.lastTime.getFullMilli());
                
                // 本机执行:默认的
                if ( this.cloudSocket == null )
                {
                    Object v_Object = XJava.getObject(this.xid.trim());
                    if ( v_Object == null )
                    {
                        throw new NullPointerException("Job.getXid() = " + this.xid + " XJava.getObject(...) is null.");
                    }
                    
                    (new Execute(v_Object ,this.methodName.trim())).start();
                }
                // 云服务执行:当配置CloudServer时。
                else
                {
                    (new Execute(this.cloudSocket ,"sendCommand" ,new Object[]{this.xid ,this.methodName.trim() ,false})).start();
                }
                
                System.out.println(Date.getNowTime().getFullMilli() + " 执行定时任务 " + this.xid + ":" + this.getTaskDesc());
            }
        }
        catch (Exception exce)
        {
            exce.printStackTrace();
        }
        
        if ( this.jobs != null )
        {
            // 注意:delMonitor()方法不要加同步锁。否则会出现线程阻塞
            this.jobs.delMonitor(this);
            this.finishTask();
        }
        else
        {
            // 当 this.jobs 为空时,表示本方法是手工执行,并不是定时任务自动执行的。
        }
    }
    
    
    
    /**
     * 获取任务编号。
     * 
     * 因为每个任务对象都应当有独立的编号顺序。
     * 即每个Task实现类,实例化的第一个类的编号应当都从0开始编号,所以这个工作就由实现者来完成。
     * 
     * @return
     */
    public long getSerialNo()
    {
        return GetSerialNo();
    }
    
    
    
    /**
     * 获取:下次时间组。
     */
    public List<Date> getNextTimes()
    {
        return nextTimes;
    }



    /**
     * 获取下一次运行时间
     * 
     * @return
     */
    public Date getNextTime()
    {
        if ( this.intervalType == $IntervalType_Second )
        {
            return this.getNextTime(Date.getNowTime());
        }
        else if ( this.lastTime == null || this.nextTime == null )
        {
            return this.getNextTime(Date.getNowTime());
        }
        else if ( this.lastTime.equalsYMDHM(this.nextTime) )
        {
            return this.getNextTime(Date.getNowTime().getMinutes(1));
        }
        else
        {
            return this.getNextTime(Date.getNowTime());
        }
    }
    
    
    
    /**
     * 获取下一次运行时间
     * 
     * @return
     */
    public Date getNextTime(final Date i_Now)
    {
        if ( this.intervalType == $IntervalType_Manual )
        {
            return new Date("9999-12-31 23:59:59");
        }
        
        if ( this.nextTime == null )
        {
            // 重新创建时间对象,防止nextTime修改影响this.startTime
            // startTimes已按从小到大排序过,此处取最小时间
            this.nextTime  = new Date(this.startTimes.get(0));
            this.nextTimes = new ArrayList<Date>();
            for (Date v_STime : this.startTimes)
            {
                this.nextTimes.add(new Date(v_STime));
            }
        }
        
        if ( this.intervalType == $IntervalType_Second )
        {
            if ( i_Now.equalsYMDHMS(this.nextTime) )
            {
                // Nothing.
            }
            else if ( i_Now.getTime() > this.nextTime.getTime() )
            {
                // 为什么减1秒呢? 原因是Jobs中已等待间隔是1秒。
                this.nextTime.setTime(i_Now.getTime() + ((this.intervalLen - 1) * 1000));
            }
        }
        else if ( i_Now.equalsYMDHM(this.nextTime) )
        {
            // Nothing.
        }
        else if ( i_Now.getTime() > this.nextTime.getTime() )
        {
            // 间隔类型: 分钟 小时 天 周
            if ( this.intervalType >= $IntervalType_Minute )
            {
                // 为了性能,所以在if分支语句中写for
                for (Date v_NextTime : this.nextTimes)
                {
                    if ( i_Now.getTime() <= v_NextTime.getTime() )
                    {
                        continue;
                    }
                    
                    long v_DiffSec = (i_Now.getTime() - v_NextTime.getTime()) / 1000;
                    long v_PerC    = this.intervalType;
                    long v_Value   = ((int)(v_DiffSec / v_PerC)) * v_PerC;
                    
                    if ( v_Value < v_PerC * this.intervalLen )
                    {
                        v_Value = v_PerC * this.intervalLen;
                    }
                    else if ( v_Value == v_PerC * this.intervalLen )
                    {
                        // Nothing.
                    }
                    else if ( v_Value % (v_PerC * this.intervalLen) == 0 )
                    {
                        v_Value += v_PerC * this.intervalLen;
                    }
                    
                    v_Value = ((int)(v_Value / (v_PerC * this.intervalLen))) * v_PerC * this.intervalLen * 1000;
                    v_NextTime.setTime(v_NextTime.getTime() + v_Value);
                }
            }
            // 间隔类型: 月
            else if ( this.intervalType == $IntervalType_Month )
            {
                // 为了性能,所以在if分支语句中写for
                for (Date v_NextTime : this.nextTimes)
                {
                    if ( i_Now.getTime() <= v_NextTime.getTime() )
                    {
                        continue;
                    }
                    
                    int v_AddCount = 0;
                    while ( i_Now.getTime() >= v_NextTime.getTime() )
                    {
                        v_NextTime.setDate(v_NextTime.getNextMonth());
                        v_AddCount++;
                    }
                    
                    // 计算间隔
                    int i = 0;
                    if ( this.intervalLen == 1 )
                    {
                        i = 1;
                    }
                    else
                    {
                        i = v_AddCount % this.intervalLen;
                    }
                    for (; i<this.intervalLen; i++)
                    {
                        v_NextTime.setDate(v_NextTime.getNextMonth());
                    }
                }
            }
            // 间隔类型: 年
            else if ( this.intervalType == $IntervalType_Year )
            {
                // 为了性能,所以在if分支语句中写for
                for (Date v_NextTime : this.nextTimes)
                {
                    if ( i_Now.getTime() <= v_NextTime.getTime() )
                    {
                        continue;
                    }
                    
                    int v_AddCount = 0;
                    while ( i_Now.getTime() >= v_NextTime.getTime() )
                    {
                        v_NextTime.setDate(v_NextTime.getNextYear());
                        v_AddCount++;
                    }
                    
                    // 计算间隔
                    int i = 0;
                    if ( this.intervalLen == 1 )
                    {
                        i = 1;
                    }
                    else
                    {
                        i = v_AddCount % this.intervalLen;
                    }
                    for (; i<this.intervalLen; i++)
                    {
                        v_NextTime.setDate(v_NextTime.getNextYear());
                    }
                }
            }
            
            Help.toSort(this.nextTimes);
            this.nextTime = this.nextTimes.get(0);
        }
        
        return this.nextTime;
    }
    
    
    /**
     * 获取:任务编号
     */
    public String getCode()
    {
        return code;
    }

    
    /**
     * 设置:任务编号
     * 
     * @param code 
     */
    public void setCode(String code)
    {
        this.code = code;
    }



    /**
     * 获取:间隔类型
     */
    public int getIntervalType()
    {
        return intervalType;
    }
    
    
    /**
     * 设置:间隔类型
     * 
     * @param intervalType 
     */
    public void setIntervalType(int intervalType)
    {
        this.nextTime     = null;
        this.nextTimes    = null;
        this.intervalType = intervalType;
    }
    
    
    /**
     * 间隔长度
     *
     * @return
     */
    public int getIntervalLen()
    {
        return intervalLen;
    }


    /**
     * 设置:间隔长度
     * 
     * @param i_IntervalLen
     */
    public void setIntervalLen(int i_IntervalLen)
    {
        if ( i_IntervalLen >= 1 )
        {
            this.intervalLen = i_IntervalLen;
        }
    }

    
    /**
     * 获取:运行的线程数
     * taskCount=1表示单例,否则时间点一到,无论上次执行的任务是否完成,都将运行一个新的任务。
     * 此属性要与this.code配合使用,this.code做为惟一标记
     */
    public int getTaskCount()
    {
        return taskCount;
    }

    
    /**
     * 设置:运行的线程数
     * taskCount=1表示单例,否则时间点一到,无论上次执行的任务是否完成,都将运行一个新的任务。
     * 此属性要与this.code配合使用,this.code做为惟一标记
     * 
     * @param taskCount 
     */
    public void setTaskCount(int taskCount)
    {
        this.taskCount = taskCount;
    }


    /**
     * 获取:任务配置名称
     */
    public String getName()
    {
        return name;
    }

    
    /**
     * 设置:任务配置名称
     * 
     * @param name 
     */
    public void setName(String name)
    {
        this.name = name;
    }


    /**
     * 获取:开始时间组。多个开始时间用分号分隔。多个开始时间对 "间隔类型:秒、分" 是无效的(只取最小时间为开始时间)
     * 
     * @return
     */
    public List<Date> getStartTimes()
    {
        return this.startTimes;
    }

    
    /**
     * 设置:开始时间组。多个开始时间用分号分隔。多个开始时间对 "间隔类型:秒、分" 是无效的(只取最小时间为开始时间)
     * 
     * @param startTimes 
     */
    public void setStartTimes(List<Date> startTimes)
    {
        this.startTimes = startTimes;
        Help.toSort(this.startTimes);
    }


    /**
     * 设置:开始时间组。多个开始时间用分号分隔。多个开始时间对 "间隔类型:秒、分" 是无效的(只取最小时间为开始时间)
     * 
     * @param i_StartTimesStr
     */
    public void setStartTime(String i_StartTimesStr)
    {
        if ( Help.isNull(i_StartTimesStr) )
        {
            return;
        }
        
        this.startTimes = new ArrayList<Date>();
        String [] v_STimeArr = StringHelp.replaceAll(i_StartTimesStr ,new String[]{"\t" ,"\n" ,"\r"} ,new String[]{""}).split(",");
        for (String v_STime : v_STimeArr)
        {
            this.startTimes.add(new Date(v_STime.trim()));
        }
        
        Help.toSort(this.startTimes);
    }


    /**
     * 获取:描述
     */
    public String getDesc()
    {
        return Help.NVL(this.desc);
    }

    
    /**
     * 设置:描述
     * 
     * @param xid 
     */
    public void setDesc(String i_Desc)
    {
        this.desc = i_Desc;
    }
    
    
    /**
     * 获取:云计算服务器的地址端口。格式为:IP:Port
     */
    public String getCloudServer()
    {
        return cloudServer;
    }

    
    /**
     * 设置:云计算服务器的地址端口。格式为:IP:Port。
     * 
     * 默认端口是:1721
     * 
     * @param i_CloudServer 
     */
    public void setCloudServer(String i_CloudServer)
    {
        if ( Help.isNull(i_CloudServer) )
        {
            this.cloudSocket = null;
            this.cloudServer = null;
            return;
        }
        
        this.cloudServer = StringHelp.replaceAll(i_CloudServer ,new String[]{"," ," " ,"\t" ,"\r" ,"\n"} ,new String[]{"," ,""});
        
        String [] v_HostPort = (this.cloudServer.trim() + ":1721").split(":");
        this.cloudSocket = new ClientSocket(v_HostPort[0] ,Integer.parseInt(v_HostPort[1]));
    }


    /**
     * 获取:XJava对象标识
     */
    public String getXid()
    {
        return xid;
    }

    
    /**
     * 设置:XJava对象标识
     * 
     * @param xid 
     */
    public void setXid(String xid)
    {
        this.xid = xid;
    }


    /**
     * 获取:执行的方法名
     */
    public String getMethodName()
    {
        return methodName;
    }

    
    /**
     * 设置:执行的方法名
     * 
     * @param methodName 
     */
    public void setMethodName(String methodName)
    {
        this.methodName = methodName;
    }


    public void setMyJobs(Jobs jobs)
    {
        this.jobs = jobs;
    }
    
    
    
    /**
     * 获取:是否在初始时(即添加到Jobs时),就执行一次任务(默认:不执行)
     */
    public boolean isInitExecute()
    {
        return isInitExecute;
    }

    
    /**
     * 设置:是否在初始时(即添加到Jobs时),就执行一次任务(默认:不执行)
     * 
     * @param isInitExecute 
     */
    public void setInitExecute(boolean isInitExecute)
    {
        this.isInitExecute = isInitExecute;
    }

    
    /**
     * 获取:当 isInitExecute = true 时,这个任务是串行立刻执行? 还是多线程池执行(默认:延时执行)
     */
    public boolean isAtOnceExecute()
    {
        return isAtOnceExecute;
    }

    
    /**
     * 设置:当 isInitExecute = true 时,这个任务是串行立刻执行? 还是多线程池执行(默认:延时执行)
     * 
     * @param isAtOnceExecute 
     */
    public void setAtOnceExecute(boolean isAtOnceExecute)
    {
        this.isAtOnceExecute = isAtOnceExecute;
    }

    
    /**
     * 获取:最后一次的执行时间
     */
    public Date getLastTime()
    {
        return lastTime;
    }

    
    /**
     * 设置:最后一次的执行时间
     * 
     * @param lastTime 
     */
    public void setLastTime(Date lastTime)
    {
        this.lastTime = lastTime;
    }
    

    
    /**
     * 获取:执行次数
     */
    public long getRunCount()
    {
        return runCount;
    }
    

    
    /**
     * 设置:执行次数
     * 
     * @param runCount 
     */
    public void setRunCount(long runCount)
    {
        this.runCount = runCount;
    }
    

    
    /**
     * 获取:执行日志。记录最后1440次内的执行时间
     */
    public Busway<String> getRunLogs()
    {
        return runLogs;
    }
    

    
    /**
     * 设置:执行日志。记录最后1440次内的执行时间
     * 
     * @param runLogs 
     */
    public void setRunLogs(Busway<String> runLogs)
    {
        this.runLogs = runLogs;
    }


    
    /**
     * 获取:允许执行的条件。
     * 
     *  表达式中,预定义占位符有(占位符不区分大小写):
     *    :Y    表示年份
     *    :M    表示月份
     *    :D    表示天
     *    :H    表示小时(24小时制)
     *    :MI   表示分钟
     *    :S    表示秒
     *    :YMD  表示年月日,格式为YYYYMMDD 样式的整数类型。整数类型是为了方便比较
     */
    public String getCondition()
    {
        return condition;
    }


    
    /**
     * 设置:允许执行的条件。
     * 
     *  表达式中,预定义占位符有(占位符不区分大小写):
     *    :Y    表示年份
     *    :M    表示月份
     *    :D    表示天
     *    :H    表示小时(24小时制)
     *    :MI   表示分钟
     *    :S    表示秒
     *    :YMD  表示年月日,格式为YYYYMMDD 样式的整数类型。整数类型是为了方便比较
     * 
     * @param i_Condition 
     */
    public void setCondition(String i_Condition)
    {
        this.condition = Help.NVL(i_Condition).toUpperCase();
        this.condition = StringHelp.replaceAll(this.condition 
                                              ,new String[]{
                                                            ":" + $Condition_YMD
                                                           ,":" + $Condition_S
                                                           ,":" + $Condition_MI
                                                           ,":" + $Condition_H
                                                           ,":" + $Condition_D
                                                           ,":" + $Condition_D
                                                           ,":" + $Condition_Y
                                                           } 
                                              ,new String[]{
                                                            $Condition_YMD
                                                           ,$Condition_S
                                                           ,$Condition_MI
                                                           ,$Condition_H
                                                           ,$Condition_D
                                                           ,$Condition_M
                                                           ,$Condition_Y
                                                            });
    }
    
    
    
    /**
     * 是否允许执行
     * 
     * @author      ZhengWei(HY)
     * @createDate  2018-11-29
     * @version     v1.0
     *
     * @param i_Now  当前时间
     * @return
     */
    public boolean isAllow(final Date i_Now)
    {
        if ( this.jobs != null )
        {
            if ( this.jobs.isDisasterRecovery() )
            {
                if ( !this.jobs.isMaster() )
                {
                    return Jobs.$JOB_DisasterRecoverys_Check.equals(this.xjavaID);
                }
            }
        }
        
        if ( Help.isNull(this.condition) )
        {
            return true;
        }
        
        try
        {
            FelContext v_FelContext = new MapContext();
            
            v_FelContext.set($Condition_Y   ,i_Now.getYear());
            v_FelContext.set($Condition_M   ,i_Now.getMonth());
            v_FelContext.set($Condition_D   ,i_Now.getDay());
            v_FelContext.set($Condition_H   ,i_Now.getHours());
            v_FelContext.set($Condition_MI  ,i_Now.getMinutes());
            v_FelContext.set($Condition_S   ,i_Now.getSeconds());
            v_FelContext.set($Condition_YMD ,Integer.parseInt(i_Now.getYMD_ID()));
            
            return (Boolean) $FelEngine.eval(this.condition ,v_FelContext);
        }
        catch (Exception exce)
        {
            throw new RuntimeException("Fel[" + this.condition + "] is error." + exce.getMessage());
        }
    }



    public int compareTo(Job i_Other)
    {
        if ( i_Other == null )
        {
            return 1;
        }
        else
        {
            int v_Ret = this.getNextTime().compareTo(i_Other.getNextTime());
            
            if ( v_Ret == 0 )
            {
                return this.getCode().compareTo(i_Other.getCode());
            }
            else
            {
                return v_Ret;
            }
        }
    }
    
    
    public String toString()
    {
        if ( this.nextTime == null )
        {
            return this.getTaskDesc();
        }
        else
        {
            return this.nextTime.getFull() + " " + this.getTaskDesc();
        }
    }

}