text:
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
(function (global){
'use strict';

var AV = global.AV = global.AV || {};
AV.realtime = require('./realtime');

}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./realtime":2}],2:[function(require,module,exports){
(function (global){
/**
 * @author wangxiao
 * @date 2015-05-25
 * @homepage http://github.com/leancloud/js-realtime-sdk/
 *
 * 每位工程师都有保持代码优雅的义务
 * Each engineer has a duty to keep the code elegant
 */

'use strict';

var tool = require('./tool');
var ajax = tool.ajax;
var extend = tool.extend;

// 当前版本
var VERSION = '2.2.1';

// 配置项
var config = {
  // 心跳时间(一分钟)
  heartbeatsTime: 60 * 1000,
  // ws 在 browserify 打包时会使用浏览器内置的 WebSocket 实现
  WebSocket: require('ws')
};

// 命名空间,挂载私有方法
var engine = {};

// realtime 对象内,会被派发的全部事件名
var eNameIndex = {
  // session 连接建立完毕
  open: 'open',
  // 断开重连
  reuse: 'reuse',
  // websocket 连接关闭
  close: 'close',
  // 新建一个 conversation 时派发
  create: 'create',
  // conversation 新增加成员
  join: 'join',
  // conversation 成员离开
  left: 'left',
  // conversation 内发送的数据
  message: 'message',
  // conversation 消息回执
  receipt: 'receipt',
  // conversation 更新
  update: 'update',
  // 各种错误
  error: 'error'
};

// 生成 conversation 对象,挂载所有 conversation 相关方法,每次调用实例化
var newConvObject = function newConvObject(cache) {

  var addOrRemove = function addOrRemove(cid, argument, callback, cmd) {
    var members = [];
    var options;
    var fun;
    var eventName;

    // 传入 userId
    if (typeof argument === 'string') {
      members.push(argument);
    } else {
      // 传入多个 userId
      members = argument;
    }
    options = {
      cid: cid,
      members: members,
      serialId: engine.getSerialId(cache)
    };
    switch (cmd) {
      case 'add':
        eventName = 'conv-added';
        engine.convAdd(cache, options);
        break;
      case 'remove':
        eventName = 'conv-removed';
        engine.convRemove(cache, options);
        break;
    }
    fun = function (data) {
      if (data.i === options.serialId) {
        if (callback) {
          callback(data);
        }
        cache.ec.off(eventName, fun);
      }
    };
    cache.ec.on(eventName, fun);
    return this;
  };

  return {
    // cid 即 conversation id
    id: '',
    // 创建 Conversation 时的默认属性
    attr: {},
    add: function add(argument, callback) {
      addOrRemove(this.id, argument, callback, 'add');
      return this;
    },
    remove: function remove(argument, callback) {
      addOrRemove(this.id, argument, callback, 'remove');
      return this;
    },
    // 自己加入
    join: function join(callback) {
      this.add(cache.options.peerId, callback);
      return this;
    },
    // 自己离开
    leave: function leave(callback) {
      this.remove(cache.options.peerId, callback);
      return this;
    },
    send: function send(data, argument1, argument2) {
      var callback;
      var options = {};
      var me = this;
      switch (arguments.length) {
        // 只有两个参数时,第二个是回调函数
        case 2:
          callback = argument1;
          break;
        // 三个参数时,第二个参数是配置项,第三个参数是回调
        case 3:
          options = argument1;
          callback = argument2;
          break;
      }
      options.cid = me.id;
      options.serialId = engine.getSerialId(cache);

      // 如果 type 存在,则发送多媒体格式
      if (options.type) {
        options.data = engine.setMediaMsg(cache, options.type, data);
      } else {
        if (typeof data === 'string') {
          options.data = data;
        } else {
          // 协议中只接收 string 类型
          options.data = JSON.stringify(data);
        }
      }

      // 是否需要消息回执
      if (options.receipt) {
        options.receipt = 1;
      }

      // 如果是暂态消息,则不需回调,服务器也不会返回回调
      if (!options.transient) {
        var fun = function fun(data) {
          if (data.i === options.serialId) {
            if (callback) {
              callback(data);
            }
            cache.ec.off('ack', fun);
          }
        };
        cache.ec.on('ack', fun);
      }
      engine.send(cache, options, callback);
        return this;
    },
    log: function log(argument, callback) {
      var options = {};
      switch (arguments.length) {
        // 如果只有一个参数,那么是 callback
        case 1:
          callback = argument;
          break;
        case 2:
          options = argument;
          break;
      }
      options.cid = options.cid || this.id;
      options.serialId = options.serialId || engine.getSerialId(cache);
      var fun = function fun(data) {
        if (data.i === options.serialId) {
          if (callback) {
            // 对查出的类型进行过滤,兼容多端通信
            for (var i = 0, l = data.logs.length; i < l; i++) {
              data.logs[i].data = engine.getMediaMsg(cache, data.logs[i].data);
              // 增加字段,兼容接收消息的字段
              data.logs[i].fromPeerId = data.logs[i].from;
              data.logs[i].msg = data.logs[i].data;
            }
            callback(data.logs);
          }
          cache.ec.off('logs', fun);
        }
      };
      cache.ec.on('logs', fun);

      // 注:立刻获取消息历史有可能取不到
      engine.convLog(cache, options);
      return this;
    },
    receive: function receive(callback) {
      var id = this.id;
      cache.ec.on(eNameIndex.message, function (data) {
        // 是否是当前 room 的信息
        if (id === data.cid) {
          callback(data);
        }
      });
      return this;
    },
    // 获取信息回执
    receipt: function receipt(callback) {
      var id = this.id;
      cache.ec.on(eNameIndex.receipt, function (data) {
        // 是否是当前 room 的信息
        if (id === data.cid) {
          callback(data);
        }
      });
      return this;
    },
    list: function list(callback) {
      var options = {};
      var id = this.id;
      options.where = {
        objectId: id
      };
      options.serialId = engine.getSerialId(cache);
      var fun = function fun(data) {
        if (data.i === options.serialId) {
          if (callback) {
            if (data.results.length) {
              // 因为是查询固定的 cid,所以结果只有一个。
              callback(data.results[0].m);
            } else {
              callback([]);
            }
          }
          cache.ec.off('conv-results', fun);
        }
      };
      cache.ec.on('conv-results', fun);
      engine.convQuery(cache, options);
      return this;
    },
    count: function count(callback) {
      var id = this.id;
      var options = {
        cid: id,
        serialId: engine.getSerialId(cache)
      };
      var fun = function fun(data) {
        if (data.i === options.serialId) {
          if (callback) {
            callback(data.count);
          }
          cache.ec.off('conv-result', fun);
        }
      };
      cache.ec.on('conv-result', fun);
      engine.convCount(cache, options);
      return this;
    },
    update: function update(data, callback) {
      var id = this.id;
      var options = {
        cid: id,
        data: data,
        serialId: engine.getSerialId(cache)
      };
      var fun = function fun(data) {
        if (data.i === options.serialId) {
          if (callback) {
            callback(data);
          }
          cache.ec.off('conv-updated', fun);
        }
      };
      cache.ec.on('conv-updated', fun);
      engine.convUpdate(cache, options);
      return this;
    }
  };
};

// 创建一个新的 realtime 对象,挂载所有 realtime 中的方法,每次调用实例化一个实例,支持单页多实例。
var newRealtimeObject = function newRealtimeObject() {

  // 缓存一些已经实例化的变量
  var cache = {
    // 基础配置,包括 appId,peerId 等
    options: undefined,
    // WebSocket 实例
    ws: undefined,
    // 事件中心
    ec: undefined,
    // 所有已生成的 conversation 对象
    conv: {},
    // 是否已经 open 完毕,主要在 close 方法中检测
    openFlag: false,
    // 是否是用户关闭,如果不是将会断开重连
    closeFlag: false,
    // reuse 事件的重试 timer
    reuseTimer: undefined,
    // resuse 状态,如果为 true 表示内部已经在重试中
    reuseFlag: false,
    // 当前的 serialId
    serialId: 2015
  };

  return {
    clientId: '',
    cache: cache,
    open: function open(callback) {
      var me = this;
      var cache = this.cache;
      cache.closeFlag = false;
      engine.getServer(cache, cache.options, function (data) {
        if (data) {
          engine.connect(cache, {
            server: cache.server
          });
        }
      });
      if (callback) {
        cache.ec.once(eNameIndex.open, callback);
      }
      // 断开重连
      cache.ec.once(eNameIndex.reuse, function () {
        if (cache.reuseTimer) {
          clearTimeout(cache.reuseTimer);
        }
        cache.reuseTimer = setTimeout(function () {
          me.open();
        }, 5000);
      });
      return this;
    },
    // 表示关闭当前的 session 连接和 WebSocket 连接,并且回收内存
    close: function close() {
      var cache = this.cache;
      if (!cache.openFlag) {
        throw new Error('Must call after open() has successed.');
      }
      cache.closeFlag = true;
      engine.closeSession(cache);
      cache.ws.close();
      return this;
    },
    on: function on(eventName, callback) {
      this.cache.ec.on(eventName, callback);
      return this;
    },
    once: function once(eventName, callback) {
      this.cache.ec.once(eventName, callback);
      return this;
    },
    emit: function emit(eventName, data) {
      this.cache.ec.emit(eventName, data);
      return this;
    },
    off: function off(eventName, callback) {
      this.cache.ec.off(eventName, callback);
      return this;
    },
    room: function room(argument, callback) {
      var cache = this.cache;
      if (!cache.openFlag) {
        throw new Error('Must call after open() has successed.');
      }

      var convObject;

      // 传入 convId
      if (typeof argument === 'string') {
        var convId = argument;

        // 优先使用 cache 中的 conv
        if (cache.conv[convId]) {
          convObject = cache.conv[convId];
        } else {
          convObject = newConvObject(cache);
        }

        // 去服务器端判断下当前 room id 是否存在
        this.query({
          where: {
            objectId: convId
          }
        }, function (data) {

          // 如果服务器端有这个 id
          if (data.length) {
            convObject.id = convId;
            convObject.name = data[0].name;
            // 获取初始化时的属性
            convObject.attr = data[0].attr;
            // 将 conv 写入 cache
            cache.conv[convId] = convObject;
          }

          if (callback) {
            // 如果服务器端存在就直接返回 roomObject
            if (data.length) {
              callback(convObject);
            } else {
              // 如果服务器端不存在这个 room id
              callback(null);
            }
          }
        });
      } else {
        // 传入 options
        // 如果没有传入参数,则给一个错误提示
        if (!argument) {
          throw new Error('Createing room must have a callback function.');
        }

        var options;

        // 只传入 callback
        if (typeof argument === 'function') {
          callback = argument;
        } else {
          // 传入参数
          options = argument;
        }

        options = {
          // Room 的名字
          name: options.name || '',
          // 人员的 id list
          members: options.members || [],
          // 默认的数据,可以放 Conversation 名字等
          attr: options.attr || {},
          transient: options.transient || false,
          serialId: engine.getSerialId(cache)
        };

        convObject = newConvObject(cache);

        engine.startConv(cache, options, callback);

        // 服务器端确认收到对话创建,并创建成功
        var fun = function fun(data) {
          if (data.i === options.serialId) {
            convObject.id = data.cid;
            convObject.name = options.name;
            convObject.attr = options.attr;
            // 将 conv 写入 cache
            cache.conv[convObject.id] = convObject;
            if (callback) {
              callback(convObject);
            }
            cache.ec.emit(eNameIndex.create, data);
            cache.ec.off('conv-started', fun);
          }
        };
        cache.ec.on('conv-started', fun);
      }
      return convObject;
    },
    // conv 就是 room 的别名
    conv: function conv() {
      return this.room.apply(this, arguments);
    },
    // 相关查询,包括用户列表查询,房间查询等
    query: function query(argument, callback) {
      var cache = this.cache;
      if (!cache.openFlag) {
        throw new Error('Must call after open() has successed.');
      }
      var options = {};
      switch (arguments.length) {
        // 如果只有一个参数,那么是 callback
        case 1:
          callback = argument;
          break;
        case 2:
          options = argument;
          break;
      }
      options.serialId = engine.getSerialId(cache);
      var fun = function fun(data) {
        if (data.i === options.serialId) {
          if (callback) {
            callback(data.results);
          }
          cache.ec.off('conv-results', fun);
        }
      };
      cache.ec.on('conv-results', fun);
      engine.convQuery(cache, options);
      return this;
    },
    // 判断用户是否在线
    ping: function ping(argument, callback) {
      var cache = this.cache;
      if (!cache.openFlag) {
        throw new Error('Must call after open() has successed.');
      }
      if (!callback) {
        throw new Error('Ping must have callback.');
      }
      var peerIdList = [];
      // 传入一个 id
      if (typeof argument === 'string') {
        peerIdList.push(argument);
      } else {
        // 传入的是数组
        peerIdList = argument;
      }
      var options = {
        serialId: engine.getSerialId(cache),
        peerIdList: peerIdList
      };
      var fun = function fun(data) {
        if (data.i === options.serialId) {
          callback(data.onlineSessionPeerIds);
          cache.ec.off('session-query-result', fun);
        }
      };
      cache.ec.on('session-query-result', fun);
      engine.querySession(cache, options);
      return this;
    }
  };
};

// 主函数,启动通信并获得 realtimeObject
var realtime = function realtime(options, callback) {
  if (typeof options !== 'object') {
    throw new Error('realtime need a argument at least.');
  } else if (!options.appId) {
    throw new Error('Options must have appId.');
  } else if (!config.WebSocket) {
    console.error('Browser must support WebSocket, please read LeanCloud doc and use plugin.');
  } else {

    // 通过判断插件库中的对象是否存在来检测是否需要关掉安全链接,在需要兼容 flash 的时候需要关掉,默认开启。
    var secure = config.WebSocket.loadFlashPolicyFile ? false : true;

    options = {
      // LeanCloud 中唯一的服务 id
      appId: options.appId,
      // clientId 对应的就是 peerId,如果不传入服务器会自动生成,客户端没有持久化该数据。
      peerId: options.clientId,
      clientId: options.clientId,
      // 是否开启 HTML 转义,防止 XSS 攻击,默认关闭
      encodeHTML: options.encodeHTML || false,
      // 是否开启服务器端认证,传入认证函数
      auth: options.auth,
      // 是否关闭 WebSocket 的安全链接,即由 wss 协议转为 ws 协议,关闭 SSL 保护。默认开启。
      secure: typeof options.secure === 'undefined' ? secure : options.secure,
      // 服务器地区选项,默认为中国大陆
      region: options.region || 'cn'
    };

    var realtimeObj = newRealtimeObject();
    realtimeObj.clientId = options.clientId;
    realtimeObj.cache.options = options;
    realtimeObj.cache.ec = tool.eventCenter();
    realtimeObj.cache.authFun = options.auth;
    realtimeObj.open(callback);

    return realtimeObj;
  }
};

// 赋值版本号
realtime.version = VERSION;

// 挂载私有方法
realtime._tool = tool;
realtime._engine = engine;

realtime.config = function (newConfig) {
  extend(config, newConfig);
};

// WebSocket Open
engine.wsOpen = function (cache) {
  engine.bindEvent(cache);
  engine.openSession(cache, {
    serialId: engine.getSerialId(cache)
  });
  // 启动心跳
  engine.heartbeats(cache);
  // 启动守护进程
  engine.guard(cache);
};

// WebSocket Close
engine.wsClose = function (cache, event) {
  // 派发全局 close 事件,表示 realtime 已经关闭
  cache.ec.emit(eNameIndex.close, event);
};

// WebSocket Message
engine.wsMessage = function (cache, msg) {
  var data = JSON.parse(msg.data);

  // 对服务端返回的数据进行逻辑包装
  if (data.cmd) {
    var eventName = data.cmd;
    if (data.op) {
      eventName += '-' + data.op;
    }
    cache.ec.emit(eventName, data);
  }
};

engine.wsError = function (cache, data) {
  cache.ec.emit(eNameIndex.error, data);
  throw data;
};

// WebSocket send message
engine.wsSend = function (cache, data) {
  if (!cache.closeFlag) {
    if (!cache.ws) {
      throw new Error('The realtimeObject must opened first. Please listen to the "open" event.');
    } else {
      data.peerId = cache.options.peerId;
      cache.ws.send(JSON.stringify(data));
    }
  }
};

engine.createSocket = function (cache, server) {
  if (cache.ws) {
    cache.ws.close();
  }
  var ws = new config.WebSocket(server);
  cache.ws = ws;
  ws.addEventListener('open', function () {
    engine.wsOpen(cache);
  });
  ws.addEventListener('close', function (event) {
    engine.wsClose(cache, event);
  });
  ws.addEventListener('message', function (msg) {
    engine.wsMessage(cache, msg);
  });
  ws.addEventListener('error', function (data) {
    engine.wsError(cache, data);
  });
};

// 心跳程序
engine.heartbeats = function (cache) {

  // 当前 RealtimeObject 已经启动心跳程序
  if (cache.openFlag) {
    return;
  }

  var timer;
  cache.ws.addEventListener('message', function () {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(function () {
      cache.ws.send('{}');
    }, config.heartbeatsTime);
  });
};

// 守护进程,会派发 reuse 重连事件
engine.guard = function (cache) {

  // 当前 RealtimeObject 已经启动守护进程
  if (cache.openFlag) {
    return;
  }

  // 超时是三分钟
  var timeLength = 3 * 60 * 1000;
  var timer;

  // 结合心跳事件,如果长时间没有收到服务器的心跳,也要触发重连机制
  cache.ws.addEventListener('message', function () {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(function () {
      if (!cache.closeFlag && !cache.reuseFlag) {
        cache.reuseFlag = true;
        // 超时则派发重试事件
        cache.ec.emit(eNameIndex.reuse);
      }
    }, timeLength);
  });

  // 监测断开事件
  cache.ec.on(eNameIndex.close + ' ' + 'session-closed', function () {
    if (!cache.closeFlag && !cache.reuseFlag) {
      cache.reuseFlag = true;
      cache.ec.emit(eNameIndex.reuse);
    }
  });
};

engine.connect = function (cache, options) {
  var server = options.server;
  // 判断获取出缓存的时间是否是比较新的
  if (server && tool.now() <= server.expires) {
    engine.createSocket(cache, server.server);
  } else {
    cache.ec.emit(eNameIndex.error);
    throw new Error('WebSocket connet failed.');
  }
};

engine.getServer = function (cache, options, callback) {
  var appId = options.appId;
  // 是否获取 wss 的安全链接
  var secure = options.secure;
  var url = '';
  var protocol = 'http://';
  if (global.location && global.location.protocol === 'https:' && secure) {
    protocol = 'https://';
  }
  var node = '';
  switch (options.region) {
    case 'cn':
      node = 'g0';
      break;
    case 'us':
      node = 'a0';
      break;
    default:
      throw new Error('There is no this region.');
  }
  url = protocol + 'router-' + node + '-push.leancloud.cn/v1/route';
  url += '?_t=' + tool.now() + '&appId=' + appId;
  if (secure) {
    url += '&secure=1';
  }
  ajax(url, function (error, data) {
    if (data) {
      data.expires = tool.now() + data.ttl * 1000;
      cache.server = data;
      callback(data);
    } else {
      cache.ec.emit(eNameIndex.error);
    }
  });
};

// 打开 session
engine.openSession = function (cache, options) {
  var cmd = {
    cmd: 'session',
    op: 'open',
    appId: cache.options.appId,
    ua: 'js/' + VERSION,
    i: options.serialId
  };
  if (cache.authFun) {
    cache.authFun({
      clientId: cache.options.peerId
    }, function (authResult) {
      if (authResult && authResult.signature) {
        cmd.n = authResult.nonce;
        cmd.t = authResult.timestamp;
        cmd.s = authResult.signature;
        engine.wsSend(cache, cache.cmd);
      } else {
        throw new Error('Session open denied by application: ' + authResult);
      }
    });
  } else {
    engine.wsSend(cache, cmd);
  }
};

engine.closeSession = function (cache) {
  engine.wsSend(cache, cache, {
    cmd: 'session',
    op: 'close'
  });
};

engine.startConv = function (cache, options) {
  var cmd = {
    cmd: 'conv',
    op: 'start',
    // m [] 初始的对话用户id列表,服务器默认会把自己加入
    m: options.members,
    // attr json对象,对话的任意初始属性
    attr: {
      name: options.name || '',
      attr: options.attr || {}
    },
    i: options.serialId,
    // 是否是开放聊天室,无人数限制
    transient: options.transient || false
  };
  if (cache.authFun) {
    cache.authFun({
      clientId: cache.options.peerId,
      members: options.members
    }, function (authResult) {
      if (authResult && authResult.signature) {
        cmd.n = authResult.nonce;
        cmd.t = authResult.timestamp;
        cmd.s = authResult.signature;
        engine.wsSend(cache, cmd);
      } else {
        throw new Error('Conversation creation denied by application: ' + authResult);
      }
    });
  } else {
    engine.wsSend(cache, cmd);
  }
};

engine.convAdd = function (cache, options) {
  var cmd = {
    cmd: 'conv',
    op: 'add',
    cid: options.cid,
    m: options.members,
    i: options.serialId
  };
  if (cache.authFun) {
    cache.authFun({
      clientId: cache.options.peerId,
      members: options.members,
      convId: options.cid,
      action: 'invite'
    }, function (authResult) {
      if (authResult && authResult.signature) {
        cmd.n = authResult.nonce;
        cmd.t = authResult.timestamp;
        cmd.s = authResult.signature;
        engine.wsSend(cache, cmd);
      } else {
        throw new Error('Adding members to conversation denied by application: ' + authResult);
      }
    });
  } else {
    engine.wsSend(cache, cmd);
  }
};

engine.convRemove = function (cache, options) {
  var cmd = {
    cmd: 'conv',
    op: 'remove',
    cid: options.cid,
    m: options.members,
    i: options.serialId
  };
  if (cache.authFun && (options.members.length > 1 || options.members[0] != cache.options.peerId)) {
    cache.authFun({
      clientId: cache.options.peerId,
      members: options.members,
      convId: options.cid,
      action: 'kick'
    }, function (authResult) {
      if (authResult && authResult.signature) {
        cmd.n = authResult.nonce;
        cmd.t = authResult.timestamp;
        cmd.s = authResult.signature;
        engine.wsSend(cache, cmd);
      } else {
        throw new Error('Removing members from conversation denied by application: ' + authResult);
      }
    });
  } else {
    engine.wsSend(cache, cmd);
  }
};

engine.send = function (cache, options) {
  engine.wsSend(cache, {
    cmd: 'direct',
    cid: options.cid,
    msg: options.data,
    i: options.serialId,
    // r 是否需要回执需要则1,否则不传
    r: options.receipt || false,
    // transient 是否暂态消息(暂态消息不返回 ack,不保留离线消息,不触发离 线推送),否则不传
    transient: options.transient || false
  });
};

engine.convQuery = function (cache, options) {
  options = options || {};
  engine.wsSend(cache, {
    cmd: 'conv',
    op: 'query',
    // where 可选,对象,默认为包含自己的查询 {"m": peerId}
    where: options.where || {
      m: cache.options.peerId
      // conversation 的 id
      // objectId: options.cid
    },
    // sort 可选,字符串,默认为 -lm,最近对话反序
    sort: options.sort || '-lm',
    // limit 可选,数字,默认10
    limit: options.limit || 10,
    // skip 可选,数字,默认0
    skip: options.skip || 0,
    // i serial-id
    i: options.serialId
  });
};

// 查询 session 在线情况
engine.querySession = function (cache, options) {
  engine.wsSend(cache, {
    cmd: 'session',
    op: 'query',
    i: options.serialId,
    sessionPeerIds: options.peerIdList
  });
};

// 查询 conversation 的聊天记录
engine.convLog = function (cache, options) {
  engine.wsSend(cache, {
    cmd: 'logs',
    cid: options.cid,
    // t 时间戳,从 t 开始向前查询
    t: options.t || undefined,
    // mid 消息 id,从消息 id 开始向前查询(和 t 共同使用,为防止某毫秒时刻有重复消息)
    mid: options.mid || undefined,
    limit: options.limit || 20,
    // i serial-id
    i: options.serialId
  });
};

engine.convUpdate = function (cache, options) {
  engine.wsSend(cache, {
    cmd: 'conv',
    op: 'update',
    cid: options.cid,
    // attr 要修改的内容
    attr: options.data,
    i: options.serialId
  });
};

engine.convAck = function (cache, options) {
  engine.wsSend(cache, {
    cmd: 'ack',
    cid: options.cid,
    mid: options.mid
  });
};

engine.convCount = function (cache, options) {
  engine.wsSend(cache, {
    cmd: 'conv',
    op: 'count',
    i: options.serialId,
    cid: options.cid
  });
};

// 取出多媒体类型的格式(内置 HTML 转义逻辑)
engine.getMediaMsg = function (cache, msg) {

  // 检查是否是 JSON 格式的一个 String 类型
  if (!tool.isJSONString(msg)) {

    // 是否对消息中的 HTML 进行转义
    if (cache.options.encodeHTML) {
      msg = tool.encodeHTML(msg);
    }
    return msg;
  }

  msg = JSON.parse(msg);

  // 检查是否是多媒体类型
  if (!msg.hasOwnProperty('_lctype')) {
    return msg;
  }

  var obj = {
    text: msg._lctext,
    attr: msg._lcattrs
  };

  // 是否对消息中的 HTML 进行转义,对媒体格式仅对 text 转义
  if (cache.options.encodeHTML) {
    obj.text = tool.encodeHTML(msg._lctext);
  }

  if (msg._lcfile && msg._lcfile.url) {
    obj.url = msg._lcfile.url;
  }
  if (msg._lcfile && msg._lcfile.metaData) {
    obj.metaData = msg._lcfile.metaData;
  }

  // 多媒体类型
  switch (msg._lctype) {
    case -1:
      obj.type = 'text';
      break;
    case -2:
      obj.type = 'image';
      break;
    case -3:
      obj.type = 'audio';
      break;
    case -4:
      obj.type = 'video';
      break;
    case -5:
      obj.type = 'location';
      break;
    case -6:
      obj.type = 'file';
      break;
  }
  return obj;
};

// 生成多媒体特定格式的数据
engine.setMediaMsg = function (cache, type, data) {
  var obj;
  if (type !== 'text' && !data.metaData) {
    throw new Error('Media Data must have metaData attribute.');
  }
  switch (type) {
    case 'text':
      obj = {
        _lctype: -1,
        _lctext: data.text,
        // _lcattrs 是用来存储用户自定义的一些键值对
        _lcattrs: data.attr
      };
      break;
    case 'image':
      obj = {
        _lctype: -2,
        _lctext: data.text,
        // _lcattrs 是用来存储用户自定义的一些键值对
        _lcattrs: data.attr,
        _lcfile: {
          url: data.url,
          objId: data.objectId,
          metaData: {
            name: data.metaData.name,
            // 格式
            format: data.metaData.format,
            //单位:像素
            height: data.metaData.height,
            //单位:像素
            width: data.metaData.width,
            //单位:b
            size: data.metaData.size
          }
        }
      };
      break;
    case 'audio':
      obj = {
        _lctype: -3,
        _lctext: data.text,
        // _lcattrs 是用来存储用户自定义的一些键值对
        _lcattrs: data.attr,
        _lcfile: {
          url: data.url,
          objId: data.objectId,
          metaData: {
            name: data.metaData.name,
            // 媒体格式
            format: data.metaData.format,
            //单位:秒
            duration: data.metaData.duration,
            //单位:b
            size: data.metaData.size
          }
        }
      };
      break;
    case 'video':
      obj = {
        _lctype: -4,
        _lctext: data.text,
        // _lcattrs 是用来存储用户自定义的一些键值对
        _lcattrs: data.attr,
        _lcfile: {
          url: data.url,
          objId: data.objectId,
          metaData: {
            name: data.metaData.name,
            // 媒体格式
            format: data.metaData.format,
            // 单位:秒
            duration: data.metaData.duration,
            //单位:b
            size: data.metaData.size
          }
        }
      };
      break;
    case 'location':
      obj = {
        _lctype: -5,
        _lctext: data.text,
        // _lcattrs 是用来存储用户自定义的一些键值对
        _lcattrs: data.attr,
        _lcloc: {
          // 经度
          longitude: data.metaData.longitude,
          // 维度
          latitude: data.metaData.latitude
        }
      };
      break;
    case 'file':
      obj = {
        _lctype: -6,
        _lctext: data.text,
        // _lcattrs 是用来存储用户自定义的一些键值对
        _lcattrs: data.attr,
        _lcfile: {
          name: data.metaData.name,
          // 单位:b
          size: data.metaData.size
        }
      };
      break;
  }
  obj = JSON.stringify(obj);
  return obj;
};

// 取自增的 number 类型
engine.getSerialId = function (cache) {
  cache.serialId++;
  if (cache.serialId > 999999) {
    cache.serialId = 2015;
  }
  return cache.serialId;
};

// 绑定所有服务返回事件
engine.bindEvent = function (cache) {

  // RealtimeObject 已经初始化过,不再重复绑定事件
  if (cache.openFlag) {
    return;
  }

  cache.ec.on('session-opened', function (data) {
    // 标记重试状态为 false,表示没有在重试
    cache.reuseFlag = false;
    // 标记开启状态,已经开启
    cache.openFlag = true;
    // 派发全局 open 事件,表示 realtime 已经启动
    cache.ec.emit(eNameIndex.open, data);
  });

  // cache.ec.on('session-closed', function() {
  // session 被关闭,则关闭当前 websocket 连接
  // });

  // 查询 session 在线情况
  // cache.ec.on('session-query-result', function() {});

  cache.ec.on('session-error', function (data) {
    cache.ec.emit(eNameIndex.error, data);
  });

  // 服务器端确认收到 conversation 创建,并创建成功
  // 在创建时已经做绑定,所以注释掉
  // cache.ec.on('conv-started', function(data) {});

  // 服务器端发给客户端,表示当前用户加入了某个对话。包括创建对话、或加入对话
  cache.ec.on('conv-joined', function (data) {
    // 不是当前用户自己加入
    if (data.peerId !== data.initBy) {
      cache.ec.emit(eNameIndex.join, data);
    }
  });

  // 服务器端发给客户端,表示当前用户离开了某个对话,不再能收到对话的消息
  cache.ec.on('conv-left', function (data) {
    cache.ec.emit(eNameIndex.left, data);
  });

  // 服务器端发给客户端,表示当前对话有新人加入
  cache.ec.on('conv-members-joined', function (data) {
    cache.ec.emit(eNameIndex.join, data);
  });

  // 服务器端发给客户端,表示当前对话有新人离开
  cache.ec.on('conv-members-left', function (data) {
    cache.ec.emit(eNameIndex.left, data);
  });

  // 服务器端回复。表示 add 操作完成
  // 因为 added 之后也会触发 members-joined,所以注释掉
  // cache.ec.on('conv-added', function(data) {});

  // 服务器端确认删除成功
  // 因为 removed 之后也会触发 members-removed,所以注释掉
  // cache.ec.on('conv-removed', function() {});

  cache.ec.on('conv-error', function (data) {
    cache.ec.emit(eNameIndex.error, data);
    throw data.code + ':' + data.reason;
  });

  // 查询对话的结果
  // cache.ec.on('conv-results', function(data) {});

  // cache.ec.on('conv-updated', function(data) {});

  cache.ec.on('direct', function (data) {

    // 增加多媒体消息的数据格式化
    data.msg = engine.getMediaMsg(cache, data.msg);

    // 暂态消息无需回复
    if (!data.transient) {
      engine.convAck(cache, {
        cid: data.cid,
        mid: data.id
      });
    }

    cache.ec.emit(eNameIndex.message, data);
  });

  // 对要求回执的消息,服务器端会在对方客户端发送ack后发送回执
  cache.ec.on('rcp', function (data) {
    cache.ec.emit(eNameIndex.receipt, data);
  });

  // cache.ec.on('ack', function(data) {});

  // 用户可以获取自己所在对话的历史记录
  // cache.ec.on('logs', function(data) {});
};

if (typeof exports !== 'undefined') {
  // CommonJS 支持
  if (typeof module !== 'undefined' && module.exports) {
    exports = module.exports = realtime;
  }
  exports.realtime = realtime;
  /* jshint -W117 */
  /* ignore 'define' is not defined */
} else if (typeof define === 'function' && define.amd) {
    // AMD 支持
    define('AV/realtime', [], function () {
      return realtime;
    });
    /* jshint +W117 */
  }

}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./tool":6,"ws":7}],3:[function(require,module,exports){
(function (global){
'use strict';
module.exports = function (options, callback) {
  if (typeof options === 'string') {
    options = {
      url: options
    };
  }
  var url = options.url;
  var method = options.method || 'get';
  var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
  var xhr = new XMLHttpRequest();

  xhr.open(method, url);

  xhr.onload = function (data) {
    if (xhr.status >= 200 && xhr.status < 300 || global.XDomainRequest && !xhr.status) {
      callback(null, JSON.parse(xhr.responseText));
    } else {
      callback(JSON.parse(xhr.responseText));
    }
  };

  xhr.onerror = function (data) {
    callback(data || {});
    throw new Error('Network error.');
  };

  // IE9 中需要设置所有的 xhr 事件回调,不然可能会无法执行后续操作
  xhr.onprogress = function () {};
  xhr.ontimeout = function () {};
  xhr.timeout = 0;

  var body = JSON.stringify(options.data);

  xhr.send(body);
};

}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"xmlhttprequest":8}],4:[function(require,module,exports){
'use strict';

module.exports = function () {
  var eventList = {};
  var eventOnceList = {};

  var _on = function _on(eventName, fun, options) {
    if (!eventName) {
      throw new Error('No event name.');
    } else if (!fun) {
      throw new Error('No callback function.');
    }
    var list = eventName.split(/\s+/);
    var tempList;
    var isOnce;
    var isSingle;
    if (options) {
      isOnce = options.once;
      isSingle = options.single;
    }

    if (!isOnce) {
      tempList = eventList;
    } else {
      tempList = eventOnceList;
    }
    for (var i = 0, l = list.length; i < l; i++) {
      if (list[i]) {
        var itemEventList = tempList[list[i]];

        if (!itemEventList) {
          itemEventList = [];

          // 将新指针指向原链表
          tempList[list[i]] = itemEventList;
        }

        if (isSingle) {

          // 标记是否存在重复的方法,如果有则为 true
          var flag = false;
          for (var m = 0, n = itemEventList.length; m < n; m++) {
            if (itemEventList[m].toString() === fun.toString()) {
              flag = true;
              break;
            }
          }

          if (!flag) {
            itemEventList.push(fun);
          }
        } else {
          itemEventList.push(fun);
        }
      }
    }
  };

  var _off = function _off(eventName, fun, options) {
    var tempList;
    var isOnce;
    if (options) {
      isOnce = options.once;
    }
    if (!isOnce) {
      tempList = eventList;
    } else {
      tempList = eventOnceList;
    }
    if (tempList[eventName]) {
      var i = 0;
      var l = tempList[eventName].length;
      for (; i < l; i++) {
        if (tempList[eventName][i] === fun) {
          tempList[eventName][i] = null;
          // 每次只清除一个相同事件绑定
          break;
        }
      }
    }
  };

  function cleanNull(list) {
    var tempList = [];
    var i = 0;
    var l = list.length;
    if (l) {
      for (; i < l; i++) {
        if (list[i]) {
          tempList.push(list[i]);
        }
      }
      return tempList;
    } else {
      return null;
    }
  }

  return {
    on: function on(eventName, fun) {
      _on(eventName, fun);
      return this;
    },

    // 方法绑定以后只会运行一次
    once: function once(eventName, fun) {
      _on(eventName, fun, {
        once: true
      });
      return this;
    },

    // 同一个方法只会被绑定一次
    _one: function _one(eventName, fun) {
      _on(eventName, fun, {
        single: true
      });
    },
    emit: function emit(eventName, data) {
      if (!eventName) {
        throw new Error('No emit event name.');
      }
      var i = 0;
      var l = 0;
      if (eventList[eventName]) {
        i = 0;
        l = eventList[eventName].length;
        for (; i < l; i++) {
          if (eventList[eventName][i]) {
            eventList[eventName][i].call(this, data);
          }
        }
        eventList[eventName] = cleanNull(eventList[eventName]);
      }
      if (eventOnceList[eventName]) {
        i = 0;
        l = eventOnceList[eventName].length;
        for (; i < l; i++) {
          if (eventOnceList[eventName][i]) {
            eventOnceList[eventName][i].call(this, data);
            _off(eventName, eventOnceList[eventName][i], {
              once: true
            });
          }
        }
        eventOnceList[eventName] = cleanNull(eventOnceList[eventName]);
      }
      return this;
    },
    off: function off(eventName, fun) {
      _off(eventName, fun);
      return this;
    }
  };
};

},{}],5:[function(require,module,exports){
'use strict';

var hasOwn = Object.prototype.hasOwnProperty;
var toStr = Object.prototype.toString;

var isArray = function isArray(arr) {
  if (typeof Array.isArray === 'function') {
    return Array.isArray(arr);
  }

  return toStr.call(arr) === '[object Array]';
};

var isPlainObject = function isPlainObject(obj) {
  if (!obj || toStr.call(obj) !== '[object Object]') {
    return false;
  }

  var hasOwnConstructor = hasOwn.call(obj, 'constructor');
  var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
  // Not own constructor property must be Object
  if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {
    return false;
  }

  // Own properties are enumerated firstly, so to speed up,
  // if last one is own, then all properties are own.
  var key;
  for (key in obj) {/**/}

  return typeof key === 'undefined' || hasOwn.call(obj, key);
};

module.exports = function extend() {
  var options;
  var name;
  var src;
  var copy;
  var copyIsArray;
  var clone;
  var target = arguments[0];
  var i = 1;
  var length = arguments.length;
  var deep = false;

  // Handle a deep copy situation
  if (typeof target === 'boolean') {
    deep = target;
    target = arguments[1] || {};
    // skip the boolean and the target
    i = 2;
  } else if (typeof target !== 'object' && typeof target !== 'function' || target === null) {
    target = {};
  }

  for (; i < length; ++i) {
    options = arguments[i];
    // Only deal with non-null/undefined values
    if (options !== null) {
      // Extend the base object
      for (name in options) {
        src = target[name];
        copy = options[name];

        // Prevent never-ending loop
        if (target !== copy) {
          // Recurse if we're merging plain objects or arrays
          if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
            if (copyIsArray) {
              copyIsArray = false;
              clone = src && isArray(src) ? src : [];
            } else {
              clone = src && isPlainObject(src) ? src : {};
            }

            // Never move original objects, clone them
            target[name] = extend(deep, clone, copy);

            // Don't bring in undefined values
          } else if (typeof copy !== 'undefined') {
              target[name] = copy;
            }
        }
      }
    }
  }

  // Return the modified object
  return target;
};

},{}],6:[function(require,module,exports){
'use strict';

var tool = {};

tool.ajax = require('./ajax');
tool.extend = require('./extend');
// 小型的私有事件中心
tool.eventCenter = require('./eventcenter');

// 空函数
tool.noop = function () {};

// 检查是否是 JSON 格式的字符串
tool.isJSONString = function (obj) {
  return (/^\{.*\}$/.test(obj)
  );
};

// 获取当前时间的时间戳
tool.now = function () {
  return new Date().getTime();
};

// HTML 转义
tool.encodeHTML = function (source) {
  var encodeHTML = function encodeHTML(str) {
    if (typeof str === 'string') {
      return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
      // 考虑到其中有可能是 JSON,所以不做 HTML 强过滤,仅对标签过滤
      // .replace(/\\/g,'\')
      // .replace(/"/g,'"')
      // .replace(/'/g,''');
    } else {
        // 数字
        return str;
      }
  };

  // 对象类型
  if (typeof source === 'object') {
    for (var key in source) {
      source[key] = tool.encodeHTML(source[key]);
    }
    return source;
  } else {
    // 非对象类型
    return encodeHTML(source);
  }
};

module.exports = tool;

},{"./ajax":3,"./eventcenter":4,"./extend":5}],7:[function(require,module,exports){

/**
 * Module dependencies.
 */

var global = (function() { return this; })();

/**
 * WebSocket constructor.
 */

var WebSocket = global.WebSocket || global.MozWebSocket;

/**
 * Module exports.
 */

module.exports = WebSocket ? ws : null;

/**
 * WebSocket constructor.
 *
 * The third `opts` options object gets ignored in web browsers, since it's
 * non-standard, and throws a TypeError if passed to the constructor.
 * See: https://github.com/einaros/ws/issues/227
 *
 * @param {String} uri
 * @param {Array} protocols (optional)
 * @param {Object) opts (optional)
 * @api public
 */

function ws(uri, protocols, opts) {
  var instance;
  if (protocols) {
    instance = new WebSocket(uri, protocols);
  } else {
    instance = new WebSocket(uri);
  }
  return instance;
}

if (WebSocket) ws.prototype = WebSocket.prototype;

},{}],8:[function(require,module,exports){
exports.XMLHttpRequest = window.XMLHttpRequest || window.XDomainRequest;

},{}]},{},[1]);


