package com.hg.xdoc;

import java.io.BufferedReader;

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.FileReader;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.io.UnsupportedEncodingException;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

import java.lang.reflect.Array;

import java.lang.reflect.Field;

import java.lang.reflect.Method;

import java.lang.reflect.Modifier;

import java.net.HttpURLConnection;

import java.net.URL;

import java.net.URLConnection;

import java.net.URLDecoder;

import java.net.URLEncoder;

import java.text.SimpleDateFormat;

import java.util.ArrayList;

import java.util.Collection;

import java.util.Date;

import java.util.HashMap;

import java.util.HashSet;

import java.util.Iterator;

import java.util.List;

import java.util.Map;

import java.util.Set;

import javax.xml.parsers.DocumentBuilder;

import javax.xml.parsers.DocumentBuilderFactory;

import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;

import org.w3c.dom.Element;

import org.w3c.dom.NamedNodeMap;

import org.w3c.dom.NodeList;

import org.xml.sax.SAXException;

/**

 * XDoc服务

 * @author xdoc

 * @version 12.2.5

 */

public class XDocService {

  /**

   * 默认服务器地址

   */

  public static String DEFAULT_URL = "http://www.xdocin.com";

  /**

   * 默认账号口令

   */

  public static String DEFAULT_KEY = "";

  /**

   * XDOC服务地址

   */

  private String url;

  /**

   * XDOC服务口令

   */

  private String key;

  /**

   * 服务地址

   * @return

   */

  public String getUrl() {

    return url;

  }

  /**

   * 服务地址

   * @param url

   */

  public void setUrl(String url) {

    this.url = url;

  }

  /**

   * 账号口令

   * @return

   */

  public String getKey() {

    return key;

  }

  /**

   * 账号口令

   * @param key

   */

  public void setKey(String key) {

    this.key = key;

  }

  /**

   * 构造器

   */

  public XDocService() {

    this(DEFAULT_URL, DEFAULT_KEY); 

  }

  /**

   * 构造器

   * @param url 服务地址

   */

  public XDocService(String url) {

    this(url, DEFAULT_KEY);

  }

  /**

   * 构造器

   * @param url 服务地址

   * @param key 账号

   */

  public XDocService(String url, String key) {

    this.url = url;

    this.key = key;

  }

  /**

   * 转换为其它格式文件

   * @param xdoc xdoc

   * @param file 其它格式文件,如:a.pdf

   * @throws IOException

   */

  public void to(File xdoc, File file) throws IOException {

    to(xdoc.getAbsolutePath(), file);

  }

  /**

   * 转换为其它格式文件

   * @param xdoc xdoc文本<br>

   *        URL:文档URL地址,格式支持:xdoc、json、docx、epub、txt、rtf等,支持datauri协议,可传递二进制数据,支持本地文件<br>

   *        纯文本:以"text:"开头的文本<br>

   *        JSON:符合XDOC-JSON规范的JSON文本<br>

   *        XML:符合XDOC-XML规范的XML文本<br>

   *        HTML:用html标签括起来的html文本,如:&lt;html&gt;&lt;h1&gt;Hello&lt;/h1&gt;&lt;/html&gt;

   * @param file 其它格式文件,如:a.pdf

   * @throws IOException

   */

  public void to(String xdoc, File file) throws IOException {

    to(xdoc, new FileOutputStream(file), getFormat(file.getName()));

  }

  /**

   * 转换为其它格式,保存到指定流中

   * @param xdoc xdoc

   * @param out 输出目标,OutputStream或HttpServletResponse

   * @param format format

   * @throws IOException

   */

  public void to(String xdoc, Object out, String format) throws IOException {

    Map<String, Object> param = new HashMap<String, Object>();

    param.put("_func", "to");

    param.put("_xdoc", xdoc);

    param.put("_format", format);

    invoke(checkParam(param), out);

  }

  /**

   * 转换为其它格式并发送

   * @param xdoc xdoc

   * @param to 目标,支持ftp、http、mail、datauri等

   * @param format format

   * @throws IOException

   */