text:
package com.opengamma.strata.product.dsf;

import static com.opengamma.strata.basics.currency.Currency.USD;
import static com.opengamma.strata.basics.date.BusinessDayConventions.MODIFIED_FOLLOWING;
import static com.opengamma.strata.basics.date.BusinessDayConventions.PRECEDING;
import static com.opengamma.strata.basics.date.DayCounts.THIRTY_U_360;
import static com.opengamma.strata.basics.date.HolidayCalendarIds.SAT_SUN;
import static com.opengamma.strata.basics.schedule.Frequency.P1M;
import static com.opengamma.strata.basics.schedule.Frequency.P3M;
import static com.opengamma.strata.basics.schedule.Frequency.P6M;
import static com.opengamma.strata.collect.TestHelper.assertSerialization;
import static com.opengamma.strata.collect.TestHelper.assertThrowsIllegalArg;
import static com.opengamma.strata.collect.TestHelper.coverBeanEquals;
import static com.opengamma.strata.collect.TestHelper.coverImmutableBean;
import static com.opengamma.strata.product.common.PayReceive.PAY;
import static com.opengamma.strata.product.common.PayReceive.RECEIVE;
import static org.testng.Assert.assertEquals;

import java.time.LocalDate;

import org.testng.annotations.Test;

import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.date.BusinessDayAdjustment;
import com.opengamma.strata.basics.date.DaysAdjustment;
import com.opengamma.strata.basics.date.Tenor;
import com.opengamma.strata.basics.index.IborIndex;
import com.opengamma.strata.basics.index.IborIndices;
import com.opengamma.strata.basics.schedule.PeriodicSchedule;
import com.opengamma.strata.basics.schedule.StubConvention;
import com.opengamma.strata.basics.value.ValueSchedule;
import com.opengamma.strata.product.common.BuySell;
import com.opengamma.strata.product.swap.FixedRateCalculation;
import com.opengamma.strata.product.swap.IborRateCalculation;
import com.opengamma.strata.product.swap.NotionalSchedule;
import com.opengamma.strata.product.swap.PaymentSchedule;
import com.opengamma.strata.product.swap.RateCalculationSwapLeg;
import com.opengamma.strata.product.swap.ResolvedSwap;
import com.opengamma.strata.product.swap.Swap;
import com.opengamma.strata.product.swap.SwapLeg;
import com.opengamma.strata.product.swap.type.FixedIborSwapConventions;

/**
 * Test {@link ResolvedDsf}.
 */
@Test
public class ResolvedDsfTest {

  private static final ReferenceData REF_DATA = ReferenceData.standard();
  private static final Dsf PRODUCT = DsfTest.sut();
  private static final IborIndex INDEX = IborIndices.USD_LIBOR_3M;
  private static final BusinessDayAdjustment BDA_MF = BusinessDayAdjustment.of(MODIFIED_FOLLOWING, SAT_SUN);
  private static final BusinessDayAdjustment BDA_P = BusinessDayAdjustment.of(PRECEDING, SAT_SUN);
  private static final LocalDate START_DATE = LocalDate.of(2014, 9, 12);
  private static final Swap SWAP = FixedIborSwapConventions.USD_FIXED_6M_LIBOR_3M
      .createTrade(START_DATE, Tenor.TENOR_10Y, BuySell.SELL, 1d, 0.015, REF_DATA).getProduct();
  private static final ResolvedSwap RSWAP = PRODUCT.getUnderlyingSwap().resolve(REF_DATA);
  private static final LocalDate LAST_TRADE_DATE = PRODUCT.getLastTradeDate();
  private static final LocalDate DELIVERY_DATE = PRODUCT.getDeliveryDate();
  private static final double NOTIONAL = PRODUCT.getNotional();