  public String to(String xdoc, String to, String format) throws IOException {

    Map<String, Object> param = new HashMap<String, Object>();

    param.put("_func", "to");

    param.put("_xdoc", xdoc);

    param.put("_to", to);

    param.put("_format", format);

    ByteArrayOutputStream out = new ByteArrayOutputStream(); 

    invoke(checkParam(param), out);

    return new String(out.toByteArray(), "UTF-8");

  }

  /**

   * 运行xdoc

   * @param xdoc xdoc

   * @param param 参数

   * @param file 目标文件

   * @throws IOException

   */

  public void run(File xdoc, Map<String, Object> param, File file) throws IOException {

    if (!param.containsKey("_xformat")) {

      param.put("_xformat", getFormat(file.getName()));

    }

    run(xdoc.getAbsolutePath(), param, file);

  }

  /**

   * 运行xdoc

   * @param xdoc xdoc

   * @param param 参数

   * @param file 目标文件

   * @throws IOException

   */

  public void run(String xdoc, Map<String, Object> param, File file) throws IOException {

    run(xdoc, param, new FileOutputStream(file), getFormat(file.getName()));

  }

  /**

   * 运行xdoc

   * @param xdoc xdoc

   * @param param 参数

   * @param out 输出目标,OutputStream或HttpServletResponse

   * @param format 目标格式

   * @throws IOException

   */

  public void run(String xdoc, Map<String, Object> param, Object out, String format) throws IOException {

    param.put("_func", "run");

    param.put("_xdoc", xdoc);

    param.put("_format", format);

    invoke(checkParam(param), out);

  }

  /**

   * 运行xdoc并发送

   * @param xdoc xdoc

   * @param param 参数

   * @param to 目标,支持ftp、http、mail、datauri等

   * @param format 目标格式

   * @throws IOException

   */

  public String run(String xdoc, Map<String, Object> param, String to, String format) throws IOException {

    param.put("_func", "run");

    param.put("_xdoc", xdoc);

    param.put("_to", to);

    param.put("_format", format);

    ByteArrayOutputStream out = new ByteArrayOutputStream(); 

    invoke(checkParam(param), out);

    return new String(out.toByteArray(), "UTF-8");

  }

  /**

   * 运行注解XDoc

   * @param obj

   * @param file

   * @throws IOException

   */

  public void run(Object obj, File file) throws IOException {

    run(obj, new FileOutputStream(file), getFormat(file.getName()));

  }

  /**

   * 运行注解XDoc

   * @param obj

   * @param out 目标流

   * @param format 目标格式

   * @throws IOException

   */

  public void run(Object obj, Object out, String format) throws IOException {

    run(obj, out, null, format);

  }

  /**

   * 运行注解XDoc

   * @param obj

   * @param to 目标,支持ftp、http、mail、datauri等

   * @param format 目标格式

   * @throws IOException

   */

  public void run(Object obj, String to, String format) throws IOException {

    run(obj, null, to, format);

  }

  private void run(Object obj, Object out, String to, String format) throws IOException {

    String xurl = "";

    XDoc xdoc = obj.getClass().getAnnotation(XDoc.class);

    if (xdoc != null) {

      xurl = xdoc.value();

    }

    if (xurl.length() == 0) {

       xurl = "./" + obj.getClass().getSimpleName() + ".xdoc";

    }

    Field[] fields = obj.getClass().getDeclaredFields();

    boolean hasXParam = false;

    XParam xParam;

    Map<String, Object> param = new HashMap<String, Object>();

    String name;

    Object value;

    for (Field field : fields) {

      xParam = field.getAnnotation(XParam.class);

      if (xParam != null) {

        hasXParam = true;

        name = xParam.value();

        if (name.length() == 0) {

name = field.getName(); 

        }

        try {

field.setAccessible(true);

value = field.get(obj);

if (name.equals("_xdoc")) {

xurl = String.valueOf(value);

} else {

param.put(name, value);

}

        } catch (Exception e) {

throw new IOException(e);

        }

      }

    }

    if (!hasXParam) { //没有指定xparam,传入所有属性

      for (Field field : fields) {

        try {

field.setAccessible(true);

param.put(field.getName(), field.get(obj));

        } catch (Exception e) {

throw new IOException(e);

        }

      }

    }

    if (out != null) {

      this.run(xurl, param, out, format);

    } else {

      this.run(xurl, param, to, format);

    }

  }