  //-------------------------------------------------------------------------
  public void test_builder() {
    ResolvedDsf test = sut();
    assertEquals(test.getDeliveryDate(), DELIVERY_DATE);
    assertEquals(test.getLastTradeDate(), LAST_TRADE_DATE);
    assertEquals(test.getNotional(), NOTIONAL);
    assertEquals(test.getCurrency(), USD);
    assertEquals(test.getUnderlyingSwap(), RSWAP);
  }

  public void test_builder_deliveryAfterStart() {
    assertThrowsIllegalArg(() -> ResolvedDsf.builder()
        .notional(NOTIONAL)
        .deliveryDate(LocalDate.of(2014, 9, 19))
        .lastTradeDate(LAST_TRADE_DATE)
        .underlyingSwap(RSWAP)
        .build());
  }

  public void test_builder_tradeAfterdelivery() {
    assertThrowsIllegalArg(() -> ResolvedDsf.builder()
        .notional(NOTIONAL)
        .deliveryDate(DELIVERY_DATE)
        .lastTradeDate(LocalDate.of(2014, 9, 11))
        .underlyingSwap(RSWAP)
        .build());
  }

  public void test_builder_notUnitNotional() {
    SwapLeg fixedLeg10 = RateCalculationSwapLeg.builder()
        .payReceive(RECEIVE)
        .accrualSchedule(PeriodicSchedule.builder()
            .startDate(LocalDate.of(2014, 9, 12))
            .endDate(LocalDate.of(2016, 9, 12))
            .frequency(P6M)
            .businessDayAdjustment(BDA_MF)
            .stubConvention(StubConvention.SHORT_INITIAL)
            .build())
        .paymentSchedule(PaymentSchedule.builder()
            .paymentFrequency(P6M)
            .paymentDateOffset(DaysAdjustment.NONE)
            .build())
        .notionalSchedule(NotionalSchedule.of(USD, 10d))
        .calculation(FixedRateCalculation.builder()
            .dayCount(THIRTY_U_360)
            .rate(ValueSchedule.of(0.015))
            .build())
        .build();
    SwapLeg iborLeg500 = RateCalculationSwapLeg.builder()
        .payReceive(PAY)
        .accrualSchedule(PeriodicSchedule.builder()
            .startDate(LocalDate.of(2014, 9, 12))
            .endDate(LocalDate.of(2016, 9, 12))
            .frequency(P1M)
            .businessDayAdjustment(BDA_MF)
            .stubConvention(StubConvention.SHORT_INITIAL)
            .build())
        .paymentSchedule(PaymentSchedule.builder()
            .paymentFrequency(P3M)
            .paymentDateOffset(DaysAdjustment.NONE)
            .build())
        .notionalSchedule(NotionalSchedule.builder()
            .currency(USD)
            .amount(ValueSchedule.of(500d))
            .finalExchange(true)
            .initialExchange(true)
            .build())
        .calculation(IborRateCalculation.builder()
            .index(INDEX)
            .fixingDateOffset(DaysAdjustment.ofBusinessDays(-2, SAT_SUN, BDA_P))
            .build())
        .build();
    Swap swap1 = Swap.of(fixedLeg10, SWAP.getLeg(PAY).get());
    Swap swap2 = Swap.of(SWAP.getLeg(RECEIVE).get(), iborLeg500);
    assertThrowsIllegalArg(() -> ResolvedDsf.builder()
        .securityId(PRODUCT.getSecurityId())
        .notional(NOTIONAL)
        .deliveryDate(DELIVERY_DATE)
        .lastTradeDate(LAST_TRADE_DATE)
        .underlyingSwap(swap1.resolve(REF_DATA))
        .build());
    assertThrowsIllegalArg(() -> ResolvedDsf.builder()
        .securityId(PRODUCT.getSecurityId())
        .notional(NOTIONAL)
        .deliveryDate(DELIVERY_DATE)
        .lastTradeDate(LAST_TRADE_DATE)
        .underlyingSwap(swap2.resolve(REF_DATA))
        .build());
  }

  //-------------------------------------------------------------------------
  public void coverage() {
    coverImmutableBean(sut());
    coverBeanEquals(sut(), sut2());
  }

  public void test_serialization() {
    assertSerialization(sut());
  }

  //-------------------------------------------------------------------------
  static ResolvedDsf sut() {
    return PRODUCT.resolve(REF_DATA);
  }

  static ResolvedDsf sut2() {
    return DsfTest.sut2().resolve(REF_DATA);
  }

}

redpajama_set_name: RedPajamaGithub


text:
> A Grunt plugin for jade4php

## Getting Started
This plugin requires Grunt `~0.4.5`

If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:

```shell
npm install grunt-jade4php --save-dev
```

Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:

```js
grunt.loadNpmTasks('grunt-jade4php');
```

## The "jade4php" task

### Overview
In your project's Gruntfile, add a section named `jade4php` to the data object passed into `grunt.initConfig()`.

```js
grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'),
  jade4php: {
    options: {
      // Task-specific options go here.
    },
    // Target-specific file lists and/or options go here.
  },
});
```

### Options

#### pretty

Type: `Boolean`
Default: `false`

Adds whitespace to the resulting html to make it easier for a human to read.

### Usage Examples

#### Compile all contents of a release directory and add a `.phtml` extension
```js
jad4php: {
  options: {
    pretty: true
  },
  expand: true,
  cwd: 'release/jade/',
  src: ['*.jade'],
  dest: 'release/phtml/',
  ext: '.phtml'
}
```

## Contributing
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).

## Release History
_(Nothing yet)_


text:
export default (Component) => {
  Component.implement({
    onToggleTime() {
      this.data.currentView = this.data.currentView === 'time' ? 'date' : 'time';
    },
    onPickSuccess() {
      this.resetView();
      this.$emit('pick-success');
    },
  });
};


text:
<!DOCTYPE html>

        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Speed Read - Speed Reading on Steroids</title>
        <meta name="description" content="Quickly read large chunks of text by seeing 1 word at a time, at whatever pace you choose.">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png">
        <link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png">
        <link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png">
        <link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png">
        <link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png">
        <link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png">
        <link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png">
        <link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png">
        <link rel="icon" type="image/png" href="/favicon-196x196.png" sizes="196x196">
        <link rel="icon" type="image/png" href="/favicon-160x160.png" sizes="160x160">
        <link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96">
        <link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16">
        <link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32">
        <meta name="msapplication-TileColor" content="#2d89ef">
        <meta name="msapplication-TileImage" content="/mstile-144x144.png">

        <link href='http://fonts.googleapis.com/css?family=Lora:400|Montserrat:400|Sanchez:400|Poly:400|PT+Serif|Roboto' rel='stylesheet' type='text/css'>
        <link rel="stylesheet" href="css/app.min.css">

              <textarea name="" id="speedReadTextInput" class="text-input" placeholder="Type or paste some text to speed read"></textarea>

                        Play

                        Stop

                        Font Settings

Lora

Montserrat

Sanchez

Poly

PT Serif

Roboto

                        Words Per Minute

                                Words per Minute
                                <input id="speedReadSpeedInput" class="app-controls-menu-input" type="number" min="60" max="3000" step="1" value="300" size="5" />

                                Number of Words
                                <input id="speedReadChunkSizeInput" class="app-controls-menu-input" type="number" min="1" step="1" value="3" size="5" />

                    0
                    words at
                    400
                    words per minute

                •

                    3 words
                    at a time


text:
<?php

namespace Alfatraining\CassandraSessionHandlerBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class AlfatrainingCassandraSessionHandlerBundle extends Bundle
{
}


text:
<?php

/**
 * @file
 * Definition of Drupal\Component\Gettext\PoReaderInterface.
 */

/**
 * Shared interface definition for all Gettext PO Readers.
 */
interface PoReaderInterface extends PoMetadataInterface {

  /**
   * Reads and returns a PoItem (source/translation pair).
   *
   * @return PoItem
   *   Wrapper for item data instance.
   */
  public function readItem();

}


text:
package jalf.predicate;

import jalf.AttrList;
import jalf.AttrName;
import jalf.Predicate;
import jalf.Renaming;
import jalf.Tuple;
import jalf.util.Pair;

import java.util.Set;
import java.util.function.BiFunction;

public abstract class ComparisonPredicate<T> extends Predicate {
    protected T left;
    protected T right;
    private BiFunction<T, T, Boolean> op;

    protected ComparisonPredicate(T left, T right,
                                  BiFunction<T, T, Boolean> op) {
        this.left = left;
        this.right = right;
        this.op = op;
    }

    @Override
    public boolean test(Tuple tuple) {
        T atLeft = getFor(left, tuple);
        T atRight = getFor(right, tuple);
        return op.apply(atLeft, atRight);
    }

    @Override
    public void fillReferencedAttributes(Set<AttrName> attrNames) {
        if (left instanceof AttrName)
          attrNames.add((AttrName)left);
        if (right instanceof AttrName)
          attrNames.add((AttrName)right);
    }

    @Override
    public Pair<Predicate> split(AttrList list) {
        AttrList references = getReferencedAttributes();
        AttrList invalidOnLeft = references.difference(list);
        if (invalidOnLeft.isEmpty()) {
            return new Pair<>(this, TRUE);
        } else {
            return new Pair<>(TRUE, this);
        }
    }

    // TODO Do we need to make this method protected?
    private T getFor(T what, Tuple tuple) {
        if (what instanceof AttrName) {
            // TODO How safe is it really?
            @SuppressWarnings("unchecked")
            T value = (T) tuple.get((AttrName) what);
            return value;
        } else {
            return what;
        }
    }