  /**

   * 招呼

   * @return

   * @throws IOException

   */

  public boolean hi() throws IOException {

    return invokeStringFunc("hi").equals("ok");

  }

  /**

   * 关于

   */

  public String about() throws IOException {

    return invokeStringFunc("about");

  }

  /**

   * 动态口令

   * @return

   * @throws IOException

   */

  public String dkey() throws IOException {

    return invokeStringFunc("dkey");

  }

  /**

   * 修改口令

   * @return

   * @throws IOException

   */

  public String ckey() throws IOException {

    return invokeStringFunc("ckey");

  }

  /**

   * 注册

   * @param mail 邮件

   * @return

   * @throws IOException

   */

  public String reg(String mail) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "reg");

    params.put("_mail", mail);

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    return (String) parse(out.toByteArray());

  }

  /**

   * 账户信息

   * @return

   * @throws IOException

   */

  @SuppressWarnings("unchecked")

  public Map<String, String> acc() throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "acc");

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    return (Map<String, String>) parse(out.toByteArray());

  }

  /**

   * 基于ID上传

   * @param id

   * @param file

   * @return

   * @throws IOException

   */

  public void sup(String id, File file) throws IOException {

    sup(id, toDataURI(file.getAbsolutePath()));

  }

  /**

   * 基于ID上传

   * @param id

   * @param in

   * @throws IOException

   */

  public void sup(String id, InputStream in) throws IOException {

    sup(id, toDataURI(in));

  }

  private void sup(String id, String dataUri) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "sup");

    params.put("_id", id);

    params.put("_data", dataUri);

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    parse(out.toByteArray());

  }

  /**

   * 基于ID下载

   * @param id

   * @param file

   * @throws IOException

   */

  public void sdown(String id, File file) throws IOException {

    sdown(id, new FileOutputStream(file));

  }

  /**

   * 基于ID下载

   * @param id

   * @param out 输出目标,OutputStream或HttpServletResponse

   * @throws IOException

   */

  public void sdown(String id, Object out) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "sdown");

    params.put("_id", id);

    invoke(params, out);

  }

  /**

   * 基于ID删除

   * @param id

   * @return

   * @throws IOException

   */

  public boolean sremove(String id) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "sremove");

    params.put("_id", id);

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    return parse(out.toByteArray()).equals("ok");

  }

  /**

   * 创建目录

   * @param dir

   * @return

   * @throws IOException

   */

  public boolean mkdir(String dir) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "mkdir");

    params.put("_dir", dir);

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    return parse(out.toByteArray()).equals("ok");

  }

  /**

   * 目录列表

   * @param dir

   * @return

   * @throws IOException

   */

  @SuppressWarnings("unchecked")

  public List<Map<String, String>> dirlist(String dir) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "dirlist");

    params.put("_dir", dir);

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    return (List<Map<String, String>>) parse(out.toByteArray());

  }

  /**

   * 文件列表

   * @param dir

   * @return

   * @throws IOException

   */

  @SuppressWarnings("unchecked")

  public List<Map<String, String>> filelist(String dir) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "filelist");

    params.put("_dir", dir);

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    return (List<Map<String, String>>) parse(out.toByteArray());

  }

  /**

   * 上传

   * @param dir

   * @param file

   * @return

   * @throws IOException

   */

  public void up(String dir, File file) throws IOException {

    up(dir, toDataURI(file.getAbsolutePath()));

  }

  /**

   * 上传

   * @param dir

   * @param in

   * @throws IOException

   */

  public void up(String dir, InputStream in) throws IOException {

    up(dir, toDataURI(in));

  }

  private void up(String dir, String dataUri) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "up");

    params.put("_dir", dir);

    params.put("_data", dataUri);

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    parse(out.toByteArray());

  }

  /**

   * 下载

   * @param dir

   * @param file

   * @throws IOException

   */

  public void down(String dir, File file) throws IOException {

    down(dir, new FileOutputStream(file));

  }

  /**

   * 下载

   * @param dir

   * @param out 输出目标,OutputStream或HttpServletResponse

   * @throws IOException

   */

  public void down(String dir, Object out) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "down");

    params.put("_dir", dir);

    invoke(params, out);

  }

  /**

   * 删除

   * @param dir

   * @return

   * @throws IOException

   */

  public boolean remove(String dir) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "remove");

    params.put("_dir", dir);

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    return parse(out.toByteArray()).equals("ok");

  }

  /**

   * 文件是否存在

   * @param dir

   * @return

   * @throws IOException

   */

  public boolean exists(String dir) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "exists");

    params.put("_dir", dir);

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    return parse(out.toByteArray()).equals("true");

  }

  /**

   * 查询表单数据

   * @param xdoc 表单文档路径

   * @param keyword 数据过滤关键字

   * @return

   * @throws IOException

   */

  public List<Map<String, String>> xquery(String xdoc, String keyword) throws IOException {

    return this.xquery(xdoc, keyword, -1, -1);

  }

  /**

   * 查询表单数据

   * @param xdoc 表单文档路径

   * @param keyword 数据过滤关键字

   * @param offset 数据偏移,与“_rows”参数一起使用,用于分页,如:&_offset=20&&_rows=10表示每页10条,取第3页

   * @param rows 数量

   * @return

   * @throws IOException

   */

  @SuppressWarnings("unchecked")

  public List<Map<String, String>> xquery(String xdoc, String keyword, int offset, int rows) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "xquery");

    params.put("_xdoc", xdoc);

    params.put("_keyword", keyword);

    params.put("_offset", String.valueOf(offset));

    params.put("_rows", String.valueOf(rows));

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    return (List<Map<String, String>>) parse(out.toByteArray());

  }

  /**

   * 基于ID的XDATA转换

   * @param id

   * @param format 目标格式:xml、json、csv

   * @return

   * @throws IOException 

   */

  public String xdataById(String id, String format) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "xdata");

    params.put("_id", id);

    params.put("_format", format);

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    return (String) parse(out.toByteArray());

  }

  /**

   * XDATA转换

   * @param data xdata数据,格式:xml、json、csv

   * @param format 目标格式:xml、json、csv

   * @return

   * @throws IOException 

   */

  public String xdata(String xdata, String format) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", "xdata");

    params.put("_xdata", xdata);

    params.put("_format", format);

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    return (String) parse(out.toByteArray());

  }

  /**

   * 通过url地址调用服务,支持本地文件xdoc和xdata

   * @param args

   */

  public static void main(String[] args) {

    if (args.length > 0 && args[0].length() > 0) {

      String url = args[0];

      if (url.charAt(0) == '@') { //命令文件

        File cmdFile = new File(url.substring(1));

        try {

FileReader reader = new FileReader(cmdFile);

url = (new BufferedReader(reader)).readLine();

reader.close();

cmdFile.delete();

        } catch (Exception e) {

e.printStackTrace();

return;

        }

      }

      String server = DEFAULT_URL;

      int pos = url.indexOf('?');

      if (pos > 0) {

        server = url.substring(0, pos);

        if (server.endsWith("/xdoc")) {

server = server.substring(0, server.length() - 5);

        }

        url = url.substring(pos + 1);

      }

      String xkey = "";

      try {

        String[] params = url.split("&");

        Map<String, String> map = new HashMap<String, String>();

        String key, value;

        String to = null;

        for (int i = 0; i < params.length; i++) {

pos = params[i].indexOf('=');

if (pos > 0) {

key = decode(params[i].substring(0, pos));

value = decode(params[i].substring(pos + 1));

if (isXDocData(key, value)) {

value = toDataURI(value);

} else if (key.indexOf("@file") > 0) {

key = key.substring(0, key.length() - 5);

value = toDataURI(value);

} else if (key.equals("_key")) {

xkey = value;

continue;

} else if (key.equals("_to") && isFile(value)) {

to = value;

continue;

}

map.put(key, value);

}

        }

        if (!map.containsKey("_format") && to != null && to.indexOf('.') > 0) {

map.put("_format", to.substring(to.lastIndexOf('.') + 1));

        }

        XDocService client  = new XDocService(server, xkey);

        OutputStream out;

        if (to != null) {

out = new FileOutputStream(to);

        } else {

out = System.out;

        }

        client.invoke(map, out);

        if (to != null) {

out.flush();

out.close();

System.out.println(">> " + to);

        }

      } catch (Exception e) {

        e.printStackTrace();

      }

    }

  }

  private void invoke(Map<String, String> param, Object out) throws IOException {

    String xurl = this.url + (this.url.endsWith("/") ? "xdoc" : "/xdoc");

    HttpURLConnection httpConn = (HttpURLConnection) new URL(xurl).openConnection();

    httpConn.setDoOutput(true);

    OutputStream reqOut = httpConn.getOutputStream();

      reqOut.write(("&_key=").getBytes());

      reqOut.write(encode(this.key).getBytes());

      Iterator<String> it = param.keySet().iterator();

      String key;

      while (it.hasNext()) {

          key = it.next();

          reqOut.write(("&" + encode(key) + "=").getBytes());

          reqOut.write(encode(param.get(key)).getBytes());

      }

      reqOut.flush();

      reqOut.close();

      OutputStream os = null;

      if (out instanceof OutputStream) {

        os = (OutputStream) out;

      } else {

      try {

        Method method = out.getClass().getMethod("getOutputStream");

        os = (OutputStream) method.invoke(out);

        method = out.getClass().getMethod("setHeader", String.class, String.class);

        String[] headerNames = new String[] {"Content-Type", "Content-Disposition"};

        String headerValue;

        for (String headerName : headerNames) {

headerValue = httpConn.getHeaderField(headerName);

if (headerValue != null) {

method.invoke(out, headerName, headerValue);

}

        }

      } catch (Exception e) {

        throw new IOException(e);

      }

      }

      pipe(httpConn.getInputStream(), os);

  }

  private Object parse(byte[] data) throws IOException {

    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();   

    factory.setValidating(false);   

    try {

      DocumentBuilder builder = factory.newDocumentBuilder();

      Document document = builder.parse(new ByteArrayInputStream(data));

      document.getDocumentElement().normalize();   

      Element root = document.getDocumentElement();

      if (root.getAttribute("success").equals("true")) {

        Element result = (Element) root.getElementsByTagName("result").item(0);

        String dataType = result.getAttribute("dataType");

        if (dataType.equals("string")) {

return result.getTextContent();

        } else if (dataType.equals("map")) {

NodeList items = result.getElementsByTagName("value");

Map<String, String> map = new HashMap<String, String>();

Element value;

NamedNodeMap atts;

for (int i = 0; i < items.getLength(); i++) {

value = (Element) items.item(i);

atts = value.getAttributes();

for (int j = 0; j < atts.getLength(); j++) {

map.put(atts.item(j).getNodeName(), atts.item(j).getNodeValue());

}

}

return map;

        } else if (dataType.equals("rowset")) {

Map<String, String> fieldMap = new HashMap<String, String>();

String[] fields = result.getAttribute("fields").split(",");

String[] formerFields = fields;

if (result.hasAttribute("formerFields")) {

formerFields = csvSplit(result.getAttribute("formerFields"));

}

for (int j = 0; j < formerFields.length; j++) {

fieldMap.put(fields[j], formerFields[j]);

}

NodeList eleList = result.getElementsByTagName("row");

Element ele;

Map<String, String> map;

List<Map<String, String>> List = new ArrayList<Map<String, String>>();

for (int i = 0; i < eleList.getLength(); i++) {

ele = (Element) eleList.item(i);

map = new HashMap<String, String>();

List.add(map);

for (int j = 0; j < fields.length; j++) {

map.put(formerFields[j], ele.getAttribute(fields[j]));

}

}

return List;

        } else {

return "";

        }

      } else {

        throw new IOException(root.getElementsByTagName("error").item(0).getTextContent());

      }

    } catch (ParserConfigurationException e) {

      throw new IOException(e);

    } catch (SAXException e) {

      throw new IOException(e);

    }   

  }

  private String invokeStringFunc(String func) throws IOException {

    Map<String, String> params = new HashMap<String, String>();

    params.put("_func", func);

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    invoke(params, out);

    return (String) parse(out.toByteArray());

  }

  private Map<String, String> checkParam(Map<String, Object> param) throws IOException {

    Map<String, String> map = new HashMap<String, String>();

    String key, value;

    Iterator<String> it = param.keySet().iterator();

    while (it.hasNext()) {

      key = it.next();

      value = toParamString(param.get(key));

      if (isXDocData(key, value)) {

        value = toDataURI(value);

      } else if (key.endsWith("@file")) {

        key = key.substring(0, key.length() - 5);

        value = toDataURI(value);

      }

      map.put(key, value);

    }

    return map;

  }

  private static String toParamString(Object obj) throws IOException {

    String str;

    if (obj == null) {

      str = "";

    } else if (obj.getClass().isPrimitive()

        || obj instanceof Boolean

        || obj instanceof Number

        || obj instanceof CharSequence) {

      str = obj.toString();

    } else if (obj instanceof Date) {

      str = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) obj);

    } else if (obj instanceof File) {

      str = toDataURI(((File) obj).getAbsolutePath());

    } else if (obj instanceof InputStream) {

      str = toDataURI((InputStream) obj);

    } else {

      StringBuilder sb = new StringBuilder();

      Set<Object> chainSet = new HashSet<Object>();

      writeParamString(sb, obj, chainSet);

      str = sb.toString();

    }

    return str;

  }

  private static void writeParamString(StringBuilder sb, Object obj, Set<Object> set) throws IOException {

    if (obj == null) {

      sb.append("null");

    } else if (obj.getClass().isPrimitive()

        || obj instanceof Boolean

        || obj instanceof Number) {

      sb.append(toParamString(obj));

    } else if (obj instanceof CharSequence

        || obj instanceof Date) {

      jencode(toParamString(obj), sb);

    } else if (obj instanceof Collection) {

      sb.append("[");

      boolean b = false;

      Iterator<?> it = ((Collection<?>) obj).iterator();

      while (it.hasNext()) {

        if (b) sb.append(",");

        writeParamString(sb, it.next(), set);

        b = true;

      }

      sb.append("]");

    } else if (obj.getClass().isArray()) {

      sb.append("[");

      boolean b = false;

      int n = Array.getLength(obj);

      for (int i = 0; i < n; i++) {

        if (b) sb.append(",");

        writeParamString(sb, Array.get(obj, i), set);

        b = true;

      }

      sb.append("]");

    } else if (obj instanceof Map) {

      sb.append("{");

      Map<?, ?> map = (Map<?, ?>) obj;

      boolean b = false;

      Object key;

      Iterator<?> it = map.keySet().iterator();

      while (it.hasNext()) {

        if (b) sb.append(",");

        key = it.next();

        jencode(key.toString(), sb);

        sb.append(":");

        writeParamString(sb, map.get(key), set);

        b = true;

      }

      sb.append("}");

    } else {

      sb.append("{");

      if (!set.contains(obj) && obj.getClass() != Object.class && obj.getClass() != Class.class) {

        set.add(obj);

        try {

List<Method> getters = findGetters(obj);

boolean b = false;

for (Method method : getters) {

if (b) sb.append(",");

jencode(findGetterName(method), sb);

sb.append(":");

writeParamString(sb, method.invoke(obj, new Object[0]), set);

b = true;

}

        } catch (Exception e) {

throw new IOException(e);

        }

        set.remove(obj);

      }

      sb.append("}");

    }

  }

  private static List<Method> findGetters(Object obj) {

    List<Method> getters = new ArrayList<Method>();

    String name;

        for (Method method : obj.getClass().getMethods()) {

            name = method.getName();

            if (Modifier.isStatic(method.getModifiers())

|| method.getReturnType().equals(Void.TYPE)

|| method.getParameterTypes().length != 0

|| method.getReturnType() == ClassLoader.class

) {

                continue;

            }

            if (name.startsWith("get") && name.length() >= 4 && !name.equals("getClass")

|| name.startsWith("is") && name.length() >= 3) {

            getters.add(method);

            }

        }

        return getters;

    }

  private static String findGetterName(Method method) {

    String name = method.getName();

    if (name.startsWith("get")) {

      name = name.substring(3);

    } else if (name.startsWith("is")) {

      name = name.substring(2);

    }

        if (name.length() > 1

            && Character.isUpperCase(name.charAt(1))

            && Character.isUpperCase(name.charAt(0))){

            return name;

        }

        char chars[] = name.toCharArray();

        chars[0] = Character.toLowerCase(chars[0]);

        return new String(chars);

  }

  private static void jencode(String str, StringBuilder sb) {

        sb.append("\"");

        char c;

        for (int i = 0; i < str.length(); i++) {

            c = str.charAt(i);

            if (c == '\\') {

                sb.append("\\\\");

            } else if (c == '/') {

                sb.append("\\/");

            } else if (c == '\n') {

                sb.append("\\n");

            } else if (c == '\r') {

                sb.append("\\r");

            } else if (c == '\t') {

                sb.append("\\t");

            } else if (c == '\'') {

                sb.append("\\\'");

            } else if (c == '\"') {

                sb.append("\\\"");

            } else {

                sb.append(c);

            }

        }

        sb.append("\"");

    }

  private static boolean isXDocData(String name, String value) {

    if (name.equals("_xdoc") || name.equals("_xdata")) {

        if (value.startsWith("./")

|| value.startsWith("<")

|| value.startsWith("{")

|| value.startsWith("[")

|| value.startsWith("data:")

|| name.equals("_xdoc") && value.startsWith("text:")) {

          return false;

        } else {

          return true;

        }

    }

    return false;

  }

  private static String getFormat(String url) {

      String format = "xdoc";

      int pos = url.lastIndexOf(".");

      if (pos > 0) {

          format = url.substring(pos + 1).toLowerCase();

          if (format.equals("zip")) {

              url = url.substring(0, pos);

              pos = url.lastIndexOf(".");

              if (pos > 0) {

format = url.substring(pos + 1).toLowerCase() + ".zip";

              }

          }

      }

      return format;

  }

  private static String encode(Object str) {

        try {

            return URLEncoder.encode(String.valueOf(str), "UTF-8");

        } catch (UnsupportedEncodingException e) {

            return String.valueOf(str);

        }

    }

  private static String decode(String str) {

        try {

            return URLDecoder.decode(str, "UTF-8");

        } catch (UnsupportedEncodingException e) {

            return str;

        }

    }

    private static void pipe(InputStream in, OutputStream out) throws IOException {

        int len;

        byte[] buf = new byte[4096];

        while (true) {

            len = in.read(buf);

            if (len > 0) {

                out.write(buf, 0, len);

            } else {

                break;

            }

        }

        out.flush();

        out.close();

        in.close();

    }

    private static boolean isFile(String url) {

      int pos = url.indexOf(':');

      return pos < 0

          || pos == 1

          || (pos == 2 && url.charAt(0) == '/');

    }

    /**

     * URL结果转换为datauri

     * @param url http/ftp/file

     * @return

     * @throws IOException

     */

    public static String toDataURI(String url) throws IOException {

      return toDataURI(url, null);

    }

    /**

     * URL结果转换为datauri

     * @param url http/ftp/file

     * @param format 文件格式或mime-type

     * @return

     * @throws IOException

     */

    public static String toDataURI(String url, String format) throws IOException {

      if (url.length() > 0) {

        InputStream in = null;

        if (isFile(url) || url.startsWith("class://")) {

          if (format == null) {

int pos = url.lastIndexOf('.');

if (pos > 0) {

format = url.substring(pos + 1).toLowerCase();

}

          }

          if (url.startsWith("class://")) {

String cls = url.substring(8, url.indexOf("/", 8));

String path = url.substring(url.indexOf("/", 8) + 1);

try {

in = Class.forName(cls).getResourceAsStream(path);

} catch (Exception e) {

throw new IOException(e);

}

          } else {

in = new FileInputStream(url);

          }

        } else {

          URLConnection conn = new URL(url).openConnection();

          in = conn.getInputStream();

          if (format == null) {

format = conn.getContentType();

          }

        }

        return toDataURI(in, format);

      } else {

        return "";

      }

    }

    /**

     * 数据流转换为datauri

     * @param in

     * @return

     * @throws IOException

     */

    public static String toDataURI(InputStream in) throws IOException {

      return toDataURI(in, null);

    }

    /**

     * 数据流转换为datauri

     * @param in

     * @param format 文件格式或mime-type

     * @return

     * @throws IOException

     */

    public static String toDataURI(InputStream in, String format) throws IOException {

      ByteArrayOutputStream out = new ByteArrayOutputStream();

      pipe(in, out);

      if (format != null) {

        if (format.indexOf('/') < 0) {

          if (format.equals("jpg")) {

format = "jpeg";

          } else if (format.equals("htm")) {

format = "html";

          }

          if (format.equals("png") || format.equals("jpeg") || format.equals("gif")) {

format = "image/" + format;

          } else if (format.equals("html") || format.equals("xml")) {

format = "text/" + format;

          } else {

format = "application/" + format;

          }

        }

      } else {

      format = "application/octet-stream";

    }

      StringBuffer sb = new StringBuffer();

      sb.append("data:").append(format).append(";base64,");

      sb.append(toBase64(out.toByteArray()));

      return sb.toString();

    }

    private static String toBase64(final byte[] data) {

        final char[] out = new char[((data.length + 2) / 3) * 4];

        for (int i = 0, index = 0; i < data.length; i += 3, index += 4) {

            boolean quad = false;

            boolean trip = false;

            int val = (0xFF & data[i]);

            val <<= 8;

            if ((i + 1) < data.length) {

                val |= (0xFF & data[i + 1]);

                trip = true;

            }

            val <<= 8;

            if ((i + 2) < data.length) {

                val |= (0xFF & data[i + 2]);

                quad = true;

            }

            out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)];

            val >>= 6;

            out[index + 2] = alphabet[(trip ? (val & 0x3F) : 64)];

            val >>= 6;

            out[index + 1] = alphabet[val & 0x3F];

            val >>= 6;

            out[index + 0] = alphabet[val & 0x3F];

        }

        return new String(out);

    }

    private static char[] alphabet =

        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray();

    private static byte[] codes = new byte[256];

    static {

        for (int i = 0; i < 256; i++) {

            codes[i] = -1;

        }

        for (int i = 'A'; i <= 'Z'; i++) {

            codes[i] = (byte) (i - 'A');

        }

        for (int i = 'a'; i <= 'z'; i++) {

            codes[i] = (byte) (26 + i - 'a');

        }

        for (int i = '0'; i <= '9'; i++) {

            codes[i] = (byte) (52 + i - '0');

        }

        codes['+'] = 62;

        codes['/'] = 63;

    }

  private static String[] csvSplit(String str) {

    List<List<String>> list = csvList(str);

    if (list.size() > 0) {

      List<String> cols = (List<String>) list.get(0);

      String[] strs = new String[cols.size()];

      for (int i = 0; i < strs.length; i++) {

        strs[i] = cols.get(i);

      }

      return strs;

    } else {

      return new String[0];

    }

  }

    private static List<List<String>> csvList(String txt) {

      if (txt.length() > 0) {

        ArrayList<List<String>> rows = new ArrayList<List<String>>();

        ArrayList<String> cols = new ArrayList<String>();

        rows.add(cols);

        char c;

        boolean strBegin = false;

        StringBuffer sb = new StringBuffer();

        for (int i = 0; i < txt.length(); i++) {

          c = txt.charAt(i);

          if (strBegin) {

if (c == '"') {

if (i + 1 < txt.length()) {

if (txt.charAt(i + 1) == '"') {

sb.append(c);

i++;

} else {

strBegin = false;

}

} else {

strBegin = false;

}

} else {

sb.append(c);

}

          } else {

if (c == ',') {

cols.add(sb.toString());

sb.setLength(0);

} else if (c == '\n') {

cols.add(sb.toString());

sb.setLength(0);

cols = new ArrayList<String>();

rows.add(cols);

} else if (c == '"') {

strBegin = true;

} else if (c != '\r') {

sb.append(c);

}

          }

        }

        if (sb.length() > 0) {

          cols.add(sb.toString());

        }

        return rows;

      } else {

        return new ArrayList<List<String>>();

      }

    }

    /**

     * XDOC注解

     * @author XDOC

     */

    @Target(ElementType.TYPE)

    @Retention(RetentionPolicy.RUNTIME)

    @Documented

    public @interface XDoc {

      /**

       * xdoc

       * @return

       */

      public String value() default "";

    }

    /**

     * XDOC参数注解

     * @author XDOC

     */

    @Target(ElementType.FIELD)

    @Retention(RetentionPolicy.RUNTIME)

    @Documented

    public @interface XParam {

      /**

       * 参数名称

       * @return

       */

      public String value() default "";

    }

}