    protected T renameOperand(T op, Renaming r) {
        if (op instanceof AttrName)
            return (T) r.apply((AttrName) op);
        else
            return op;
    }

    @Override
    public int hashCode() {
        int hashCode = 31*getClass().hashCode();
        if (left != null)
            hashCode += left.hashCode();
        if (right != null)
            hashCode += right.hashCode();
        return hashCode;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!getClass().equals(obj.getClass()))
            return false;
        ComparisonPredicate<?> other = (ComparisonPredicate<?>) obj;
        return left.equals(other.left) && right.equals(other.right);
    }
}


text:
def wait_for_enabled(self, timeout=None):
    """
    DEPRECATED! Use wait_until_enabled command instead.
    """
    self.logger.warning('"wait_for_enabled" command is deprecated, use "wait_until_enabled" instead!')
    self.wait_until_enabled(timeout=timeout)
    return self


text:
'use strict';

const jaysonPromiseBrowserClient = require('jayson/promise/lib/client/browser');
const fetch = require('node-fetch');

const callServer = function(request) {
  const options = {
    method: 'POST',
    body: request,
    headers: {
      'Content-Type': 'application/json',
    }
  };
  return fetch('http://localhost:3000', options).then(res => res.text());
};

const client = new jaysonPromiseBrowserClient(callServer, {
  // other options go here
});

client.request('multiply', [5, 5]).then(function(result) {
  console.log(result);
}, function(err) {
  console.error(err);
});


text:
using System;
using UnityEngine;

namespace UnitySampleAssets.Utility
{
    public class SimpleActivatorMenu : MonoBehaviour
    {
        // An incredibly simple menu which, when given references
        // to gameobjects in the scene
        public GUIText camSwitchButton;
        public GameObject[] objects;

        private int m_CurrentActiveObject;

        private void OnEnable()
        {
            // active object starts from first in array
            m_CurrentActiveObject = 0;
            camSwitchButton.text = objects[m_CurrentActiveObject].name;
        }

        public void NextCamera()
        {
            int nextactiveobject = m_CurrentActiveObject + 1 >= objects.Length ? 0 : m_CurrentActiveObject + 1;

            for (int i = 0; i < objects.Length; i++)
            {
                objects[i].SetActive(i == nextactiveobject);
            }

            m_CurrentActiveObject = nextactiveobject;
            camSwitchButton.text = objects[m_CurrentActiveObject].name;
        }
    }
}


text:
.expandable-list {
  font-family:"Lucida Console", Monaco, monospace;
  font-size:10pt;
}

.expandable-list ul {
  padding: 0;
  list-style-type: none;
  margin: 5px 20px;
  display: none;
}

.expandable-list .expanded ~ ul {
  display: block;
}

.expandable-list li > span:first-child + ul {
  display: block;
}

.expandable-list  > ul {
  display: block;
}

.expandable-list .expanded:before {
  content: "";
}

.expandable-list a:before {
  content: "";
}

.expandable-list .value > a:before {
  content: "";
}

.expandable-list .array > a + span:after {
  content: "...";
}

.expandable-list .object > a + span:after {
  content: "...";
}

.expandable-list a.expanded + span:after {
  content: ""
}

.expandable-list a {
    cursor: pointer;
}

.expandable-list li {
  list-style-type: none;
  padding: 0;
  margin: 0;
  margin-top: 3px;
}


text:
      <form class="form-signin">

  <input placeholder="username" id="username" ng-model="user.username" class="form-control" required autofocus>
  <input placeholder="password" id="password" ng-model="user.password" type="password" class="form-control" required autofocus>
  <input placeholder="Enter Password Again" id="password2" ng-model="user.password2" type="password" class="form-control" required autofocus>
  <button class="btn btn-lg btn-primary btn-block" type="submit" ng-click="signup(user)">Sign Up</button>

Click Here to Login

      </form>


text:
package br.com.mobileti.mynotes.activitys;

import android.content.DialogInterface;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;

import br.com.mobileti.mynotes.R;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class SigninActivity extends AppCompatActivity {

    private FirebaseAuth.AuthStateListener mAuthListener;
    private String TAG = SignupActivity.class.getSimpleName();
    @BindView(R.id.createAccountButton) Button createAccountButton;
    @BindView(R.id.signinButton) Button signinButton;
    @BindView(R.id.emailEditText) EditText emailEditText;
    @BindView(R.id.passwordEditText) EditText passwordEditText;
    @BindView(R.id.resetPasswordButton) Button resetPasswordButton;

    private FirebaseAuth mAuth;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_signin);

        ButterKnife.bind(this);

        mAuth = FirebaseAuth.getInstance();

    }

    public boolean validadeForm(String email, String password) {
        if(email.isEmpty()) {
            emailEditText.setError(getString(R.string.email_empty_error));
            return false;
        }

        if(password.isEmpty()) {
            passwordEditText.setError(getString(R.string.password_empty_error));
            return false;
        }

        return true;
    }

    @OnClick(R.id.resetPasswordButton)
    public void resetPasswordClick(){
        final EditText resetEmailEditText = new EditText(this);
        new AlertDialog.Builder(this)
            .setTitle(R.string.reset_password_button)
            .setView(resetEmailEditText)
            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    final String email = resetEmailEditText.getText().toString();

                    mAuth.sendPasswordResetEmail(email)
                        .addOnCompleteListener(new OnCompleteListener<Void>() {
                            @Override
                            public void onComplete(@NonNull Task<Void> task) {
                                String msg = "";
                                if (task.isSuccessful()) {
                                    msg = getString(R.string.reset_password_email_success, email);
                                } else {
                                    msg = getString(R.string.reset_password_email_error);
                                }
                                Snackbar.make(resetPasswordButton, msg, Snackbar.LENGTH_LONG).show();
                            }
                        });
                }
            })
            .setNegativeButton(android.R.string.cancel, null)
            .create().show();
    }

    @OnClick(R.id.signinButton)
    public void signinButtonClick(){
        String email = emailEditText.getText().toString();
        String password = passwordEditText.getText().toString();

        if(validadeForm(email, password)) {
            mAuth.signInWithEmailAndPassword(email, password)
            .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    Log.d(TAG, "signInWithEmail:onComplete:" + task.isSuccessful());

                    // If sign in fails, display a message to the user. If sign in succeeds
                    // the auth state listener will be notified and logic to handle the
                    // signed in user can be handled in the listener.
                    if (!task.isSuccessful()) {
                        Log.w(TAG, "signInWithEmail:failed", task.getException());
                        Toast.makeText(getBaseContext(), R.string.signin_fail, Toast.LENGTH_LONG).show();
                    } else {
                        CheckLoginActivity.user = FirebaseAuth.getInstance().getCurrentUser();
                        Intent it = new Intent(getBaseContext(), MainActivity.class);
                        startActivity(it);
                        finish();
                    }
                }
            });
        }
    }

    @OnClick(R.id.createAccountButton)
    public void createAccoutButtonClick() {
        Intent it = new Intent(this, SignupActivity.class);
        startActivity(it);
        finish();
    }
}


text:
'use strict';

angular.module('hymns')
	.controller('PublishersController', ['$scope', '$modal', 'Authentication', 'AuthService', 'Publishers',
		function($scope, $modal, Authentication, AuthService, Publishers) {
			$scope.authentication = Authentication;

			$scope.show = {
				newSection: false
			};

			$scope.loadPublishers = function() {
				$scope.publishers = Publishers.query();
			};

			$scope.createPublisher = function() {
				validatePublisherForm($scope.newPublisherForm, $scope.newPublisher);
				if ($scope.newPublisherForm.$valid) {
					var publisherToSave = new Publishers({
						names: []
					});
					$scope.newPublisher.names.forEach(function(elem) {
						if (elem.name !== undefined) publisherToSave.names.push(elem);
					});
					publisherToSave.$save(function(response) {
						resetNewPublisherList();
						$scope.loadPublishers();
						$scope.show.newSection = false;
					}, function(response) {
						//  Add error message
					});
				}
			};

			$scope.delete = function(publisher) {
				var modalInstance = $modal.open({
					templateUrl: 'modules/hymns/views/modal-delete-item.client.view.html',
					controller: 'DeleteItemController',
					windowClass: 'delete-item-modal',
					resolve: {
						itemToDelete: function () {
							return publisher;
						},
						deleteMsg: function() {
							return '';
						}
					}
				});

				modalInstance.result.then(function (publisher) {
					publisher.$delete(function(response) {
						$scope.loadPublishers();
					});
				});
			};

			$scope.edit = function(publisher) {
				var modalInstance = $modal.open({
					templateUrl: 'modules/hymns/views/modal-edit-publisher.client.view.html',
					controller: 'EditItemController',
					windowClass: 'edit-item-modal',
					resolve: {
						itemToEdit: function () {
							return publisher;
						},
						selectLists: function() {
							return {};
						},
						checkboxItems: function() {
							return {};
						},
						validateFn: function() {
							return validatePublisherForm;
						}
					}
				});

				modalInstance.result.then(function (publisher) {
					publisher.$update(function(response) {
						$scope.loadPublishers();
					});
				});
			};

			$scope.cancelNew = function() {
				$scope.show.newSection = false;
				resetNewPublisherList();
			};

			$scope.hasWriteAuthorization = function(item) {
				return AuthService.hasWriteAuthorization($scope.authentication, item);
			};

			function resetNewPublisherList() {
				$scope.newPublisher = {
					names: [
						{ lang: 'zh', name: '' },
						{ lang: 'en', name: '' }
					]
				};
			}

			function validatePublisherForm(form, obj) {
				if (!obj.names[0].name && !obj.names[1].name) {
					form.name.$setValidity('oneRequired', false);
				} else {
					form.name.$setValidity('oneRequired', true);
				}
			}

			resetNewPublisherList();
		}])

	.controller('HymnbooksController', ['$scope', '$state', '$stateParams', '$timeout', '$modal', 'Authentication', 'AuthService', 'Publishers', 'Hymnbooks', 'HymnConfig',
		function($scope, $state, $stateParams, $timeout, $modal, Authentication, AuthService, Publishers, Hymnbooks, HymnConfig){
			$scope.currentState = $state.current;
			$scope.authentication = Authentication;

			$scope.show = {
				newSection: false,
				filterSection: false
			};

			$scope.search = {
				langs: []
			};

			$scope.parentInfo = {
				parent: 'publishers',
				child: 'hymnbooks',
				title: 'Publisher'
			};

			$scope.lyricLangs = HymnConfig.getConfig().lyricLangs;

			$scope.loadHymnbooks = function() {
				if ($scope.currentState.name === 'listHymnbooksInPublisher') {
					$scope.parentInfo.obj = Publishers.get({
						publisherId: $stateParams.publisherId
					});
					$scope.hymnbooks = Hymnbooks.query({
						publisherId: $stateParams.publisherId
					});
				} else {
					$scope.hymnbooks = Hymnbooks.getAll();
					$scope.unknownHymnbook = Hymnbooks.getUnknownHymnbook();
					$scope.publishersList = Publishers.query();
				}
			};

			$scope.createHymnbook = function() {
				validateHymnbookForm($scope.newHymnbookForm, $scope.newHymnbook);
				if ($scope.newHymnbookForm.$valid) {
					var hymnbookToSave;
					if ($stateParams.publisherId || $scope.newHymnbook.publisherId) {
						hymnbookToSave = new Hymnbooks({
							publisherId: $stateParams.publisherId || $scope.newHymnbook.publisherId,
							names: [],
							lyricLangs: []
						});
					} else {
						hymnbookToSave = new Hymnbooks({
							names: [],
							lyricLangs: []
						});
					}
					$scope.newHymnbook.names.forEach(function(elem) {
						if (elem.name !== undefined) hymnbookToSave.names.push(elem);
					});
					if ($scope.newHymnbook.year) {
						hymnbookToSave.year = $scope.newHymnbook.year;
					}
					hymnbookToSave.lyricLangs = angular.copy($scope.newHymnbook.lyricLangs);
					if ($stateParams.publisherId || $scope.newHymnbook.publisherId) {
						hymnbookToSave.$save(function(response) {
							resetNewHymnbookObj();
							$scope.loadHymnbooks();
							$scope.show.newSection = false;
						}, function(response) {
							// add error message
						});
					} else {
						hymnbookToSave.$saveWithNoPublisher(function(response) {
							resetNewHymnbookObj();
							$scope.loadHymnbooks();
							$scope.show.newSection = false;
						}, function(response) {
							// add error message
						});
					}
				}
			};

			$scope.delete = function(hymnbook) {
				var modalInstance = $modal.open({
					templateUrl: 'modules/hymns/views/modal-delete-item.client.view.html',
					controller: 'DeleteItemController',
					windowClass: 'delete-item-modal',
					resolve: {
						itemToDelete: function () {
							return hymnbook;
						},
						deleteMsg: function() {
							return '';
						}
					}
				});

				modalInstance.result.then(function (hymnbook) {
					hymnbook.$delete(function(response) {
						$scope.loadHymnbooks();
					});
				});
			};

			$scope.edit = function(hymnbook) {
				if (!$scope.publishersList) {
					$scope.publishersList = Publishers.query();
				}
				var modalInstance = $modal.open({
					templateUrl: 'modules/hymns/views/modal-edit-hymnbook.client.view.html',
					controller: 'EditItemController',
					windowClass: 'edit-item-modal',
					resolve: {
						itemToEdit: function () {
							return hymnbook;
						},
						selectLists: function() {
							return {
								publishers: $scope.publishersList
							};
						},
						checkboxItems: function() {
							return {
								lyricLangs: $scope.lyricLangs
							};
						},
						validateFn: function() {
							return validateHymnbookForm;
						}
					}
				});

				modalInstance.result.then(function (hymnbook) {
					hymnbook.$update(function(response) {
						$scope.loadHymnbooks();
					});
				});
			};

			$scope.cancelNew = function() {
				$scope.show.newSection = false;
				resetNewHymnbookObj();
			};

			$scope.hasWriteAuthorization = function(item) {
				return AuthService.hasWriteAuthorization($scope.authentication, item);
			};

			function resetNewHymnbookObj() {
				$scope.newHymnbook = {
					names: [
						{ lang: 'zh', name: '' },
						{ lang: 'en', name: '' }
					],
					lyricLangs: []
				};
			}

			function validateHymnbookForm(form, obj) {
				if (obj.names && !obj.names[0].name && !obj.names[1].name) {
					form.name.$setValidity('oneRequired', false);
				} else {
					form.name.$setValidity('oneRequired', true);
				}
			}

			resetNewHymnbookObj();
		}])

	.controller('HymnsController', ['$scope', '$state', '$stateParams', '$timeout', '$modal', 'Authentication', 'AuthService', 'Publishers', 'Hymnbooks', 'Hymns', 'HymnConfig',
		function($scope, $state, $stateParams, $timeout, $modal, Authentication, AuthService, Publishers, Hymnbooks, Hymns, HymnConfig) {
			$scope.currentState = $state.current;
			$scope.authentication = Authentication;

			$scope.show = {
				newSection: false
			};

			$scope.search = {
				langs: []
			};

			$scope.parentInfo = {
				parent: 'hymnbooks',
				child: 'hymns',
				title: 'Hymn Book'
			};

			$scope.lyricLangs = HymnConfig.getConfig().lyricLangs;

			$scope.loadHymns = function() {
				$scope.hymnbooksList = Hymnbooks.getAll();
				$scope.publishersList = Publishers.query();
				if ($scope.currentState.name === 'listHymnsInHymnbook') {
					$scope.parentInfo.obj = Hymnbooks.getOne({
						hymnbookId: $stateParams.hymnbookId
					});
					$scope.hymns = Hymns.query({
						hymnbookId: $stateParams.hymnbookId
					}, function() {
						$scope.hymns.forEach(function(e) {
							e.hymnbookIndexInt = parseInt(e.hymnbookIndex) || 999;
						});
					});
				}
			};

			$scope.createHymn = function() {
				validateHymnForm($scope.newHymnForm, $scope.newHymn);
				if ($scope.newHymnForm.$valid) {
					var hymnToSave = new Hymns({
						names: [],
						lyricLangs: []
					});
					if ($scope.newHymn.hymnbookId) {
						hymnToSave.hymnbookId = $scope.newHymn.hymnbookId;
					} else {
						hymnToSave.hymnbookId = 'unknown';
					}
					$scope.newHymn.names.forEach(function(elem) {
						if (elem.name !== undefined) hymnToSave.names.push(elem);
					});
					hymnToSave.lyricLangs = angular.copy($scope.newHymn.lyricLangs);
					hymnToSave.hymnbookIndex = $scope.newHymn.hymnbookIndex;
					hymnToSave.$save(function(response) {
						resetNewHymnObj();
						$scope.loadHymns();
						$scope.show.newSection = false;
					}, function(response) {
						// add error message
					});

				}
			};

			$scope.delete = function(hymn) {
				var modalInstance = $modal.open({
					templateUrl: 'modules/hymns/views/modal-delete-item.client.view.html',
					controller: 'DeleteItemController',
					windowClass: 'delete-item-modal',
					resolve: {
						itemToDelete: function () {
							return hymn;
						},
						deleteMsg: function() {
							return '';
						}
					}
				});

				modalInstance.result.then(function (hymn) {
					hymn.$delete(function(response) {
						$scope.loadHymns();
					});
				});
			};

			$scope.edit = function(hymn) {
				if (!$scope.publishersList) {
					$scope.publishersList = Publishers.query();
				}
				if (!$scope.hymnbooksList) {
					$scope.hymnbooksList = Hymnbooks.getAll();
				}
				var modalInstance = $modal.open({
					templateUrl: 'modules/hymns/views/modal-edit-hymn.client.view.html',
					controller: 'EditItemController',
					windowClass: 'edit-item-modal',
					resolve: {
						itemToEdit: function () {
							return hymn;
						},
						selectLists: function() {
							return {
								hymnbooks: $scope.hymnbooksList
							};
						},
						checkboxItems: function() {
							return {
								lyricLangs: $scope.lyricLangs
							};
						},
						validateFn: function() {
							return validateHymnForm;
						}
					}
				});

				modalInstance.result.then(function (hymn) {
					hymn.$update(function(response) {
						$scope.loadHymns();
					});
				});
			};

			$scope.cancelNew = function() {
				$scope.show.newSection = false;
				resetNewHymnObj();
			};

			$scope.hasWriteAuthorization = function(item) {
				return AuthService.hasWriteAuthorization($scope.authentication, item);
			};

			function resetNewHymnObj() {
				$scope.newHymn = {
					names: [
						{ lang: 'zh', name: '' },
						{ lang: 'en', name: '' }
					],
					lyricLangs: []
				};
				if ($stateParams.hymnbookId && $stateParams.hymnbookId !== 'unknown') {
					$scope.newHymn.hymnbookId = $stateParams.hymnbookId;
					$scope.newHymn.publisherId = $scope.parentInfo.obj.publisher._id;
					$scope.newHymn.lyricLangs = angular.copy($scope.parentInfo.obj.lyricLangs);
				}
			}

			function validateHymnForm(form, obj) {
				if (!obj.names[0].name && !obj.names[1].name) {
					form.name.$setValidity('oneRequired', false);
				} else {
					form.name.$setValidity('oneRequired', true);
				}
			}

			$scope.$watchCollection('[hymnbooksList.$resolved, publishersList.$resolved, parentInfo.obj.$resolved]',
				function(newValues) {
					if (_.all(newValues, _.identity)) {
						$timeout(function() {
							resetNewHymnObj();
						}, true);
					}
				});
		}])

	.controller('HymnController', ['$scope', '$state', '$stateParams', '$modal', 'Authentication', 'AuthService', 'Publishers', 'Hymnbooks', 'Hymns', 'HymnConfig', 'HymnClipboard',
		function($scope, $state, $stateParams, $modal, Authentication, AuthService, Publishers, Hymnbooks, Hymns, HymnConfig, HymnClipboard) {
			var selectedTab = 0;
			$scope.currentState = $state.current;
			$scope.authentication = Authentication;

			$scope.show = {
				addLyrics: false
			};

			$scope.newLyrics = {
				lyricLangs: []
			};

			$scope.newVerse = [];

			$scope.langsAvailable = [];

			$scope.lyricsVerseViewModel = [];

			$scope.addLyricsSet = function() {
				if ($scope.hymn.$resolved && $scope.newLyrics.lyricLangs.length > 0) {
					$scope.hymn.lyricsList.push({
						langs: angular.copy($scope.newLyrics.lyricLangs),
						lyrics: [],
						slideIndexes: []
					});
					$scope.hymn.$update(function(response) {
						$scope.loadHymn();
						resetNewLyrics();
					});
				}
			};

			$scope.parentInfo = {
				parent: 'hymnbooks',
				child: 'hymns',
				title: 'Hymn Book'
			};

			$scope.lyricLangs = HymnConfig.getConfig().lyricLangs;

			$scope.loadHymn = function() {
				$scope.hymn = Hymns.getOne({
					hymnId: $stateParams.hymnId
				});
				$scope.allLabels = Hymns.allLabels();
			};

			$scope.addLabel = function(label) {
				if ($scope.hymn.labels.indexOf(label) === -1) {
					$scope.hymn.labels.push(label);
					$scope.hymn.$update(function(response) {
						// TO-DO success message
					});
				}
				$scope.newLabel = '';
			};

			$scope.removeLabel = function(label) {
				var labelIndex = $scope.hymn.labels.indexOf(label);
				if (labelIndex > -1) {
					$scope.hymn.labels.splice(labelIndex, 1);
					$scope.hymn.$update(function(response) {
						$scope.newLabel = undefined;
						$scope.loadHymn();
					});
				}
			};

			$scope.addLyricVerse = function(index) {
				if ($scope.newVerse[index]) {
					$scope.hymn.lyricsList[index].lyrics.push({
						verse: $scope.newVerse[index].verse,
						lines: convertLinesToModel($scope.newVerse[index].lines)
					});
					$scope.hymn.lyricsList[index].lyrics = _.sortBy($scope.hymn.lyricsList[index].lyrics, 'verse');
					$scope.hymn.$update(function(response) {
						if ($scope.lyricsVerseViewModel[index]) {
							$scope.lyricsVerseViewModel[index].push({
								verse: $scope.newVerse[index].verse,
								lines: $scope.newVerse[index].lines
							});
							$scope.lyricsVerseViewModel[index] = _.sortBy($scope.lyricsVerseViewModel[index], 'verse');
						}
						resetNewVerse(index);
					});

				}
			};

			$scope.cancelAddVerse = function(index) {
				resetNewVerse(index);
			};

			$scope.updateLyricVerse = function(lyricSetIndex, verseIndex) {
				var lyricSet = $scope.hymn.lyricsList[lyricSetIndex];
				var verseViewModel = $scope.lyricsVerseViewModel[lyricSetIndex][verseIndex];
				if (lyricSet.lyrics && lyricSet.lyrics.length > verseIndex) {
					lyricSet.lyrics[verseIndex].verse = verseViewModel.verse;
					lyricSet.lyrics[verseIndex].lines = convertLinesToModel(verseViewModel.lines);
					$scope.hymn.$update(function(response) {
						verseViewModel.editMode = false;
					});
				}
			};

			$scope.cancelEditVerse = function(lyricSet, verseViewModel, index) {
				if (lyricSet.lyrics && lyricSet.lyrics.length > index) {
					verseViewModel.verse = lyricSet.lyrics[index].verse;
					verseViewModel.lines = convertLinesToView(lyricSet.lyrics[index].lines);
					verseViewModel.editMode = false;
				}
			};

			$scope.removeVerse = function(lyricSetIndex, verseIndex) {
				var modalInstance = $modal.open({
					templateUrl: 'modules/hymns/views/modal-delete-item.client.view.html',
					controller: 'DeleteItemController',
					windowClass: 'delete-item-modal',
					resolve: {
						itemToDelete: function () {
							return {
								lyricSetIndex: lyricSetIndex,
								verseIndex: verseIndex
							};
						},
						deleteMsg: function() {
							return 'Confirm delete this verse?';
						}
					}
				});

				modalInstance.result.then(function (lyricsIndexObj) {
					if ($scope.hymn.lyricsList[lyricsIndexObj.lyricSetIndex] &&
						$scope.hymn.lyricsList[lyricsIndexObj.lyricSetIndex].lyrics.length > lyricsIndexObj.verseIndex &&
						$scope.lyricsVerseViewModel[lyricsIndexObj.lyricSetIndex] &&
						$scope.lyricsVerseViewModel[lyricsIndexObj.lyricSetIndex].length > lyricsIndexObj.verseIndex) {
						$scope.hymn.lyricsList[lyricsIndexObj.lyricSetIndex].lyrics.splice(lyricsIndexObj.verseIndex, 1);
						$scope.hymn.$update(function(response) {
							$scope.lyricsVerseViewModel[lyricsIndexObj.lyricSetIndex].splice(lyricsIndexObj.verseIndex, 1);
						});
					}
				});
			};

			$scope.setHymnClipboard = function(lyricSetIndex, verseIndex) {
				if ($scope.lyricsVerseViewModel[lyricSetIndex] &&
					$scope.lyricsVerseViewModel[lyricSetIndex].length > verseIndex) {
					HymnClipboard.set($scope.lyricsVerseViewModel[lyricSetIndex][verseIndex].lines);
				}
			};

			$scope.onStartDrag = function() {
				$scope.startDrag = true;
			};

			$scope.onStopDrag = function() {
				$scope.startDrag = false;
			};

			$scope.hasAuthorization = function(item) {
				return $scope.authentication.user && $scope.authentication.user._id === item.user._id;
			};

			$scope.hasWriteAuthorization = function(item) {
				return AuthService.hasWriteAuthorization($scope.authentication, item);
			};

			$scope.updateSelectedTab = function() {
				$scope.verseIndexSelected = -1;
				$scope.hymn.lyricsList.forEach(function(elem, index) {
					if (elem.active) {
						selectedTab = index;
						return;
					}
				});
			};

			$scope.saveHymn = function() {
				$scope.hymn.$update(function(response) {
					// success message
				});
			};

			$scope.selectVerseFromArrangement = function(indexFromArrangement) {
				if ($scope.verseIndexSelected === indexFromArrangement) {
					$scope.verseIndexSelected = -1;
				} else {
					$scope.verseIndexSelected = indexFromArrangement;
				}
			};

			$scope.removeVerseFromArrangement = function() {
				if ($scope.hymn.defaultArrangement.length > $scope.verseIndexSelected) {
					$scope.hymn.defaultArrangement.splice($scope.verseIndexSelected, 1);
				}
				$scope.saveHymn();
			};

			$scope.addVerseToArrangement = function(verse) {
				$scope.hymn.defaultArrangement.push(verse);
				$scope.saveHymn();
			};

			$scope.defaultArrangementSortableOptions = {
				update: function(e, ui) {
					$scope.saveHymn();
				}
			};

			function setLangsAvailable() {
				var lyricsListLangs = [];
				$scope.hymn.lyricsList.forEach(function(elem) {
					lyricsListLangs.push(elem.langs);
				});
				$scope.langsAvailable =  _.difference($scope.hymn.lyricLangs, _.flatten(lyricsListLangs)).sort();
			}

			function resetNewLyrics() {
				$scope.newLyrics = {
					lyricLangs: []
				};
			}

			function resetNewVerse(index) {
				$scope.newVerse[index] = { lines: [] };
			}

			function convertLinesToView(linesArray) {
				if (!_.isArray(linesArray)) return linesArray;

				return linesArray.join('\n');
			}

			function convertLinesToModel(linesString) {
				return linesString.split('\n');
			}

			function convertVerseToViewModel(verseObj) {
				return {
					verse: verseObj.verse,
					lines: convertLinesToView(verseObj.lines),
					editMode: false
				};
			}

			$scope.$watch('hymn.$resolved', function() {
				if ($scope.hymn.$resolved) {
					$scope.parentInfo.obj = $scope.hymn.hymnbook;
					$scope.hymn.lyricsList.forEach(function(elem, index) {
						var lyricsViewModel = [];
						if (elem.lyrics && elem.lyrics.length > 0) {
							elem.lyrics.forEach(function(elem, index) {
								lyricsViewModel.push(convertVerseToViewModel(elem));
							});
						}
						$scope.lyricsVerseViewModel.push(lyricsViewModel);
						$scope.newVerse.push({ lines: [] });
					});
					setLangsAvailable();
				}
			});

			$scope.$watch('hymn', function() {
				if (selectedTab > 0) {
					if ($scope.hymn.lyricsList && $scope.hymn.lyricsList.length > selectedTab) {
						$scope.hymn.lyricsList[selectedTab].active = true;
					}
				}
			}, true);

			resetNewLyrics();
		}])

	.controller('DeleteItemController', ['$scope', '$modalInstance', 'itemToDelete', 'deleteMsg',
		function($scope, $modalInstance, itemToDelete, deleteMsg) {
			$scope.itemToDelete = itemToDelete;
			$scope.deleteMsg = deleteMsg;

			$scope.confirm = function() {
				$modalInstance.close($scope.itemToDelete);
			};

			$scope.cancel = function () {
				$modalInstance.dismiss('cancel');
			};
		}])

	.controller('EditItemController', ['$scope', '$modalInstance', 'itemToEdit', 'selectLists', 'checkboxItems', 'validateFn',
		function($scope, $modalInstance, itemToEdit, selectLists, checkboxItems, validateFn) {
			$scope.itemToEdit = itemToEdit;
			$scope.selectLists = selectLists;
			$scope.checkboxItems = checkboxItems;

			$scope.save = function(editForm) {
				validateFn(editForm, $scope.itemToEdit);
				if (editForm.$valid) {
					$modalInstance.close($scope.itemToEdit);
				}
			};

			$scope.cancel = function () {
				$modalInstance.dismiss('cancel');
			};
		}]);


text:
/*************************************************************************/
/*                                                                       */
/*                  Language Technologies Institute                      */
/*                     Carnegie Mellon University                        */
/*                         Copyright (c) 2007                            */
/*                        All Rights Reserved.                           */
/*                                                                       */
/*  Permission is hereby granted, free of charge, to use and distribute  */
/*  this software and its documentation without restriction, including   */
/*  without limitation the rights to use, copy, modify, merge, publish,  */
/*  distribute, sublicense, and/or sell copies of this work, and to      */
/*  permit persons to whom this work is furnished to do so, subject to   */
/*  the following conditions:                                            */
/*   1. The code must retain the above copyright notice, this list of    */
/*      conditions and the following disclaimer.                         */
/*   2. Any modifications must be clearly marked as such.                */
/*   3. Original authors' names are not deleted.                         */
/*   4. The authors' names are not used to endorse or promote products   */
/*      derived from this software without specific prior written        */
/*      permission.                                                      */
/*                                                                       */
/*  CARNEGIE MELLON UNIVERSITY AND THE CONTRIBUTORS TO THIS WORK         */
/*  DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING      */
/*  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT   */
/*  SHALL CARNEGIE MELLON UNIVERSITY NOR THE CONTRIBUTORS BE LIABLE      */
/*  FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES    */
/*  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN   */
/*  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,          */
/*  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF       */
/*  THIS SOFTWARE.                                                       */
/*                                                                       */
/*************************************************************************/
/*             Author:  Alan W Black (awb@cs.cmu.edu)                    */
/*               Date:  November 2007                                    */
/*************************************************************************/
/*  Language independant feature functions                               */
/*************************************************************************/

#ifndef _CST_FFEATURES_H
#define _CST_FFEATURES_H

#include "cst_val.h"
#include "cst_item.h"

const cst_val *ph_vc(const cst_item *p);
const cst_val *ph_vlng(const cst_item *p);
const cst_val *ph_vheight(const cst_item *p);
const cst_val *ph_vrnd(const cst_item *p);
const cst_val *ph_vfront(const cst_item *p);
const cst_val *ph_ctype(const cst_item *p);
const cst_val *ph_cplace(const cst_item *p);
const cst_val *ph_cvox(const cst_item *p);

const cst_val *cg_duration(const cst_item *p);
const cst_val *cg_state_pos(const cst_item *p);
const cst_val *cg_state_place(const cst_item *p);
const cst_val *cg_state_index(const cst_item *p);
const cst_val *cg_state_rindex(const cst_item *p);
const cst_val *cg_phone_place(const cst_item *p);
const cst_val *cg_phone_index(const cst_item *p);
const cst_val *cg_phone_rindex(const cst_item *p);

void basic_ff_register(cst_features *ffunctions);

#endif /* _CST_FFEATURES_H */