laowo 4 лет назад
Родитель
Сommit
4a45fa187f
99 измененных файлов с 14979 добавлено и 0 удалено
  1. 3 0
      .gitignore
  2. 155 0
      pom.xml
  3. 23 0
      src/main/java/com/usky/Application.java
  4. 33 0
      src/main/java/com/usky/annotion/AutoLog.java
  5. 189 0
      src/main/java/com/usky/aspect/AutoLogAspect.java
  6. 35 0
      src/main/java/com/usky/config/CorsConfig.java
  7. 91 0
      src/main/java/com/usky/config/HibernateConfig.java
  8. 72 0
      src/main/java/com/usky/config/RemoveDruidAdConfig.java
  9. 45 0
      src/main/java/com/usky/config/Swagger2Configuration.java
  10. 94 0
      src/main/java/com/usky/config/mqtt/MqttCofigBean.java
  11. 111 0
      src/main/java/com/usky/config/mqtt/SpringMqttConfig.java
  12. 63 0
      src/main/java/com/usky/config/redis/RedisConfig.java
  13. 69 0
      src/main/java/com/usky/config/shiro/MyRealm.java
  14. 100 0
      src/main/java/com/usky/config/shiro/ShiroConfig.java
  15. 22 0
      src/main/java/com/usky/config/sms/SmsConfig.java
  16. 24 0
      src/main/java/com/usky/config/sms/SmsConstants.java
  17. 42 0
      src/main/java/com/usky/config/sms/SmsProperties.java
  18. 84 0
      src/main/java/com/usky/constant/CommonConstant.java
  19. 210 0
      src/main/java/com/usky/controller/build/BuildController.java
  20. 38 0
      src/main/java/com/usky/controller/mqtt/MqttController.java
  21. 63 0
      src/main/java/com/usky/controller/mqtt/MqttHandler.java
  22. 13 0
      src/main/java/com/usky/dao/BaseDao.java
  23. 27 0
      src/main/java/com/usky/dao/impl/BaseDaoImpl.java
  24. 42 0
      src/main/java/com/usky/emu/ResponeCodeEnum.java
  25. 117 0
      src/main/java/com/usky/entity/build/TbBuildingEntity.java
  26. 36 0
      src/main/java/com/usky/entity/build/TbBuildingVoEntity.java
  27. 96 0
      src/main/java/com/usky/entity/build/TbFloorEntity.java
  28. 33 0
      src/main/java/com/usky/entity/build/TbFloorVoEntity.java
  29. 95 0
      src/main/java/com/usky/entity/build/TbRoomEntity.java
  30. 33 0
      src/main/java/com/usky/entity/build/TbRoomVoEntity.java
  31. 206 0
      src/main/java/com/usky/entity/sys/SysLogDTO.java
  32. 106 0
      src/main/java/com/usky/exception/GloableExceptionResolver.java
  33. 104 0
      src/main/java/com/usky/service/build/BuildService.java
  34. 263 0
      src/main/java/com/usky/service/build/BuildServiceImpl.java
  35. 7 0
      src/main/java/com/usky/service/log/LogService.java
  36. 19 0
      src/main/java/com/usky/service/log/LogServiceImpl.java
  37. 22 0
      src/main/java/com/usky/task/Dhtask.java
  38. 17 0
      src/main/java/com/usky/task/ScheduledConfig.java
  39. 74 0
      src/main/java/com/usky/utils/BeanHelp.java
  40. 28 0
      src/main/java/com/usky/utils/ConfigUtil.java
  41. 26 0
      src/main/java/com/usky/utils/DaoResult.java
  42. 23 0
      src/main/java/com/usky/utils/DialectForInkfish.java
  43. 315 0
      src/main/java/com/usky/utils/HttpUtils.java
  44. 58 0
      src/main/java/com/usky/utils/IPUtils.java
  45. 53 0
      src/main/java/com/usky/utils/JsonUtil.java
  46. 75 0
      src/main/java/com/usky/utils/JsonUtils.java
  47. 37 0
      src/main/java/com/usky/utils/ListUtil.java
  48. 30 0
      src/main/java/com/usky/utils/LoginResult.java
  49. 39 0
      src/main/java/com/usky/utils/MD5Util.java
  50. 99 0
      src/main/java/com/usky/utils/MD5Utils.java
  51. 88 0
      src/main/java/com/usky/utils/MapUtils.java
  52. 128 0
      src/main/java/com/usky/utils/Page.java
  53. 130 0
      src/main/java/com/usky/utils/PageData.java
  54. 618 0
      src/main/java/com/usky/utils/RedisUtil.java
  55. 33 0
      src/main/java/com/usky/utils/RequestUtils.java
  56. 106 0
      src/main/java/com/usky/utils/Result.java
  57. 137 0
      src/main/java/com/usky/utils/ResultBase.java
  58. 122 0
      src/main/java/com/usky/utils/ResultSocket.java
  59. 83 0
      src/main/java/com/usky/utils/ResultSw.java
  60. 83 0
      src/main/java/com/usky/utils/ResultSwaager.java
  61. 76 0
      src/main/java/com/usky/utils/SmsUtils.java
  62. 79 0
      src/main/java/com/usky/utils/SpringContextUtils.java
  63. 23 0
      src/main/java/com/usky/utils/TimeUtil.java
  64. 16 0
      src/main/java/com/usky/utils/UUIDUtils.java
  65. 65 0
      src/main/java/com/usky/webScoket/HttpAuthHandler.java
  66. 59 0
      src/main/java/com/usky/webScoket/MyInterceptor.java
  67. 27 0
      src/main/java/com/usky/webScoket/WebSocketConfig.java
  68. 94 0
      src/main/java/com/usky/webScoket/WsSessionManager.java
  69. 287 0
      src/main/java/com/usky/xml/CDL.java
  70. 224 0
      src/main/java/com/usky/xml/Cookie.java
  71. 86 0
      src/main/java/com/usky/xml/CookieList.java
  72. 162 0
      src/main/java/com/usky/xml/HTTP.java
  73. 77 0
      src/main/java/com/usky/xml/HTTPTokener.java
  74. 1710 0
      src/main/java/com/usky/xml/JSONArray.java
  75. 69 0
      src/main/java/com/usky/xml/JSONException.java
  76. 542 0
      src/main/java/com/usky/xml/JSONML.java
  77. 2616 0
      src/main/java/com/usky/xml/JSONObject.java
  78. 295 0
      src/main/java/com/usky/xml/JSONPointer.java
  79. 45 0
      src/main/java/com/usky/xml/JSONPointerException.java
  80. 43 0
      src/main/java/com/usky/xml/JSONPropertyIgnore.java
  81. 47 0
      src/main/java/com/usky/xml/JSONPropertyName.java
  82. 43 0
      src/main/java/com/usky/xml/JSONString.java
  83. 79 0
      src/main/java/com/usky/xml/JSONStringer.java
  84. 526 0
      src/main/java/com/usky/xml/JSONTokener.java
  85. 414 0
      src/main/java/com/usky/xml/JSONWriter.java
  86. 75 0
      src/main/java/com/usky/xml/Property.java
  87. 859 0
      src/main/java/com/usky/xml/XML.java
  88. 286 0
      src/main/java/com/usky/xml/XMLParserConfiguration.java
  89. 416 0
      src/main/java/com/usky/xml/XMLTokener.java
  90. 66 0
      src/main/java/com/usky/xml/XMLXsiTypeConverter.java
  91. 72 0
      src/main/resources/application.yml
  92. BIN
      src/main/resources/favicon.ico
  93. 121 0
      src/main/resources/logback-spring.xml
  94. 10 0
      src/main/resources/mqtt-spring.properties
  95. 13 0
      src/main/test/java/com/usky/controller/Basetest.java
  96. 111 0
      src/main/test/java/com/usky/controller/TestTest.java
  97. 17 0
      src/main/test/java/com/usky/controller/mqtt/BaseTest.java
  98. 18 0
      src/main/test/java/com/usky/controller/mqtt/MqttTestControllerTest.java
  99. 24 0
      src/main/test/java/com/usky/controller/top/TopConfigControllerTest.java

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+/.idea/
+/JX_Cover.iml
+/target

+ 155 - 0
pom.xml

@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <properties>
+        <java.version>1.8</java.version>
+    </properties>
+    <groupId>com.usky</groupId>
+    <artifactId>JX_Cover</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <parent>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <groupId>org.springframework.boot</groupId>
+        <version>2.1.4.RELEASE</version>
+    </parent>
+    <dependencies>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.35</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tomcat</groupId>
+            <artifactId>tomcat-juli</artifactId>
+            <version>8.5.51</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>1.1.22</version>
+        </dependency>
+        <!-- JPA-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <!--导入连接MySQL的依赖 -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+        <!--thymeleaf启动器-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-thymeleaf</artifactId>
+        </dependency>
+        <!--test-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <!--shiro依赖-->
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-spring</artifactId>
+            <version>1.5.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.hikvision.ga</groupId>
+            <artifactId>artemis-http-client</artifactId>
+            <version>1.1.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>2.9.2</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>io.swagger</groupId>
+                    <artifactId>swagger-models</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+            <version>2.0.4</version>
+        </dependency>
+        <dependency>
+            <groupId>net.sf.json-lib</groupId>
+            <artifactId>json-lib</artifactId>
+            <version>2.4</version>
+            <classifier>jdk15</classifier>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+        <!--MQTT依赖-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-integration</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.integration</groupId>
+            <artifactId>spring-integration-mqtt</artifactId>
+        </dependency>
+        <!--redis-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+        <!--短信服务-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>4.1.1</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.6.2</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.1.4.RELEASE</version>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 23 - 0
src/main/java/com/usky/Application.java

@@ -0,0 +1,23 @@
+package com.usky;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/11/2 11:27
+ * @description TODO
+ **/
+@SpringBootApplication
+@EnableTransactionManagement
+@EnableScheduling
+public class Application {
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+
+    }
+
+}

+ 33 - 0
src/main/java/com/usky/annotion/AutoLog.java

@@ -0,0 +1,33 @@
+package com.usky.annotion;
+
+import com.usky.constant.CommonConstant;
+
+import java.lang.annotation.*;
+
+/**
+ * 系统日志注解
+ * @author laowo
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface AutoLog {
+
+	/**
+	 * 日志内容
+	 * @return
+	 */
+	String value() default "";
+
+	/**
+	 * 日志类型
+	 * @return 0:操作日志;1:登录日志;2:定时任务;
+	 */
+	int logType() default CommonConstant.LOG_TYPE_2;
+	
+	/**
+	 * 操作日志类型
+	 * @return (1查询,2添加,3修改,4删除)
+	 */
+	int operateType() default 0;
+}

+ 189 - 0
src/main/java/com/usky/aspect/AutoLogAspect.java

@@ -0,0 +1,189 @@
+package com.usky.aspect;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.serializer.PropertyFilter;
+import com.usky.annotion.AutoLog;
+import com.usky.constant.CommonConstant;
+import com.usky.entity.sys.SysLogDTO;
+import com.usky.service.log.LogService;
+import com.usky.utils.IPUtils;
+import com.usky.utils.SpringContextUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.multipart.MultipartFile;
+import javax.annotation.Resource;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+import java.sql.Timestamp;
+
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/11/10 14:41
+ * @description 系统日志切面类
+ **/
+@Aspect
+@Component
+public class AutoLogAspect {
+
+    @Resource
+    private LogService logService;
+
+    @Pointcut("@annotation(com.usky.annotion.AutoLog)")
+    public void logPointCut() {
+
+    }
+
+    @Around("logPointCut()")
+    public Object around(ProceedingJoinPoint point) throws Throwable {
+        long beginTime = System.currentTimeMillis();
+        //执行方法
+        Object result = point.proceed();
+        //执行时长(毫秒)
+        long time = System.currentTimeMillis() - beginTime;
+
+        //保存日志
+        saveSysLog(point, time, result);
+
+        return result;
+    }
+
+    private void saveSysLog(ProceedingJoinPoint joinPoint, long time, Object obj) {
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+
+        SysLogDTO dto = new SysLogDTO();
+        AutoLog syslog = method.getAnnotation(AutoLog.class);
+        if(syslog != null){
+            //update-begin-author:taoyan date:
+            String content = syslog.value();
+            //注解上的描述,操作日志内容
+            dto.setLogType(syslog.logType());
+            dto.setLogContent(content);
+        }
+
+        //请求的方法名
+        String className = joinPoint.getTarget().getClass().getName();
+        String methodName = signature.getName();
+        dto.setMethod(className + "." + methodName + "()");
+
+
+        //设置操作类型
+        if (dto.getLogType() == CommonConstant.LOG_TYPE_2) {
+            dto.setOperateType(getOperateType(methodName, syslog.operateType()));
+        }
+
+        //获取request
+        HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
+        //请求的参数
+        dto.setRequestType(request.getMethod());
+        dto.setRequestParam(getReqestParams(request,joinPoint));
+        //设置IP地址
+        dto.setIp(IPUtils.getIpAddr(request));
+        //获取登录用户信息 TODO 添加用户数据后启用
+
+      // LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+      // if(sysUser!=null){
+      //     dto.setUserid(sysUser.getUsername());
+      //     dto.setUsername(sysUser.getRealname());
+
+      // }
+        //耗时
+        dto.setCostTime(time);
+        dto.setCreateTime(new Timestamp(System.currentTimeMillis()));
+        //保存系统日志
+        logService.addLog(dto);
+    }
+
+
+    /**
+     * 获取操作类型
+     */
+    private int getOperateType(String methodName,int operateType) {
+        if (operateType > 0) {
+            return operateType;
+        }
+        if (methodName.startsWith("list")) {
+            return CommonConstant.OPERATE_TYPE_1;
+        }
+        if (methodName.startsWith("add")) {
+            return CommonConstant.OPERATE_TYPE_2;
+        }
+        if (methodName.startsWith("edit")) {
+            return CommonConstant.OPERATE_TYPE_3;
+        }
+        if (methodName.startsWith("delete")) {
+            return CommonConstant.OPERATE_TYPE_4;
+        }
+        if (methodName.startsWith("import")) {
+            return CommonConstant.OPERATE_TYPE_5;
+        }
+        if (methodName.startsWith("export")) {
+            return CommonConstant.OPERATE_TYPE_6;
+        }
+        return CommonConstant.OPERATE_TYPE_1;
+    }
+
+    /**
+     * 请求参数获取
+     * @param request
+     * @param joinPoint
+     * @return
+     */
+    private String getReqestParams(HttpServletRequest request, JoinPoint joinPoint) {
+        String httpMethod = request.getMethod();
+        String params = "";
+        if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod)) {
+            Object[] paramsArray = joinPoint.getArgs();
+            // java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
+            //  https://my.oschina.net/mengzhang6/blog/2395893
+            Object[] arguments  = new Object[paramsArray.length];
+            for (int i = 0; i < paramsArray.length; i++) {
+                if (paramsArray[i] instanceof BindingResult || paramsArray[i] instanceof ServletRequest || paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile) {
+                    //ServletRequest不能序列化,从入参里排除,否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
+                    //ServletResponse不能序列化 从入参里排除,否则报异常:java.lang.IllegalStateException: getOutputStream() has already been called for this response
+                    continue;
+                }
+                arguments[i] = paramsArray[i];
+            }
+            //update-begin-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
+            PropertyFilter profilter = new PropertyFilter() {
+                @Override
+                public boolean apply(Object o, String name, Object value) {
+                    if(value!=null && value.toString().length()>500){
+                        return false;
+                    }
+                    return true;
+                }
+            };
+            params = JSONObject.toJSONString(arguments, profilter);
+            //update-end-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
+        } else {
+            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+            Method method = signature.getMethod();
+            // 请求的方法参数值
+            Object[] args = joinPoint.getArgs();
+            // 请求的方法参数名称
+            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
+            String[] paramNames = u.getParameterNames(method);
+            if (args != null && paramNames != null) {
+                for (int i = 0; i < args.length; i++) {
+                    params += "  " + paramNames[i] + ": " + args[i];
+                }
+            }
+        }
+        return params;
+    }
+
+
+}

+ 35 - 0
src/main/java/com/usky/config/CorsConfig.java

@@ -0,0 +1,35 @@
+package com.usky.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/11/11 9:04
+ * @description TODO
+ **/
+@Configuration
+public class CorsConfig implements WebMvcConfigurer {
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**").allowedOrigins("*").allowCredentials(true)
+                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").maxAge(3600);
+    }
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        /*静态资源的位置*/
+        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
+        registry.addResourceHandler("/templates/**").addResourceLocations("classpath:/templates/");
+        /*放行swagger*/
+        registry.addResourceHandler("/webjars/**")
+                .addResourceLocations("classpath:/META-INF/resources/webjars/");
+        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
+
+
+    }
+
+
+}

+ 91 - 0
src/main/java/com/usky/config/HibernateConfig.java

@@ -0,0 +1,91 @@
+package com.usky.config;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.alibaba.druid.support.http.StatViewServlet;
+import com.alibaba.druid.support.http.WebStatFilter;
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.hibernate5.HibernateTemplate;
+import org.springframework.orm.hibernate5.HibernateTransactionManager;
+import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.sql.DataSource;
+import java.util.Properties;
+
+@Configuration
+@EnableJpaRepositories
+@EnableTransactionManagement
+public class HibernateConfig {
+    @Autowired
+    private SessionFactory sessionFactory;
+
+    @ConfigurationProperties(prefix = "spring.datasource.druid")
+    @Bean
+    public DataSource dataSource() {
+        return new DruidDataSource();
+    }
+
+
+    @Bean
+    public LocalSessionFactoryBean localSessionFactoryBean() {
+        Properties properties = new Properties();
+        properties.setProperty("hibernate.show_sql", "true");
+        properties.setProperty("hibernate.hbm2ddl.auto", "update");
+        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
+        LocalSessionFactoryBean lsb = new LocalSessionFactoryBean();
+        lsb.setDataSource(dataSource());
+//        lsb.setAnnotatedClasses(Student.class);这里demo可以这么写,但我们项目不可能就一个实体类,所以用下面的setPackagesToScan方法
+        lsb.setPackagesToScan("com.usky.entity");
+        lsb.setHibernateProperties(properties);
+        return lsb;
+    }
+    @Bean
+    public HibernateTemplate hibernateTemplate() {
+        HibernateTemplate hibernateTemplate = new HibernateTemplate();
+        hibernateTemplate.setSessionFactory(sessionFactory);
+        return hibernateTemplate;
+    }
+    @Bean
+    public HibernateTransactionManager hibernateTransactionManager() {
+        HibernateTransactionManager ts = new HibernateTransactionManager();
+        ts.setSessionFactory(sessionFactory);
+        return ts;
+    }
+    @Bean
+    public ServletRegistrationBean statViewServlet() {
+        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
+        // 添加IP白名单
+     //   servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
+        // 添加IP黑名单,当白名单和黑名单重复时,黑名单优先级更高
+     //   servletRegistrationBean.addInitParameter("deny", "127.0.0.1");
+        // 添加控制台管理用户
+        servletRegistrationBean.addInitParameter("loginUsername", "admin");
+        servletRegistrationBean.addInitParameter("loginPassword", "admin");
+        // 是否能够重置数据
+        servletRegistrationBean.addInitParameter("resetEnable", "true");
+        return servletRegistrationBean;
+    }
+
+    /**
+     * 配置服务过滤器
+     * @return 返回过滤器配置对象
+     */
+    @Bean
+    public FilterRegistrationBean statFilter() {
+        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
+        // 添加过滤规则
+        filterRegistrationBean.addUrlPatterns("/*");
+        // 忽略过滤格式
+        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*,");
+        return filterRegistrationBean;
+    }
+}
+
+

+ 72 - 0
src/main/java/com/usky/config/RemoveDruidAdConfig.java

@@ -0,0 +1,72 @@
+package com.usky.config;
+
+
+import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
+import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
+import com.alibaba.druid.util.Utils;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.*;
+import java.io.IOException;
+
+
+@Configuration
+@ConditionalOnWebApplication
+@AutoConfigureAfter(DruidDataSourceAutoConfigure.class)
+@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled",
+        havingValue = "true", matchIfMissing = true)
+public class RemoveDruidAdConfig {
+ 
+    /**
+     * 方法名: removeDruidAdFilterRegistrationBean
+     * 方法描述:  除去页面底部的广告
+     * @param properties
+     * @return org.springframework.boot.web.servlet.FilterRegistrationBean
+     * @throws
+     */
+    @Bean
+    public FilterRegistrationBean removeDruidAdFilterRegistrationBean(DruidStatProperties properties)
+    {
+        // 获取web监控页面的参数
+        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
+        // 提取common.js的配置路径
+        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
+        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
+ 
+        final String filePath = "support/http/resources/js/common.js";
+ 
+        //创建filter进行过滤
+        Filter filter = new Filter() {
+            @Override
+            public void init(FilterConfig filterConfig) throws ServletException {
+            }
+ 
+            @Override
+            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+                    throws IOException, ServletException {
+                chain.doFilter(request, response);
+                // 重置缓冲区,响应头不会被重置
+                response.resetBuffer();
+                // 获取common.js
+                String text = Utils.readFromResource(filePath);
+                // 正则替换banner, 除去底部的广告信息
+                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
+                text = text.replaceAll("powered.*?shrek.wang</a>", "");
+                response.getWriter().write(text);
+            }
+ 
+            @Override
+            public void destroy() {
+            }
+        };
+        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
+        registrationBean.setFilter(filter);
+        registrationBean.addUrlPatterns(commonJsPattern);
+        return registrationBean;
+    }
+}

+ 45 - 0
src/main/java/com/usky/config/Swagger2Configuration.java

@@ -0,0 +1,45 @@
+package com.usky.config;
+
+import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+@Configuration
+@EnableSwagger2
+@EnableKnife4j
+public class Swagger2Configuration {
+    @Value("${swagger.enable}")
+    private boolean enable;
+
+    @Bean
+    public Docket buildDocket() {
+        return new Docket(DocumentationType.SWAGGER_2)
+                .apiInfo(buildApiInf())
+                .enable(enable)
+                .select()
+                .apis(RequestHandlerSelectors.basePackage("com.usky.controller"))
+                .paths(PathSelectors.any())
+                .build();
+    }
+
+    private ApiInfo buildApiInf() {
+        return new ApiInfoBuilder()
+                .title("山东党校 v1.0")
+                .version("1.0.0")
+                .description("springboot swagger2")
+                //   .termsOfServiceUrl("网址链接")
+                .contact(new Contact("上海永天科技", "http://www.chinausky.com/", "blibaba@foxmail.com"))
+                .build();
+
+    }
+
+}

+ 94 - 0
src/main/java/com/usky/config/mqtt/MqttCofigBean.java

@@ -0,0 +1,94 @@
+package com.usky.config.mqtt;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.stereotype.Component;
+
+@Component
+@PropertySource(value = {"classpath:mqtt-spring.properties"})
+public class MqttCofigBean {
+
+	@Value("${mqtt.username}")
+	private String username;
+
+	@Value("${mqtt.password}")
+	private String password;
+
+	@Value("${mqtt.url}")
+	private String hostUrl;
+
+	@Value("${mqtt.client-id-prefix}")
+	private String clientId;
+
+	@Value("${mqtt.sub-topics}")
+	private String msgTopic;
+
+	@Value("${mqtt.keep-alive-interval}")
+	//心跳间隔
+	private int keepAliveInterval;
+	@Value("${mqtt.completionTimeout}")
+	//心跳间隔
+	private int completionTimeout;
+
+	/**
+	 * 获取用户名
+	 *
+	 * @return
+	 */
+	public String getUsername() {
+		return this.username;
+	}
+
+	/**
+	 * 连接超时
+	 * @return
+	 */
+	public int getCompletionTimeout() {
+		return completionTimeout;
+	}
+
+	/**
+	 * 获取密码
+	 *
+	 * @return
+	 */
+	public String getPassword() {
+		return this.password;
+	}
+
+	/**
+	 * 获取服务器连接地址
+	 *
+	 * @return
+	 */
+	public String getHostUrl() {
+		return this.hostUrl;
+	}
+
+	/**
+	 * 获取客户端ID
+	 *
+	 * @return
+	 */
+	public String getClientId() {
+		return this.clientId;
+	}
+
+	/**
+	 * 获取默认主题
+	 *
+	 * @return
+	 */
+	public String[] getMsgTopic() {
+		String[] topic = msgTopic.split(",");
+		return topic;
+	}
+
+	/***
+	 * 获取连接超时时间
+	 * @return
+	 */
+	public int getKeepAliveInterval() {
+		return keepAliveInterval;
+	}
+}

+ 111 - 0
src/main/java/com/usky/config/mqtt/SpringMqttConfig.java

@@ -0,0 +1,111 @@
+package com.usky.config.mqtt;
+
+import com.usky.utils.UUIDUtils;
+import org.aspectj.weaver.ast.Var;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.integration.annotation.MessagingGateway;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.core.MessageProducer;
+import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
+import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
+import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
+import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
+import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
+import org.springframework.integration.mqtt.support.MqttHeaders;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.messaging.handler.annotation.Header;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.List;
+
+@Configuration
+public class SpringMqttConfig {
+
+    @Autowired
+    public MqttCofigBean mqttCofigBean;
+
+    /*****
+     * 创建MqttPahoClientFactory,设置MQTT Broker连接属性,如果使用SSL验证,也在这里设置。
+     * @return
+     */
+    //  private List topicList = Arrays.asList(mqttCofigBean.getMsgTopic());
+    @Bean
+    public MqttPahoClientFactory mqttClientFactory() {
+        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
+        MqttConnectOptions options = new MqttConnectOptions();
+        options.setServerURIs(new String[]{mqttCofigBean.getHostUrl()});
+        options.setUserName(mqttCofigBean.getUsername());
+        options.setPassword(mqttCofigBean.getPassword().toCharArray());
+        factory.setConnectionOptions(options);
+        return factory;
+    }
+
+    @Bean
+    public MessageChannel mqttInputChannel() {
+        return new DirectChannel();
+    }
+
+    @Bean
+    public MessageProducer inbound() {
+        //创建消息适配器 TODO 这里一定要注意,多端部署时id不能重复
+        MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(UUIDUtils.getUUID(),
+                mqttClientFactory(), mqttCofigBean.getMsgTopic());
+        adapter.setCompletionTimeout(mqttCofigBean.getCompletionTimeout());
+        adapter.setConverter(new DefaultPahoMessageConverter());
+        adapter.setQos(1);
+        adapter.setOutputChannel(mqttInputChannel());
+        return adapter;
+    }
+
+//    @Bean
+//    //ServiceActivator注解表明当前方法用于处理MQTT消息,inputChannel参数指定了用于消费消息的channel。
+//    @ServiceActivator(inputChannel = "mqttInputChannel")
+//    public MessageHandler handler() {
+//        return message -> {
+//            String payload = message.getPayload().toString();
+//            String topic = message.getHeaders().get("mqtt_receivedTopic").toString();
+//            // 根据topic分别进行消息处理。
+//            System.out.println(topic + ": 处理消息 " + payload);
+//
+////            if (Arrays.asList(mqttCofigBean.getMsgTopic()).contains(topic)) {
+////                System.out.println(topic + ": 处理消息 " + payload);
+////            } else {
+////                System.out.println(topic + ": 丢弃消息 " + payload);
+////            }
+//        };
+//    }
+    // 发送消息
+    @Bean
+    public MessageChannel mqttOutboundChannel() {
+        return new DirectChannel();
+    }
+    /*****
+     * 发送消息和消费消息Channel可以使用相同MqttPahoClientFactory
+     * @return
+     */
+    @Bean
+    @ServiceActivator(inputChannel = "mqttOutboundChannel")
+    public MessageHandler outbound() {
+        // 在这里进行mqttOutboundChannel的相关设置
+        MqttPahoMessageHandler messageHandler =
+                new MqttPahoMessageHandler("publishClient", mqttClientFactory());
+        //如果设置成true,发送消息时将不会阻塞。
+        messageHandler.setAsync(true);
+        messageHandler.setDefaultTopic("testTopic");
+        return messageHandler;
+    }
+    @MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
+    public interface MqttGateway {
+        // 定义重载方法,用于消息发送
+        void sendToMqtt(String payload);
+        // 指定topic进行消息发送
+        void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload);
+        void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
+    }
+}

+ 63 - 0
src/main/java/com/usky/config/redis/RedisConfig.java

@@ -0,0 +1,63 @@
+package com.usky.config.redis;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import javax.annotation.Resource;
+
+/**
+* 开启缓存支持
+* @author zyf
+ * @Return:
+*/
+@Slf4j
+@EnableCaching
+@Configuration
+public class RedisConfig {
+
+	@Resource
+	private LettuceConnectionFactory lettuceConnectionFactory;
+
+	/**
+	 * RedisTemplate配置
+	 * @param lettuceConnectionFactory
+	 * @return
+	 */
+	@Bean
+	public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
+		log.info(" --- redis config init --- ");
+		Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =jacksonSerializer();
+		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
+		redisTemplate.setConnectionFactory(lettuceConnectionFactory);
+		RedisSerializer<?> stringSerializer = new StringRedisSerializer();
+		// key序列化
+		redisTemplate.setKeySerializer(stringSerializer);
+		// value序列化
+		redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
+		// Hash key序列化
+		redisTemplate.setHashKeySerializer(stringSerializer);
+		// Hash value序列化
+		redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
+		redisTemplate.afterPropertiesSet();
+		return redisTemplate;
+	}
+	private Jackson2JsonRedisSerializer jacksonSerializer() {
+		Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
+		ObjectMapper objectMapper = new ObjectMapper();
+		objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+		objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
+		jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
+		return jackson2JsonRedisSerializer;
+	}
+
+}

+ 69 - 0
src/main/java/com/usky/config/shiro/MyRealm.java

@@ -0,0 +1,69 @@
+package com.usky.config.shiro;
+
+
+import org.apache.shiro.authc.*;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.util.ByteSource;
+import org.apache.shiro.util.SimpleByteSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+
+import java.util.Set;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/11/3 10:32
+ * @description 自定义Realm
+ **/
+
+//public class MyRealm extends AuthorizingRealm {
+//    Logger logger = LoggerFactory.getLogger(MyRealm.class);
+//    @Autowired
+//    @Lazy
+//    private UserService userServiceImpl;
+//
+//    /*
+//     * 授权逻辑
+//     * */
+//    @Override
+//    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
+//        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
+//        //获取用户名
+//        TbUserEntity userEntity = (TbUserEntity) principalCollection.getPrimaryPrincipal();//(认证时构造)
+//        String username = userEntity.getUserName();
+//        //从数据库查询用户的权限和角色
+//        //角色查询
+//        Set<String> roles = userServiceImpl.queryUseRoleByUserName(username);
+//        if (roles != null && roles.size() > 0) {
+//            simpleAuthorizationInfo.addRoles(roles);
+//        }
+//        //权限查询
+//        Set<String> permissions = userServiceImpl.findPermissionsByUsername(username);
+//        if (permissions != null && permissions.size() > 0) {
+//            simpleAuthorizationInfo.addStringPermissions(permissions);
+//        }
+//        return simpleAuthorizationInfo;
+//    }
+//
+//    /*
+//     * 认证逻辑
+//     * */
+//    @Override
+//    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
+//        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
+//        String username = token.getUsername();
+//        TbUserEntity userEntity = userServiceImpl.queryUserByUserName(username);
+//        if (userEntity == null) {
+//            return null;
+//        }
+//        ByteSource byteSource = new SimpleByteSource(userEntity.getPrivateSalt());
+//        AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userEntity, userEntity.getPassword(), byteSource, getName());
+//        return authenticationInfo;
+//    }
+//}

+ 100 - 0
src/main/java/com/usky/config/shiro/ShiroConfig.java

@@ -0,0 +1,100 @@
+package com.usky.config.shiro;
+
+import org.apache.shiro.authc.credential.CredentialsMatcher;
+import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
+import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
+import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/11/3 9:20
+ * @description TODO
+ **/
+
+
+//@Configuration
+//public class ShiroConfig {
+//    //创建ShiroFilterFactoryBean
+//    @Bean
+//    public ShiroFilterFactoryBean getShiroFilterFactoryBean() {
+//        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
+//        //设置安全管理器
+//        shiroFilterFactoryBean.setSecurityManager(securityManager());
+//        /*
+//        anon 无需认证访问
+//        authc 认证访问
+//        perms 授权访问
+//        role 角色授权访问
+//         */
+//        Map<String, String> hashMap = new LinkedHashMap<>();
+//        shiroFilterFactoryBean.setLoginUrl("/");
+//        hashMap.put("/login/toLogin", "anon");
+//        hashMap.put("/", "anon");
+//        //swagger2
+//        hashMap.put("/swagger-ui.html", "anon");
+//        hashMap.put("/swagger-resources", "anon");
+//        hashMap.put("/swagger-resources/configuration/security", "anon");
+//        hashMap.put("/swagger-resources/configuration/ui", "anon");
+//        hashMap.put("/v2/api-docs", "anon");
+//        hashMap.put("/webjars/springfox-swagger-ui/**", "anon");
+//        //  hashMap.put("/user/queryUserAll", "anon");
+//        //  hashMap.put("/*", "authc");
+//        hashMap.put("/login/loginOut", "logout");
+//        shiroFilterFactoryBean.setFilterChainDefinitionMap(hashMap);
+//        return shiroFilterFactoryBean;
+//    }
+//    //创建DefaultWebSecurityManager
+//    @Bean("SecurityManager")
+//    public SecurityManager securityManager() {
+//        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
+//        //     DefaultWebSecurityManager defaultWebSecurityManager = SpringUtil.getBean(DefaultWebSecurityManager.class);
+//        //关联Reaml
+//        // ApplicationContext().getBean()
+//        defaultWebSecurityManager.setRealm(getRealm());
+//        return defaultWebSecurityManager;
+//    }
+//    @Bean(name = "Realm")
+//    @Lazy
+//    public Realm getRealm() {
+//        MyRealm myRealm = new MyRealm();
+//        //设置密码匹配器
+//        myRealm.setCredentialsMatcher(credentialsMatcher());
+//        return myRealm;
+//    }
+//    //创建密码匹配器
+//    @Bean
+//    public CredentialsMatcher credentialsMatcher() {
+//        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
+//        hashedCredentialsMatcher.setHashAlgorithmName("md5");
+//        hashedCredentialsMatcher.setHashIterations(1);
+//        return hashedCredentialsMatcher;
+//    }
+//    /**
+//     * 注解支持:
+//     */
+//    @Bean
+//    @ConditionalOnMissingBean
+//    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
+//        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
+//        defaultAAP.setProxyTargetClass(true);
+//        return defaultAAP;
+//    }
+//    @Bean
+//    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
+//        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
+//        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
+//        return authorizationAttributeSourceAdvisor;
+//    }
+//}

+ 22 - 0
src/main/java/com/usky/config/sms/SmsConfig.java

@@ -0,0 +1,22 @@
+package com.usky.config.sms;
+
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.profile.DefaultProfile;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+
+@Configuration
+@EnableConfigurationProperties(SmsProperties.class)
+public class SmsConfig {
+
+    @Bean
+    public IAcsClient acsClient(SmsProperties prop){
+        // 发送短信的客户端
+        DefaultProfile profile = DefaultProfile.getProfile(
+                prop.getRegionID(), prop.getAccessKeyID(), prop.getAccessKeySecret());
+        return new DefaultAcsClient(profile);
+    }
+}

+ 24 - 0
src/main/java/com/usky/config/sms/SmsConstants.java

@@ -0,0 +1,24 @@
+package com.usky.config.sms;
+
+
+public final class SmsConstants {
+    /**
+     * 请求参数
+     */
+    public static final String SMS_PARAM_REGION_ID = "RegionId";
+    public static final String SMS_PARAM_KEY_PHONE = "PhoneNumbers";
+    public static final String SMS_PARAM_KEY_SIGN_NAME = "SignName";
+    public static final String SMS_PARAM_KEY_TEMPLATE_CODE = "TemplateCode";
+    public static final String SMS_PARAM_KEY_TEMPLATE_PARAM= "TemplateParam";
+
+    /**
+     * 响应结果
+     */
+    public static final String SMS_RESPONSE_KEY_CODE = "Code";
+    public static final String SMS_RESPONSE_KEY_MESSAGE = "Message";
+
+    /**
+     * 状态
+     */
+    public static final String OK = "OK";
+}

+ 42 - 0
src/main/java/com/usky/config/sms/SmsProperties.java

@@ -0,0 +1,42 @@
+package com.usky.config.sms;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@ConfigurationProperties(prefix = "sms")
+public class SmsProperties {
+    /**
+     * 账号
+     */
+    String accessKeyID;
+    /**
+     * 密钥
+     */
+    String accessKeySecret;
+    /**
+     * 短信签名
+     */
+    String signName;
+    /**
+     * 短信模板
+     */
+    String verifyCodeTemplate;
+    /**
+     * 发送短信请求的域名
+     */
+    String domain;
+    /**
+     * API版本
+     */
+    String version;
+    /**
+     * API类型
+     */
+    String action;
+    /**
+     * 区域
+     */
+    String regionID;
+}

+ 84 - 0
src/main/java/com/usky/constant/CommonConstant.java

@@ -0,0 +1,84 @@
+package com.usky.constant;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public interface CommonConstant {
+
+	/**
+	 * 正常状态
+	 */
+	public static final Integer STATUS_NORMAL = 0;
+
+	/**
+	 * 禁用状态
+	 */
+	public static final Integer STATUS_DISABLE = -1;
+
+	/**
+	 * 删除标志
+	 */
+	public static final Integer DEL_FLAG_1 = 1;
+
+	/**
+	 * 未删除
+	 */
+	public static final Integer DEL_FLAG_0 = 0;
+
+	/**
+	 * 系统日志类型: 登录
+	 */
+	public static final int LOG_TYPE_1 = 1;
+	
+	/**
+	 * 系统日志类型: 操作
+	 */
+	public static final int LOG_TYPE_2 = 2;
+
+	/**
+	 * 操作日志类型: 查询
+	 */
+	public static final int OPERATE_TYPE_1 = 1;
+	
+	/**
+	 * 操作日志类型: 添加
+	 */
+	public static final int OPERATE_TYPE_2 = 2;
+	
+	/**
+	 * 操作日志类型: 更新
+	 */
+	public static final int OPERATE_TYPE_3 = 3;
+	
+	/**
+	 * 操作日志类型: 删除
+	 */
+	public static final int OPERATE_TYPE_4 = 4;
+	
+	/**
+	 * 操作日志类型: 导入
+	 */
+	public static final int OPERATE_TYPE_5 = 5;
+	
+	/**
+	 * 操作日志类型: 导出
+	 */
+	public static final int OPERATE_TYPE_6 = 6;
+	
+	
+	/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
+    public static final Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
+    /** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
+    public static final Integer SC_OK_200 = 200;
+
+	/**
+	 * 首页天气地址
+	 */
+
+	public static final String TOP_WEATHER_HOST  = "https://weather01.market.alicloudapi.com";
+	public static final String TOP_WEATHER_PATH  = "/area-to-weather";
+	public static final String TOP_WEATHER_METHOD  = "GET";
+	public static final String TOP_WEATHER_APPCODE  = "0f2b7fce6e104ba8835358b7b59b4fb6";
+
+
+}

+ 210 - 0
src/main/java/com/usky/controller/build/BuildController.java

@@ -0,0 +1,210 @@
+package com.usky.controller.build;
+
+import com.usky.entity.build.TbBuildingVoEntity;
+import com.usky.entity.build.TbFloorVoEntity;
+import com.usky.entity.build.TbRoomVoEntity;
+import com.usky.service.build.BuildService;
+import com.usky.utils.Page;
+import com.usky.utils.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/5/24 09:20
+ * @description TODO
+ **/
+@RequestMapping("build")
+@Api(tags = "建筑管理")
+@Controller
+@Slf4j
+public class BuildController {
+    @Autowired
+    private BuildService buildServiceIpml;
+
+    @RequestMapping(value = "addBuild", method = {RequestMethod.POST})
+    @ResponseBody
+    @ApiOperation(value = "建筑新增")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "buildName", value = "建筑名称", required = true, paramType = "query"),
+            @ApiImplicitParam(name = "buildAddr", value = "地址", required = true, paramType = "query"),
+            @ApiImplicitParam(name = "latitude", value = "维度", required = true, paramType = "query"),
+            @ApiImplicitParam(name = "longitude", value = "经度", required = true, paramType = "query")
+    })
+    public Result addBuild(@RequestParam("buildName") String buildName,
+                           @RequestParam("buildAddr") String buildAddr,
+                           @RequestParam("latitude") String latitude,
+                           @RequestParam("longitude") String longitude
+    ) {
+        return buildServiceIpml.addBuild(buildName, buildAddr, latitude, longitude);
+    }
+
+    @RequestMapping(value = "editBuild", method = {RequestMethod.POST})
+    @ResponseBody
+    @ApiOperation(value = "建筑信息修改")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "buildName", value = "建筑名称", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "buildAddr", value = "地址", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "latitude", value = "维度", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "id", value = "主键id", required = true, paramType = "query"),
+            @ApiImplicitParam(name = "status", value = "状态 0 删除", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "longitude", value = "经度", required = false, paramType = "query")
+    })
+    public Result editBuild(@RequestParam(value = "buildName", required = false) String buildName,
+                              @RequestParam(value = "buildAddr", required = false) String buildAddr,
+                              @RequestParam(value = "latitude", required = false) String latitude,
+                              @RequestParam(value = "status", required = false) String status,
+                              @RequestParam(value = "id", required = true) String id,
+                              @RequestParam(value = "longitude", required = false) String longitude
+    ) {
+        return buildServiceIpml.updateBuild(buildName, buildAddr, latitude, longitude, id, status);
+    }
+
+    @RequestMapping(value = "listBuild", method = {RequestMethod.GET})
+    @ResponseBody
+    @ApiOperation(value = "建筑信息查询")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "buildName", value = "建筑名称", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "buildAddr", value = "地址", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "latitude", value = "维度", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "pageNo", value = "当前页,默认1", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "页数据条数20", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "longitude", value = "经度", required = false, paramType = "query")
+    })
+    public Page<TbBuildingVoEntity> listBuild(@RequestParam(value = "buildName", required = false) String buildName,
+                                               @RequestParam(value = "buildAddr", required = false) String buildAddr,
+                                               @RequestParam(value = "latitude", required = false) String latitude,
+                                               @RequestParam(value = "pageNo", required = false, defaultValue = "1") String PageNo,
+                                               @RequestParam(value = "pageSize", required = false, defaultValue = "20") String pageSize,
+                                               @RequestParam(value = "longitude", required = false) String longitude
+    ) {
+        return buildServiceIpml.listBuild(buildName, buildAddr, latitude, longitude, PageNo, pageSize);
+    }
+
+
+    @RequestMapping(value = "addFloor", method = {RequestMethod.POST})
+    @ResponseBody
+    @ApiOperation(value = "楼层新增")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "floorName", value = "楼层名称", required = true, paramType = "query"),
+            @ApiImplicitParam(name = "floorBuildUuid", value = "建筑唯一编码", required = true, paramType = "query")
+    })
+    public Result addFloor(@RequestParam("floorName") String floorName,
+                           @RequestParam("floorBuildUuid") String floorBuildUuid
+    ) {
+        return buildServiceIpml.addFloor(floorName, floorBuildUuid);
+    }
+
+    @RequestMapping(value = "editFloor", method = {RequestMethod.POST})
+    @ResponseBody
+    @ApiOperation(value = "楼层信息修改")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "floorName", value = "楼层名称", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "floorBuildUuid", value = "所属建筑", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "id", value = "主键id", required = true, paramType = "query"),
+            @ApiImplicitParam(name = "status", value = "状态 0 删除", required = false, paramType = "query"),
+    })
+    public Result editFloor(@RequestParam(value = "floorName", required = false) String floorName,
+                              @RequestParam(value = "floorBuildUuid", required = false) String floorBuildUuid,
+                              @RequestParam(value = "id", required = false) String id,
+                              @RequestParam(value = "status", required = false) String status
+    ) {
+        return buildServiceIpml.updateFloor(floorName, floorBuildUuid, id, status);
+    }
+
+    @RequestMapping(value = "listFloor", method = {RequestMethod.GET})
+    @ResponseBody
+    @ApiOperation(value = "楼层信息查询")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "floorName", value = "楼层名称", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "floorBuildUuid", value = "所属建筑唯一编码", required = false, paramType = "query")
+    })
+    public Page<TbFloorVoEntity> listFloor(@RequestParam(value = "floorName", required = false) String floorName,
+                                            @RequestParam(value = "floorBuildUuid", required = false) String floorBuildUuid,
+                                            @RequestParam(value = "pageNo", required = false, defaultValue = "1") String PageNo,
+                                            @RequestParam(value = "pageSize", required = false, defaultValue = "20") String pageSize
+    ) {
+        return buildServiceIpml.queryFloor(floorName, floorBuildUuid, PageNo, pageSize);
+    }
+
+
+    @RequestMapping(value = "addRoom", method = {RequestMethod.POST})
+    @ResponseBody
+    @ApiOperation(value = "房间新增")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roomName", value = "房间名称", required = true, paramType = "query"),
+            @ApiImplicitParam(name = "roomFloorUuid", value = "楼层唯一编码", required = true, paramType = "query")
+    })
+    public Result addRoom(@RequestParam(value = "roomName", required = true) String roomName,
+                          @RequestParam(value = "roomFloorUuid", required = true) String roomFloorUuid
+    ) {
+        return buildServiceIpml.addRoom(roomName, roomFloorUuid);
+    }
+
+    @RequestMapping(value = "editRoom", method = {RequestMethod.POST})
+    @ResponseBody
+    @ApiOperation(value = "房间信息修改")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roomName", value = "房间名称", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "roomFloorUuid", value = "所属楼层", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "id", value = "主键id", required = true, paramType = "query"),
+            @ApiImplicitParam(name = "status", value = "状态 0 删除", required = false, paramType = "query"),
+    })
+    public Result editRoom(@RequestParam(value = "roomName", required = false) String RoomName,
+                             @RequestParam(value = "roomFloorUuid", required = false) String roomFloorUuid,
+                             @RequestParam(value = "id", required = false) String id,
+                             @RequestParam(value = "status", required = false) String status
+    ) {
+        return buildServiceIpml.updateRoom(RoomName, roomFloorUuid, id, status);
+    }
+
+    @RequestMapping(value = "listRoom", method = {RequestMethod.GET})
+    @ResponseBody
+    @ApiOperation(value = "房间信息查询")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roomName", value = "房间名称", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "roomFloorUuid", value = "所属楼层唯一编码", required = false, paramType = "query")
+    })
+    public Page<TbRoomVoEntity> listRoom(@RequestParam(value = "roomName", required = false) String roomName,
+                                          @RequestParam(value = "roomFloorUuid", required = false) String roomFloorUuid,
+                                          @RequestParam(value = "pageNo", required = false, defaultValue = "1") String PageNo,
+                                          @RequestParam(value = "pageSize", required = false, defaultValue = "20") String pageSize
+    ) {
+
+
+        return buildServiceIpml.queryRoom(roomName, roomFloorUuid, PageNo, pageSize);
+    }
+
+    @RequestMapping(value = "listBuildFloorRoom", method = {RequestMethod.GET})
+    @ResponseBody
+    @ApiOperation(value = "建筑下拉框")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roomName", value = "房间名称", required = false, paramType = "query"),
+            @ApiImplicitParam(name = "roomFloorUuid", value = "所属楼层唯一编码", required = false, paramType = "query")
+    })
+    public Page<TbRoomVoEntity> listBuildFloorRoom(@RequestParam(value = "roomName", required = false) String roomName,
+                                         @RequestParam(value = "roomFloorUuid", required = false) String roomFloorUuid,
+                                         @RequestParam(value = "pageNo", required = false, defaultValue = "1") String PageNo,
+                                         @RequestParam(value = "pageSize", required = false, defaultValue = "20") String pageSize
+    ) {
+
+
+        return buildServiceIpml.queryRoom(roomName, roomFloorUuid, PageNo, pageSize);
+    }
+
+
+
+
+
+}

+ 38 - 0
src/main/java/com/usky/controller/mqtt/MqttController.java

@@ -0,0 +1,38 @@
+package com.usky.controller.mqtt;
+
+import com.usky.config.mqtt.SpringMqttConfig;
+import com.usky.utils.RedisUtil;
+import io.swagger.annotations.Api;
+import io.swagger.models.auth.In;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/6/21 11:40
+ * @description TODO
+ **/
+@RestController
+@Api(tags = "MQTT测试")
+@Slf4j
+public class MqttController {
+    @Autowired
+    private RedisUtil redisUtil;
+    @Autowired
+    private SpringMqttConfig.MqttGateway mqttGateway;
+    @Autowired
+    private RedisTemplate<String, String> redisTemplate;
+
+    @PostMapping("/send/{topic}/{message}")
+    public String send(@PathVariable String topic, @PathVariable String message) {
+        // 发送消息到指定topic
+        mqttGateway.sendToMqtt(topic, message);
+        return "send message : " + message;
+    }
+}

+ 63 - 0
src/main/java/com/usky/controller/mqtt/MqttHandler.java

@@ -0,0 +1,63 @@
+package com.usky.controller.mqtt;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.usky.utils.RedisUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.stereotype.Component;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/6/24 11:01
+ * @description TODO
+ **/
+@Component
+@Slf4j
+public class MqttHandler {
+    @Autowired
+    private RedisUtil redisUtil;
+
+    @Bean
+    //ServiceActivator注解表明当前方法用于处理MQTT消息,inputChannel参数指定了用于消费消息的channel。
+    @ServiceActivator(inputChannel = "mqttInputChannel")
+    public MessageHandler handler() {
+        return message -> {
+            //获取实时数据
+            String payload = "";
+            String s = message.getPayload().toString();
+            byte[] bytes = s.getBytes();
+            try {
+                payload = new String(s.getBytes("gbk"),"utf-8");
+            } catch (UnsupportedEncodingException e) {
+                log.error("【MQTT】payload字符编码转换错误{}", s, e);
+                e.printStackTrace();
+            }
+
+            log.info("【MQTT】原始数据: {}", payload);
+            Map<String, Object> dataMap = JSON.parseObject(payload, Map.class);
+            if (dataMap.containsKey("sn")) {
+                String sn = (String) dataMap.get("sn");
+                List<JSONObject> devsList = (List) dataMap.get("devs");
+                String devName = null;
+                for (JSONObject jsonObject : devsList) {
+                    devName = (String) jsonObject.get("dev");
+                }
+                redisUtil.hmset(sn + "#" + devName, dataMap);
+            }
+            String topic = message.getHeaders().get("mqtt_receivedTopic").toString();
+            log.info(topic + ": 处理消息 " + payload);
+
+        };
+    }
+
+}

+ 13 - 0
src/main/java/com/usky/dao/BaseDao.java

@@ -0,0 +1,13 @@
+package com.usky.dao;
+
+import org.hibernate.Session;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/11/2 17:03
+ * @description TODO
+ **/
+public interface BaseDao {
+    public Session getSession();
+}

+ 27 - 0
src/main/java/com/usky/dao/impl/BaseDaoImpl.java

@@ -0,0 +1,27 @@
+package com.usky.dao.impl;
+
+import com.usky.dao.BaseDao;
+import org.hibernate.Session;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.orm.hibernate5.HibernateTransactionManager;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/11/2 17:04
+ * @description TODO
+ **/
+@Repository
+@Transactional
+public class BaseDaoImpl implements BaseDao {
+    @Autowired
+    private HibernateTransactionManager hibernateTransactionManager;
+
+    @Override
+    public Session getSession() {
+        Session currentSession = hibernateTransactionManager.getSessionFactory().getCurrentSession();
+        return currentSession;
+    }
+}

+ 42 - 0
src/main/java/com/usky/emu/ResponeCodeEnum.java

@@ -0,0 +1,42 @@
+package com.usky.emu;
+
+/**
+ * @author laowo
+ * @version 1.0.0
+ * @ClassName ResponeCodeEnum.java
+ * @Description TODO
+ * @createTime 2021/01/11/ 13:09:00
+ */
+public enum ResponeCodeEnum {
+    /**
+     * 枚举类
+     */
+    SUCCESS(true, "0000", "操作成功!"),
+
+    FAIL(false, "0001", "操作失败!"),
+
+    ERROR(false, "0002", "系统错误!");
+    private Boolean flag; // 标记Code
+
+    private String responseCode;
+
+    private String comment;
+
+    ResponeCodeEnum(Boolean flag, String responseCode, String comment) {
+        this.flag = flag;
+        this.responseCode = responseCode;
+        this.comment = comment;
+    }
+
+    public Boolean getFlag() {
+        return flag;
+    }
+
+    public String getResponseCode() {
+        return responseCode;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+}

+ 117 - 0
src/main/java/com/usky/entity/build/TbBuildingEntity.java

@@ -0,0 +1,117 @@
+package com.usky.entity.build;
+
+import javax.persistence.*;
+import java.sql.Timestamp;
+import java.util.Objects;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/5/24 09:41
+ * @description TODO
+ **/
+@Entity
+@Table(name = "tb_building", schema = "gx_Security", catalog = "")
+public class TbBuildingEntity {
+    private int id;
+    private String buildUuid;
+    private String buildName;
+    private String buildAddr;
+    private String latitude;
+    private String longitude;
+    private int status;
+    private Timestamp creatTime;
+
+    @Id
+    @Column(name = "id")
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    @Basic
+    @Column(name = "build_uuid")
+    public String getBuildUuid() {
+        return buildUuid;
+    }
+
+    public void setBuildUuid(String buildUuid) {
+        this.buildUuid = buildUuid;
+    }
+
+    @Basic
+    @Column(name = "build_name")
+    public String getBuildName() {
+        return buildName;
+    }
+
+    public void setBuildName(String buildName) {
+        this.buildName = buildName;
+    }
+
+    @Basic
+    @Column(name = "build_addr")
+    public String getBuildAddr() {
+        return buildAddr;
+    }
+
+    public void setBuildAddr(String buildAddr) {
+        this.buildAddr = buildAddr;
+    }
+
+    @Basic
+    @Column(name = "latitude")
+    public String getLatitude() {
+        return latitude;
+    }
+
+    public void setLatitude(String latitude) {
+        this.latitude = latitude;
+    }
+
+    @Basic
+    @Column(name = "longitude")
+    public String getLongitude() {
+        return longitude;
+    }
+
+    public void setLongitude(String longitude) {
+        this.longitude = longitude;
+    }
+
+    @Basic
+    @Column(name = "status")
+    public int getStatus() {
+        return status;
+    }
+
+    public void setStatus(int status) {
+        this.status = status;
+    }
+
+    @Basic
+    @Column(name = "creat_time")
+    public Timestamp getCreatTime() {
+        return creatTime;
+    }
+
+    public void setCreatTime(Timestamp creatTime) {
+        this.creatTime = creatTime;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        TbBuildingEntity that = (TbBuildingEntity) o;
+        return id == that.id && status == that.status && Objects.equals(buildUuid, that.buildUuid) && Objects.equals(buildName, that.buildName) && Objects.equals(buildAddr, that.buildAddr) && Objects.equals(latitude, that.latitude) && Objects.equals(longitude, that.longitude) && Objects.equals(creatTime, that.creatTime);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, buildUuid, buildName, buildAddr, latitude, longitude, status, creatTime);
+    }
+}

+ 36 - 0
src/main/java/com/usky/entity/build/TbBuildingVoEntity.java

@@ -0,0 +1,36 @@
+package com.usky.entity.build;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.sql.Timestamp;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/5/24 09:41
+ * @description TODO
+ **/
+@Data
+@ApiModel("建筑Vo")
+public class TbBuildingVoEntity {
+    @ApiModelProperty("主键id")
+    private int id;
+    @ApiModelProperty("建筑唯一编码")
+    private String buildUuid;
+    @ApiModelProperty("建筑名称")
+    private String buildName;
+    @ApiModelProperty("地址")
+    private String buildAddr;
+    @ApiModelProperty("维度")
+    private String latitude;
+    @ApiModelProperty("经度")
+    private String longitude;
+    @ApiModelProperty("0未删除 1 已删除")
+    private int status;
+    @ApiModelProperty("添加时间")
+    private Timestamp creatTime;
+
+
+}

+ 96 - 0
src/main/java/com/usky/entity/build/TbFloorEntity.java

@@ -0,0 +1,96 @@
+package com.usky.entity.build;
+
+import javax.persistence.*;
+import java.sql.Timestamp;
+import java.util.Objects;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/5/24 09:41
+ * @description TODO
+ **/
+@Entity
+@Table(name = "tb_floor", schema = "gx_Security", catalog = "")
+public class TbFloorEntity {
+    private int id;
+    private String floorUuid;
+    private String floorBuildUuid;
+    private String floorName;
+    private int status;
+    private Timestamp creatTime;
+
+    @Id
+    @Column(name = "id")
+    @GeneratedValue(strategy=GenerationType.IDENTITY)
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    @Basic
+    @Column(name = "floor_uuid")
+    public String getFloorUuid() {
+        return floorUuid;
+    }
+
+    public void setFloorUuid(String floorUuid) {
+        this.floorUuid = floorUuid;
+    }
+
+    @Basic
+    @Column(name = "floor_build_uuid")
+    public String getFloorBuildUuid() {
+        return floorBuildUuid;
+    }
+
+    public void setFloorBuildUuid(String floorBuildUuid) {
+        this.floorBuildUuid = floorBuildUuid;
+    }
+
+    @Basic
+    @Column(name = "floor_name")
+    public String getFloorName() {
+        return floorName;
+    }
+
+    public void setFloorName(String floorName) {
+        this.floorName = floorName;
+    }
+
+    @Basic
+    @Column(name = "status")
+    public int getStatus() {
+        return status;
+    }
+
+    public void setStatus(int status) {
+        this.status = status;
+    }
+
+    @Basic
+    @Column(name = "creat_time")
+    public Timestamp getCreatTime() {
+        return creatTime;
+    }
+
+    public void setCreatTime(Timestamp creatTime) {
+        this.creatTime = creatTime;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        TbFloorEntity that = (TbFloorEntity) o;
+        return id == that.id && status == that.status && Objects.equals(floorUuid, that.floorUuid) && Objects.equals(floorBuildUuid, that.floorBuildUuid) && Objects.equals(floorName, that.floorName) && Objects.equals(creatTime, that.creatTime);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, floorUuid, floorBuildUuid, floorName, status, creatTime);
+    }
+}

+ 33 - 0
src/main/java/com/usky/entity/build/TbFloorVoEntity.java

@@ -0,0 +1,33 @@
+package com.usky.entity.build;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.sql.Timestamp;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/5/24 09:41
+ * @description TODO
+ **/
+@Data
+@ApiModel("楼层VO")
+public class TbFloorVoEntity {
+    @ApiModelProperty("主键id")
+    private int id;
+    @ApiModelProperty("楼层唯一编码")
+    private String floorUuid;
+    @ApiModelProperty("建筑唯一编码")
+    private String floorBuildUuid;
+    @ApiModelProperty("建筑名称")
+    private String buildName;
+    @ApiModelProperty("楼层唯一编码")
+    private String floorName;
+    @ApiModelProperty("0 未删除 1 已删除")
+    private int status;
+    @ApiModelProperty("添加时间")
+    private Timestamp creatTime;
+
+}

+ 95 - 0
src/main/java/com/usky/entity/build/TbRoomEntity.java

@@ -0,0 +1,95 @@
+package com.usky.entity.build;
+
+import javax.persistence.*;
+import java.sql.Timestamp;
+import java.util.Objects;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/5/24 09:42
+ * @description TODO
+ **/
+@Entity
+@Table(name = "tb_room", schema = "gx_Security", catalog = "")
+public class TbRoomEntity {
+    private int id;
+    private String roomUuid;
+    private String roomFloorUuid;
+    private String roomName;
+    private Timestamp creatTime;
+    private Integer status;
+
+    @Id
+    @Column(name = "id")
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    @Basic
+    @Column(name = "room_uuid")
+    public String getRoomUuid() {
+        return roomUuid;
+    }
+
+    public void setRoomUuid(String roomUuid) {
+        this.roomUuid = roomUuid;
+    }
+
+    @Basic
+    @Column(name = "room_floor_uuid")
+    public String getRoomFloorUuid() {
+        return roomFloorUuid;
+    }
+
+    public void setRoomFloorUuid(String roomFloorUuid) {
+        this.roomFloorUuid = roomFloorUuid;
+    }
+
+    @Basic
+    @Column(name = "room_name")
+    public String getRoomName() {
+        return roomName;
+    }
+
+    public void setRoomName(String roomName) {
+        this.roomName = roomName;
+    }
+
+    @Basic
+    @Column(name = "creat_time")
+    public Timestamp getCreatTime() {
+        return creatTime;
+    }
+
+    public void setCreatTime(Timestamp creatTime) {
+        this.creatTime = creatTime;
+    }
+
+    @Basic
+    @Column(name = "status")
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        TbRoomEntity that = (TbRoomEntity) o;
+        return id == that.id && Objects.equals(roomUuid, that.roomUuid) && Objects.equals(roomFloorUuid, that.roomFloorUuid) && Objects.equals(roomName, that.roomName) && Objects.equals(creatTime, that.creatTime) && Objects.equals(status, that.status);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, roomUuid, roomFloorUuid, roomName, creatTime, status);
+    }
+}

+ 33 - 0
src/main/java/com/usky/entity/build/TbRoomVoEntity.java

@@ -0,0 +1,33 @@
+package com.usky.entity.build;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.sql.Timestamp;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/5/24 09:42
+ * @description TODO
+ **/
+@Data
+@ApiModel("房间VO")
+public class TbRoomVoEntity {
+    @ApiModelProperty("主键id")
+    private int id;
+    @ApiModelProperty("房间唯一编码")
+    private String roomUuid;
+    @ApiModelProperty("楼层唯一编码")
+    private String roomFloorUuid;
+    @ApiModelProperty("房间名称")
+    private String roomName;
+    @ApiModelProperty("楼层名称")
+    private String floorName;
+    @ApiModelProperty("添加时间")
+    private Timestamp creatTime;
+    @ApiModelProperty("0 未删除 1 已删除")
+    private Integer status;
+
+
+}

+ 206 - 0
src/main/java/com/usky/entity/sys/SysLogDTO.java

@@ -0,0 +1,206 @@
+package com.usky.entity.sys;
+
+import javax.persistence.*;
+import java.sql.Timestamp;
+import java.util.Objects;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/7/14 13:57
+ * @description 系统日志DTO
+ **/
+@Entity
+@Table(name = "sys_log", schema = "sd_party_school", catalog = "")
+public class SysLogDTO {
+    private long id;
+    private Integer logType;
+    private String logContent;
+    private Integer operateType;
+    private String userid;
+    private String username;
+    private String ip;
+    private String method;
+    private String requestUrl;
+    private String requestParam;
+    private String requestType;
+    private Long costTime;
+    private String createBy;
+    private Timestamp createTime;
+    private String updateBy;
+    private Timestamp updateTime;
+
+
+    @Id
+    @Column(name = "id")
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    @Basic
+    @Column(name = "log_type", nullable = true)
+    public Integer getLogType() {
+        return logType;
+    }
+
+    public void setLogType(Integer logType) {
+        this.logType = logType;
+    }
+
+    @Basic
+    @Column(name = "log_content", nullable = true, length = 1000)
+    public String getLogContent() {
+        return logContent;
+    }
+
+    public void setLogContent(String logContent) {
+        this.logContent = logContent;
+    }
+
+    @Basic
+    @Column(name = "operate_type", nullable = true)
+    public Integer getOperateType() {
+        return operateType;
+    }
+
+    public void setOperateType(Integer operateType) {
+        this.operateType = operateType;
+    }
+
+    @Basic
+    @Column(name = "userid", nullable = true, length = 32)
+    public String getUserid() {
+        return userid;
+    }
+
+    public void setUserid(String userid) {
+        this.userid = userid;
+    }
+
+    @Basic
+    @Column(name = "username", nullable = true, length = 100)
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    @Basic
+    @Column(name = "ip", nullable = true, length = 100)
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+
+    @Basic
+    @Column(name = "method", nullable = true, length = 500)
+    public String getMethod() {
+        return method;
+    }
+
+    public void setMethod(String method) {
+        this.method = method;
+    }
+
+    @Basic
+    @Column(name = "request_url", nullable = true, length = 255)
+    public String getRequestUrl() {
+        return requestUrl;
+    }
+
+    public void setRequestUrl(String requestUrl) {
+        this.requestUrl = requestUrl;
+    }
+
+    @Basic
+    @Column(name = "request_param", nullable = true, length = -1)
+    public String getRequestParam() {
+        return requestParam;
+    }
+
+    public void setRequestParam(String requestParam) {
+        this.requestParam = requestParam;
+    }
+
+    @Basic
+    @Column(name = "request_type", nullable = true, length = 10)
+    public String getRequestType() {
+        return requestType;
+    }
+
+    public void setRequestType(String requestType) {
+        this.requestType = requestType;
+    }
+
+    @Basic
+    @Column(name = "cost_time", nullable = true)
+    public Long getCostTime() {
+        return costTime;
+    }
+
+    public void setCostTime(Long costTime) {
+        this.costTime = costTime;
+    }
+
+    @Basic
+    @Column(name = "create_by", nullable = true, length = 32)
+    public String getCreateBy() {
+        return createBy;
+    }
+
+    public void setCreateBy(String createBy) {
+        this.createBy = createBy;
+    }
+
+    @Basic
+    @Column(name = "create_time", nullable = true)
+    public Timestamp getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Timestamp createTime) {
+        this.createTime = createTime;
+    }
+
+    @Basic
+    @Column(name = "update_by", nullable = true, length = 32)
+    public String getUpdateBy() {
+        return updateBy;
+    }
+
+    public void setUpdateBy(String updateBy) {
+        this.updateBy = updateBy;
+    }
+
+    @Basic
+    @Column(name = "update_time", nullable = true)
+    public Timestamp getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Timestamp updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SysLogDTO sysLog = (SysLogDTO) o;
+        return Objects.equals(id, sysLog.id) && Objects.equals(logType, sysLog.logType) && Objects.equals(logContent, sysLog.logContent) && Objects.equals(operateType, sysLog.operateType) && Objects.equals(userid, sysLog.userid) && Objects.equals(username, sysLog.username) && Objects.equals(ip, sysLog.ip) && Objects.equals(method, sysLog.method) && Objects.equals(requestUrl, sysLog.requestUrl) && Objects.equals(requestParam, sysLog.requestParam) && Objects.equals(requestType, sysLog.requestType) && Objects.equals(costTime, sysLog.costTime) && Objects.equals(createBy, sysLog.createBy) && Objects.equals(createTime, sysLog.createTime) && Objects.equals(updateBy, sysLog.updateBy) && Objects.equals(updateTime, sysLog.updateTime);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, logType, logContent, operateType, userid, username, ip, method, requestUrl, requestParam, requestType, costTime, createBy, createTime, updateBy, updateTime);
+    }
+}

+ 106 - 0
src/main/java/com/usky/exception/GloableExceptionResolver.java

@@ -0,0 +1,106 @@
+package com.usky.exception;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.authz.UnauthorizedException;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/11/10 15:00
+ * @description 全局异常处理
+ **/
+@ControllerAdvice
+public class GloableExceptionResolver {
+
+    @ExceptionHandler(UnauthorizedException.class)
+    public void calUnauthorizedException(UnauthorizedException e) {
+        PrintWriter writer = null;
+        try {
+            //判断是否是ajax
+            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+            HttpServletRequest request = requestAttributes.getRequest();
+            HttpServletResponse response = requestAttributes.getResponse();
+            String header = request.getHeader("X-Requested-With");
+            if (StringUtils.isNoneBlank(header) && "XMLHttpRequest".equalsIgnoreCase(header)) {
+                response.setCharacterEncoding("UTF-8");
+                response.setContentType("application/json; charset=utf-8");
+                writer = response.getWriter();
+                writer.write("{\"status\":401,\"message\":\"无权访问\"}");
+            } else {
+                String contextPath = request.getContextPath();
+                if ("/".equals(contextPath))
+                    contextPath = "";
+                response.sendRedirect(request.getContextPath() + "/page/toDenied");
+            }
+        } catch (IOException io) {
+            io.printStackTrace();
+        } finally {
+            if (writer != null)
+                writer.close();
+        }
+    }
+
+    @ExceptionHandler(UnauthenticatedException.class)
+    public void calUnauthorizedException(UnauthenticatedException e) {
+        PrintWriter writer = null;
+        try {
+            //判断是否是异步请求
+            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+            HttpServletRequest request = requestAttributes.getRequest();
+            HttpServletResponse response = requestAttributes.getResponse();
+            String header = request.getHeader("X-Requested-With");
+            if (StringUtils.isNoneBlank(header) && "XMLHttpRequest".equalsIgnoreCase(header)) {
+                response.setCharacterEncoding("UTF-8");
+                response.setContentType("application/json; charset=utf-8");
+                writer = response.getWriter();
+                writer.write("{\"status\":302,\"message\":\"请前去登录\"}");
+            } else {
+                String contextPath = request.getContextPath();
+                if ("/".equals(contextPath))
+                    contextPath = "";
+                response.sendRedirect(request.getContextPath() + "/login/");
+            }
+        } catch (IOException io) {
+            io.printStackTrace();
+        } finally {
+            if (writer != null)
+                writer.close();
+        }
+    }
+    /**
+     * 处理所有未知的异常
+     */
+  /*  @ExceptionHandler(Exception.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ResponseBody
+    public Map<String, String> validationExceptionHandler(Exception exception) {
+        Map<String, String> map = new HashMap<>(1);
+        map.put("message", "请求失败,系统异常");
+        return map;
+    }*/
+
+    /**
+     * 处理IO异常
+     */
+    @ExceptionHandler(IOException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public Map<String, String> validationExceptionHandler(IOException exception) {
+        Map<String, String> map = new HashMap<>(1);
+        map.put("message", "IO异常");
+        return map;
+    }
+}

+ 104 - 0
src/main/java/com/usky/service/build/BuildService.java

@@ -0,0 +1,104 @@
+package com.usky.service.build;
+
+import com.usky.entity.build.TbBuildingVoEntity;
+import com.usky.entity.build.TbFloorVoEntity;
+import com.usky.entity.build.TbRoomVoEntity;
+import com.usky.utils.Page;
+import com.usky.utils.Result;
+
+public interface BuildService {
+    /**
+     * 建筑新增
+     *
+     * @param buildName
+     * @param buildAddr
+     * @param latitude
+     * @param longitude
+     * @return
+     */
+    Result addBuild(String buildName, String buildAddr, String latitude, String longitude);
+
+    /**
+     * 建筑信息修改
+     *
+     * @param buildName
+     * @param buildAddr
+     * @param latitude
+     * @param longitude
+     * @param id
+     * @param status
+     * @return
+     */
+    Result updateBuild(String buildName, String buildAddr, String latitude, String longitude, String id, String status);
+
+    /**
+     * 建筑信息查询
+     *
+     * @param buildName
+     * @param buildAddr
+     * @param latitude
+     * @param longitude
+     * @param pageNo
+     * @param pageSize
+     * @return
+     */
+    Page<TbBuildingVoEntity> listBuild(String buildName, String buildAddr, String latitude, String longitude, String pageNo, String pageSize);
+
+    /**
+     * 楼层新增
+     *
+     * @param floorBuildUuid
+     * @return
+     */
+    Result addFloor(String floorName, String floorBuildUuid);
+
+    /**
+     * 楼层信息修改
+     *
+     * @param id
+     * @param status
+     * @return
+     */
+    Result updateFloor(String floorName, String floorBuildUuid, String id, String status);
+
+    /**
+     * 楼层信息查询
+     *
+     * @param floorName
+     * @param floorBuildUuid
+     * @param pageNo
+     * @param pageSize
+     * @return
+     */
+    Page<TbFloorVoEntity> queryFloor(String floorName, String floorBuildUuid, String pageNo, String pageSize);
+
+    /**
+     * 添加房间
+     *
+     * @param roomName
+     * @param roomFloorUuid
+     * @return
+     */
+    Result addRoom(String roomName, String roomFloorUuid);
+
+    /**
+     * 房间信息修改
+     *
+     * @param roomName
+     * @param roomFloorUuid
+     * @param id
+     * @param status
+     * @return
+     */
+    Result updateRoom(String roomName, String roomFloorUuid, String id, String status);
+
+    /**
+     * 房间信息查询
+     * @param roomName
+     * @param roomFloorUuid
+     * @param pageNo
+     * @param pageSize
+     * @return
+     */
+    Page<TbRoomVoEntity> queryRoom(String roomName, String roomFloorUuid, String pageNo, String pageSize);
+}

+ 263 - 0
src/main/java/com/usky/service/build/BuildServiceImpl.java

@@ -0,0 +1,263 @@
+package com.usky.service.build;
+
+import com.usky.dao.impl.BaseDaoImpl;
+import com.usky.entity.build.*;
+import com.usky.utils.*;
+import org.apache.commons.lang3.StringUtils;
+import org.hibernate.query.Query;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/5/24 09:53
+ * @description TODO
+ **/
+@Service
+@Transactional
+public class BuildServiceImpl extends BaseDaoImpl implements BuildService {
+    @Override
+    public Result addBuild(String buildName, String buildAddr, String latitude, String longitude) {
+        TbBuildingEntity buildDTO = new TbBuildingEntity();
+        buildDTO.setBuildName(buildName);
+        buildDTO.setBuildAddr(buildAddr);
+        buildDTO.setLatitude(latitude);
+        buildDTO.setLongitude(longitude);
+        buildDTO.setCreatTime(new Timestamp(System.currentTimeMillis()));
+        buildDTO.setBuildUuid(UUIDUtils.getUUID());
+        buildDTO.setStatus(0);
+        getSession().save(buildDTO);
+        return Result.OK();
+    }
+
+    @Override
+    public Result updateBuild(String buildName, String buildAddr, String latitude, String longitude, String id, String status) {
+        Query query = getSession().createQuery("from TbBuildingEntity t where t.id =:id and t.status = 0");
+        List<TbBuildingEntity> buildList = query.setInteger("id", Integer.parseInt(id)).list();
+        if (ListUtil.isBlank(buildList)) {
+            return Result.error("建筑不存在");
+        }
+        TbBuildingEntity tbBuildingEntity = buildList.get(0);
+        if (StringUtils.isNotBlank(buildAddr)) {
+            tbBuildingEntity.setBuildAddr(buildAddr);
+        }
+        if (StringUtils.isNotBlank(buildName)) {
+            tbBuildingEntity.setBuildName(buildName);
+        }
+        if (StringUtils.isNotBlank(latitude)) {
+            tbBuildingEntity.setLatitude(latitude);
+        }
+        if (StringUtils.isNotBlank(longitude)) {
+            tbBuildingEntity.setLongitude(longitude);
+        }
+        if (StringUtils.isNotBlank(status)) {
+
+            List list = getSession().createQuery("from TbFloorEntity t where t.status=0 and t.floorBuildUuid='" + tbBuildingEntity.getBuildUuid() + "'").list();
+            if (ListUtil.isNotBlank(list)) {
+                return Result.error("请先删除下属楼层信息");
+            }
+            tbBuildingEntity.setStatus(Integer.parseInt(status));
+        }
+        getSession().update(tbBuildingEntity);
+        return Result.OK();
+    }
+
+    @Override
+    public Page<TbBuildingVoEntity> listBuild(String buildName, String buildAddr, String latitude, String longitude, String pageNo, String pageSize) {
+
+        StringBuffer sb = new StringBuffer();
+        sb.append("from TbBuildingEntity t where t.status = 0 ");
+        if (StringUtils.isNotBlank(buildName)) {
+            sb.append(" and t.buildName like '%" + buildName + "%'");
+        }
+        if (StringUtils.isNotBlank(buildAddr)) {
+            sb.append(" and t.buildAddr like '%" + buildAddr + "%'");
+        }
+        sb.append(" order By t.creatTime DESC");
+        List list = getSession().createQuery(sb.toString()).list();
+        Page<TbBuildingVoEntity> result = new Page<>(list.size(), Integer.parseInt(pageSize));
+        result.setPageNo(Integer.parseInt(pageNo));
+        Query query = getSession().createQuery(sb.toString());
+        query.setFirstResult((Integer.parseInt(pageNo) - 1) * Integer.parseInt(pageSize));
+        query.setMaxResults(Integer.parseInt(pageSize));
+        List<TbBuildingVoEntity> list1 = query.list();
+        result.setPageList(list1);
+        return result;
+    }
+
+    @Override
+    public Result addFloor(String floorName, String floorBuildUuid) {
+        TbFloorEntity floorEntity = new TbFloorEntity();
+        Query query = getSession().createQuery("from TbBuildingEntity t where t.status = 0 and t.buildUuid=:floorBuildUuid");
+        List floorBuildUuid1 = query.setString("floorBuildUuid", floorBuildUuid).list();
+        if (ListUtil.isBlank(floorBuildUuid1)) {
+            return Result.error("建筑不存在");
+        }
+        floorEntity.setFloorName(floorName);
+        floorEntity.setStatus(0);
+        floorEntity.setCreatTime(new Timestamp(System.currentTimeMillis()));
+        floorEntity.setFloorUuid(UUIDUtils.getUUID());
+        floorEntity.setFloorBuildUuid(floorBuildUuid);
+        getSession().save(floorEntity);
+        return  Result.OK();
+    }
+
+    @Override
+    public Result updateFloor(String floorName, String floorBuildUuid, String id, String status) {
+        Query query = getSession().createQuery("from TbFloorEntity t where t.status=0 and t.id=:id");
+        List<TbFloorEntity> floorList = query.setInteger("id", Integer.parseInt(id)).list();
+        if (ListUtil.isBlank(floorList)) {
+            return Result.error("楼层不存在");
+        }
+        TbFloorEntity tbFloorEntity = floorList.get(0);
+        if (StringUtils.isNotBlank(floorBuildUuid)) {
+            Query builsQuery = getSession().createQuery("from TbBuildingEntity t where t.status = 0 and t.buildUuid=:floorBuildUuid");
+            List floorBuildUuid1 = builsQuery.setString("floorBuildUuid", floorBuildUuid).list();
+            if (ListUtil.isBlank(floorBuildUuid1)) {
+                return  Result.error( "建筑不存在");
+            }
+            tbFloorEntity.setFloorBuildUuid(floorBuildUuid);
+        }
+
+
+        if (StringUtils.isNotBlank(floorName)) {
+            tbFloorEntity.setFloorName(floorName);
+        }
+        if (StringUtils.isNotBlank(status)) {
+            List<TbRoomEntity> list = getSession().createQuery("from TbRoomEntity t where t.status=0 and t.roomFloorUuid='" + tbFloorEntity.getFloorUuid() + "'").list();
+            if (ListUtil.isNotBlank(list)) {
+                return  Result.error("请删除下属房间");
+            }
+
+            tbFloorEntity.setStatus(Integer.parseInt(status));
+        }
+        getSession().update(tbFloorEntity);
+        return  Result.OK();
+    }
+
+    @Override
+    public Page<TbFloorVoEntity> queryFloor(String floorName, String floorBuildUuid, String pageNo, String pageSize) {
+        StringBuffer sb = new StringBuffer();
+        sb.append("from TbFloorEntity t where t.status = 0");
+        if (StringUtils.isNotBlank(floorName)) {
+            sb.append(" and t.floorName like '%" + floorName + "%'");
+        }
+        if (StringUtils.isNotBlank(floorBuildUuid)) {
+            sb.append(" and t.floorBuildUuid ='" + floorBuildUuid + "'");
+        }
+        sb.append(" order By t.creatTime DESC");
+        List list = getSession().createQuery(sb.toString()).list();
+        Page<TbFloorVoEntity> result = new Page<>(list.size(), Integer.parseInt(pageSize));
+        Query query = getSession().createQuery(sb.toString());
+        query.setFirstResult((Integer.parseInt(pageNo) - 1) * Integer.parseInt(pageSize));
+        query.setMaxResults(Integer.parseInt(pageSize));
+        List<TbFloorEntity> list1 = query.list();
+        List<TbFloorVoEntity> tbFloorVoEntities = BeanHelp.copyWithCollection(list1, TbFloorVoEntity.class);
+        if (ListUtil.isBlank(list1)) {
+            result.setPageNo(Integer.parseInt(pageNo));
+            result.setPageList(tbFloorVoEntities);
+            return result;
+        }
+        for (TbFloorVoEntity o : tbFloorVoEntities) {
+            List<TbBuildingEntity> buildingEntityList = getSession().createQuery("from TbBuildingEntity t where t.status=0 and t.buildUuid='" + o.getFloorBuildUuid() + "'").list();
+            if (ListUtil.isBlank(buildingEntityList)) {
+                o.setBuildName("");
+            } else {
+
+                o.setBuildName(buildingEntityList.get(0).getBuildName());
+            }
+        }
+        result.setPageNo(Integer.parseInt(pageNo));
+        result.setPageList(tbFloorVoEntities);
+        return result;
+
+
+    }
+
+    @Override
+    public Result addRoom(String roomName, String roomFloorUuid) {
+        Query query = getSession().createQuery("from TbFloorEntity t where t.status = 0 and t.floorUuid=:roomFloorUuid");
+        List roomFloorUuid1 = query.setString("roomFloorUuid", roomFloorUuid).list();
+        if (ListUtil.isBlank(roomFloorUuid1)) {
+            return  Result.error("楼层不存在");
+        }
+        TbRoomEntity roomEntity = new TbRoomEntity();
+        roomEntity.setRoomName(roomName);
+        roomEntity.setRoomUuid(UUIDUtils.getUUID());
+        roomEntity.setRoomFloorUuid(roomFloorUuid);
+        roomEntity.setCreatTime(new Timestamp(System.currentTimeMillis()));
+        roomEntity.setStatus(0);
+        getSession().save(roomEntity);
+        return  Result.OK();
+    }
+
+    @Override
+    public Result updateRoom(String roomName, String roomFloorUuid, String id, String status) {
+        Query query = getSession().createQuery("from TbRoomEntity t where t.status=0 and t.id=:id");
+        List<TbRoomEntity> roomEntities = query.setInteger("id", Integer.parseInt(id)).list();
+        if (ListUtil.isBlank(roomEntities)) {
+            return  Result.error("房间不存在");
+        }
+        TbRoomEntity tbRoomEntity = roomEntities.get(0);
+        if (StringUtils.isNotBlank(roomName)) {
+            tbRoomEntity.setRoomName(roomName);
+        }
+        if (StringUtils.isNotBlank(status)) {
+            tbRoomEntity.setStatus(Integer.parseInt(status));
+        }
+        if (StringUtils.isNotBlank(roomFloorUuid)) {
+            Query query1 = getSession().createQuery("from TbFloorEntity t where t.status = 0 and t.floorUuid=:roomFloorUuid");
+            List<TbFloorEntity> roomFloorUuid1 = query1.setString("roomFloorUuid", roomFloorUuid).list();
+            if (ListUtil.isBlank(roomFloorUuid1)) {
+                return  Result.error("楼层不存在");
+            }
+            tbRoomEntity.setRoomFloorUuid(roomFloorUuid);
+        }
+        getSession().update(tbRoomEntity);
+        return  Result.OK();
+    }
+
+    @Override
+    public Page<TbRoomVoEntity> queryRoom(String roomName, String roomFloorUuid, String pageNo, String pageSize) {
+        StringBuffer sb = new StringBuffer();
+        sb.append("from TbRoomEntity t where t.status=0 ");
+        if (StringUtils.isNotBlank(roomName)) {
+            sb.append(" and t.roomName like'%" + roomName + "%'");
+        }
+        if (StringUtils.isNotBlank(roomFloorUuid)) {
+            sb.append(" and t.roomFloorUuid = '" + roomFloorUuid + "'");
+        }
+        sb.append(" order By t.creatTime DESC");
+        List<TbRoomEntity> total = getSession().createQuery(sb.toString()).list();
+        Page<TbRoomVoEntity> result = new Page<TbRoomVoEntity>(total.size(), Integer.parseInt(pageSize));
+        result.setPageNo(Integer.parseInt(pageNo));
+        result.setPageSize(Integer.parseInt(pageSize));
+        Query query = getSession().createQuery(sb.toString());
+        query.setFirstResult((Integer.parseInt(pageNo) - 1) * Integer.parseInt(pageSize));
+        query.setMaxResults(Integer.parseInt(pageSize));
+        List<TbRoomVoEntity> list = query.list();
+        List<TbRoomVoEntity> roomVoEntityList = BeanHelp.copyWithCollection(list, TbRoomVoEntity.class);
+        if (ListUtil.isBlank(roomVoEntityList)) {
+            {
+                result.setPageList(roomVoEntityList);
+                return result;
+            }
+        }
+        for (TbRoomVoEntity roomVoEntity : roomVoEntityList) {
+            List<TbFloorEntity> floorEntityList = getSession().createQuery("from TbFloorEntity t where t.status=0 and t.floorUuid='" + roomVoEntity.getRoomFloorUuid() + "'").list();
+            if (ListUtil.isBlank(floorEntityList)) {
+                roomVoEntity.setFloorName("");
+            } else {
+
+                roomVoEntity.setFloorName(floorEntityList.get(0).getFloorName());
+            }
+        }
+        result.setPageList(roomVoEntityList);
+        return result;
+
+    }
+}

+ 7 - 0
src/main/java/com/usky/service/log/LogService.java

@@ -0,0 +1,7 @@
+package com.usky.service.log;
+
+import com.usky.entity.sys.SysLogDTO;
+
+public interface LogService {
+    void addLog(SysLogDTO dto);
+}

+ 19 - 0
src/main/java/com/usky/service/log/LogServiceImpl.java

@@ -0,0 +1,19 @@
+package com.usky.service.log;
+
+import com.usky.dao.impl.BaseDaoImpl;
+import com.usky.entity.sys.SysLogDTO;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/7/14 14:10
+ * @description TODO
+ **/
+@Service
+public class LogServiceImpl extends BaseDaoImpl implements LogService {
+    @Override
+    public void addLog(SysLogDTO dto) {
+        getSession().save(dto);
+    }
+}

+ 22 - 0
src/main/java/com/usky/task/Dhtask.java

@@ -0,0 +1,22 @@
+package com.usky.task;
+
+import com.usky.utils.TimeUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/5/28 09:50
+ * @description TODO
+ **/
+@Component
+@Slf4j
+public class Dhtask {
+    @Scheduled(cron = "${cron.rk}")
+    public void dhTask(){
+        String time = TimeUtil.getTime();
+        log.info("大华摄像机数据同步开始++++"+time+"++++++++");
+    }
+}

+ 17 - 0
src/main/java/com/usky/task/ScheduledConfig.java

@@ -0,0 +1,17 @@
+package com.usky.task;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+@Configuration
+public class ScheduledConfig {
+    @Bean
+    public TaskScheduler taskScheduler() {
+        ThreadPoolTaskScheduler scheduling = new ThreadPoolTaskScheduler();
+        scheduling.setPoolSize(10);
+        scheduling.initialize();
+        return scheduling;
+    }
+}

+ 74 - 0
src/main/java/com/usky/utils/BeanHelp.java

@@ -0,0 +1,74 @@
+package com.usky.utils;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/3/9 12:49
+ * @description TODO
+ **/
+@Slf4j
+public class BeanHelp {
+    public static <T> T copyProperties(Object source, Class<T> target){
+        try {
+            T t = target.newInstance();
+            BeanUtils.copyProperties(source, t);
+            return t;
+        } catch (Exception e) {
+            log.error("【数据转换】数据转换出错,目标对象{}构造函数异常", target.getName(), e);
+            //logger.error();
+            // throw new LyException(ExceptionEnum.DATA_TRANSFER_ERROR);
+        }
+        return null;
+    }
+
+    public static <T> List<T> copyWithCollection(List<?> sourceList, Class<T> target){
+        try {
+            return sourceList.stream().map(s -> copyProperties(s, target)).collect(Collectors.toList());
+        } catch (Exception e) {
+            log.error("【数据转换】数据转换出错,目标对象{}构造函数异常", target.getName(), e);
+            // throw new LyException(ExceptionEnum.DATA_TRANSFER_ERROR);
+        }
+        return null;
+    }
+
+    public static <T> Set<T> copyWithCollection(Set<?> sourceList, Class<T> target){
+        try {
+            return sourceList.stream().map(s -> copyProperties(s, target)).collect(Collectors.toSet());
+        } catch (Exception e) {
+
+        }
+        return null;
+    }
+
+
+    /**
+     * JSON字符串转对象
+     * @param jsonStr
+     * @param clazz
+     * @return
+     */
+    public static <T> T jsonStrToObject(String jsonStr, Class<T> clazz){
+        return JSONObject.parseObject(jsonStr, clazz);
+
+    }
+
+    /**
+     * JSON字符串转List
+     * @param type
+     * @param listString
+     * @return
+     */
+    public static <T> List<T> listStringToOjectList(String listString,Class<T> type){
+        return JSONObject.parseArray(listString, type);
+
+    }
+
+}

+ 28 - 0
src/main/java/com/usky/utils/ConfigUtil.java

@@ -0,0 +1,28 @@
+package com.usky.utils;
+
+import java.util.ResourceBundle;
+
+/**
+ * @author 鲁班七号
+ * @version v1.0
+ * @date 2020/4/30 14:28
+ * @description TODO
+ **/
+public class ConfigUtil {
+    private static String BASE_URL;
+    static {
+        ResourceBundle bundle = ResourceBundle.getBundle("config");
+        BASE_URL = bundle.getString("BASE_URL");//系统路径
+
+    }
+
+    public ConfigUtil() {
+    }
+
+
+    public static String getSimBaseUrl() {
+        return BASE_URL;
+    }
+
+
+}

+ 26 - 0
src/main/java/com/usky/utils/DaoResult.java

@@ -0,0 +1,26 @@
+package com.usky.utils;
+
+public class DaoResult {
+	String result;
+	boolean success;
+	public DaoResult(String result, boolean success){
+		this.result = result;
+		this.success = success;
+	}
+	public DaoResult(){
+		this.result="";
+		this.success=false;
+	}
+	public String getResult() {
+		return result;
+	}
+	public void setResult(String result) {
+		this.result = result;
+	}
+	public boolean isSuccess() {
+		return success;
+	}
+	public void setSuccess(boolean success) {
+		this.success = success;
+	}
+}

+ 23 - 0
src/main/java/com/usky/utils/DialectForInkfish.java

@@ -0,0 +1,23 @@
+package com.usky.utils;
+
+import org.hibernate.dialect.MySQL5Dialect;
+
+import java.sql.Types;
+
+/**
+ * @Author zhangzilong
+ * @Description //TODO
+ * @Date $ $
+ * @Param $
+ * @return $
+ */
+public class DialectForInkfish extends MySQL5Dialect {
+
+
+    public DialectForInkfish() {
+        super();
+        registerHibernateType(Types.LONGVARCHAR, 65535, "text");
+    }
+
+
+}

+ 315 - 0
src/main/java/com/usky/utils/HttpUtils.java

@@ -0,0 +1,315 @@
+package com.usky.utils;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+
+public class HttpUtils {
+	
+	/**
+	 * get
+	 * 
+	 * @param host
+	 * @param path
+	 * @param method
+	 * @param headers
+	 * @param querys
+	 * @return
+	 * @throws Exception
+	 */
+	public static HttpResponse doGet(String host, String path, String method, 
+			Map<String, String> headers, 
+			Map<String, String> querys)
+            throws Exception {    	
+    	HttpClient httpClient = wrapClient(host);
+
+    	HttpGet request = new HttpGet(buildUrl(host, path, querys));
+        for (Map.Entry<String, String> e : headers.entrySet()) {
+        	request.addHeader(e.getKey(), e.getValue());
+        }
+        
+        return httpClient.execute(request);
+    }
+	
+	/**
+	 * post form
+	 * 
+	 * @param host
+	 * @param path
+	 * @param method
+	 * @param headers
+	 * @param querys
+	 * @param bodys
+	 * @return
+	 * @throws Exception
+	 */
+	public static HttpResponse doPost(String host, String path, String method, 
+			Map<String, String> headers, 
+			Map<String, String> querys, 
+			Map<String, String> bodys)
+            throws Exception {    	
+    	HttpClient httpClient = wrapClient(host);
+
+    	HttpPost request = new HttpPost(buildUrl(host, path, querys));
+        for (Map.Entry<String, String> e : headers.entrySet()) {
+        	request.addHeader(e.getKey(), e.getValue());
+        }
+
+        if (bodys != null) {
+            List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();
+
+            for (String key : bodys.keySet()) {
+                nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
+            }
+            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
+            formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
+            request.setEntity(formEntity);
+        }
+
+        return httpClient.execute(request);
+    }	
+	
+	/**
+	 * Post String
+	 * 
+	 * @param host
+	 * @param path
+	 * @param method
+	 * @param headers
+	 * @param querys
+	 * @param body
+	 * @return
+	 * @throws Exception
+	 */
+	public static HttpResponse doPost(String host, String path, String method, 
+			Map<String, String> headers, 
+			Map<String, String> querys, 
+			String body)
+            throws Exception {    	
+    	HttpClient httpClient = wrapClient(host);
+
+    	HttpPost request = new HttpPost(buildUrl(host, path, querys));
+        for (Map.Entry<String, String> e : headers.entrySet()) {
+        	request.addHeader(e.getKey(), e.getValue());
+        }
+
+        if (StringUtils.isNotBlank(body)) {
+        	request.setEntity(new StringEntity(body, "utf-8"));
+        }
+
+        return httpClient.execute(request);
+    }
+	
+	/**
+	 * Post stream
+	 * 
+	 * @param host
+	 * @param path
+	 * @param method
+	 * @param headers
+	 * @param querys
+	 * @param body
+	 * @return
+	 * @throws Exception
+	 */
+	public static HttpResponse doPost(String host, String path, String method, 
+			Map<String, String> headers, 
+			Map<String, String> querys, 
+			byte[] body)
+            throws Exception {    	
+    	HttpClient httpClient = wrapClient(host);
+
+    	HttpPost request = new HttpPost(buildUrl(host, path, querys));
+        for (Map.Entry<String, String> e : headers.entrySet()) {
+        	request.addHeader(e.getKey(), e.getValue());
+        }
+
+        if (body != null) {
+        	request.setEntity(new ByteArrayEntity(body));
+        }
+
+        return httpClient.execute(request);
+    }
+	
+	/**
+	 * Put String
+	 * @param host
+	 * @param path
+	 * @param method
+	 * @param headers
+	 * @param querys
+	 * @param body
+	 * @return
+	 * @throws Exception
+	 */
+	public static HttpResponse doPut(String host, String path, String method, 
+			Map<String, String> headers, 
+			Map<String, String> querys, 
+			String body)
+            throws Exception {    	
+    	HttpClient httpClient = wrapClient(host);
+
+    	HttpPut request = new HttpPut(buildUrl(host, path, querys));
+        for (Map.Entry<String, String> e : headers.entrySet()) {
+        	request.addHeader(e.getKey(), e.getValue());
+        }
+
+        if (StringUtils.isNotBlank(body)) {
+        	request.setEntity(new StringEntity(body, "utf-8"));
+        }
+
+        return httpClient.execute(request);
+    }
+	
+	/**
+	 * Put stream
+	 * @param host
+	 * @param path
+	 * @param method
+	 * @param headers
+	 * @param querys
+	 * @param body
+	 * @return
+	 * @throws Exception
+	 */
+	public static HttpResponse doPut(String host, String path, String method, 
+			Map<String, String> headers, 
+			Map<String, String> querys, 
+			byte[] body)
+            throws Exception {    	
+    	HttpClient httpClient = wrapClient(host);
+
+    	HttpPut request = new HttpPut(buildUrl(host, path, querys));
+        for (Map.Entry<String, String> e : headers.entrySet()) {
+        	request.addHeader(e.getKey(), e.getValue());
+        }
+
+        if (body != null) {
+        	request.setEntity(new ByteArrayEntity(body));
+        }
+
+        return httpClient.execute(request);
+    }
+	
+	/**
+	 * Delete
+	 *  
+	 * @param host
+	 * @param path
+	 * @param method
+	 * @param headers
+	 * @param querys
+	 * @return
+	 * @throws Exception
+	 */
+	public static HttpResponse doDelete(String host, String path, String method, 
+			Map<String, String> headers, 
+			Map<String, String> querys)
+            throws Exception {    	
+    	HttpClient httpClient = wrapClient(host);
+
+    	HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
+        for (Map.Entry<String, String> e : headers.entrySet()) {
+        	request.addHeader(e.getKey(), e.getValue());
+        }
+        
+        return httpClient.execute(request);
+    }
+	
+	private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
+    	StringBuilder sbUrl = new StringBuilder();
+    	sbUrl.append(host);
+    	if (!StringUtils.isBlank(path)) {
+    		sbUrl.append(path);
+        }
+    	if (null != querys) {
+    		StringBuilder sbQuery = new StringBuilder();
+        	for (Map.Entry<String, String> query : querys.entrySet()) {
+        		if (0 < sbQuery.length()) {
+        			sbQuery.append("&");
+        		}
+        		if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
+        			sbQuery.append(query.getValue());
+                }
+        		if (!StringUtils.isBlank(query.getKey())) {
+        			sbQuery.append(query.getKey());
+        			if (!StringUtils.isBlank(query.getValue())) {
+        				sbQuery.append("=");
+        				sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
+        			}        			
+                }
+        	}
+        	if (0 < sbQuery.length()) {
+        		sbUrl.append("?").append(sbQuery);
+        	}
+        }
+    	
+    	return sbUrl.toString();
+    }
+	
+	private static HttpClient wrapClient(String host) {
+		HttpClient httpClient = new DefaultHttpClient();
+		if (host.startsWith("https://")) {
+			sslClient(httpClient);
+		}
+		
+		return httpClient;
+	}
+	
+	private static void sslClient(HttpClient httpClient) {
+        try {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            X509TrustManager tm = new X509TrustManager() {
+                @Override
+				public X509Certificate[] getAcceptedIssuers() {
+                    return null;
+                }
+                @Override
+				public void checkClientTrusted(X509Certificate[] xcs, String str) {
+                	
+                }
+                @Override
+				public void checkServerTrusted(X509Certificate[] xcs, String str) {
+                	
+                }
+            };
+            ctx.init(null, new TrustManager[] { tm }, null);
+            SSLSocketFactory ssf = new SSLSocketFactory(ctx);
+            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
+            ClientConnectionManager ccm = httpClient.getConnectionManager();
+            SchemeRegistry registry = ccm.getSchemeRegistry();
+            registry.register(new Scheme("https", 443, ssf));
+        } catch (KeyManagementException ex) {
+            throw new RuntimeException(ex);
+        } catch (NoSuchAlgorithmException ex) {
+        	throw new RuntimeException(ex);
+        }
+    }
+}

+ 58 - 0
src/main/java/com/usky/utils/IPUtils.java

@@ -0,0 +1,58 @@
+
+package com.usky.utils;
+
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * IP地址
+ *
+ * @author Mark sunlightcs@gmail.com
+ */
+public class IPUtils {
+    private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
+
+    /**
+     * 获取IP地址
+     * <p>
+     * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
+     * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
+     */
+    public static String getIpAddr(HttpServletRequest request) {
+        String ip = null;
+        try {
+            ip = request.getHeader("x-forwarded-for");
+            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+                ip = request.getHeader("Proxy-Client-IP");
+            }
+            if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+                ip = request.getHeader("WL-Proxy-Client-IP");
+            }
+            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+                ip = request.getHeader("HTTP_CLIENT_IP");
+            }
+            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
+            }
+            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
+                ip = request.getRemoteAddr();
+            }
+        } catch (Exception e) {
+            logger.error("IPUtils ERROR ", e);
+        }
+
+        //使用代理,则获取第一个IP地址
+        if (StringUtils.isEmpty(ip) && ip.length() > 15) {
+            if (ip.indexOf(",") > 0) {
+                ip = ip.substring(0, ip.indexOf(","));
+            }
+        }
+
+        return ip;
+    }
+
+}

+ 53 - 0
src/main/java/com/usky/utils/JsonUtil.java

@@ -0,0 +1,53 @@
+package com.usky.utils;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author 我去喂猪了
+ * @version v1.0
+ * @date 2020/9/12 14:35
+ * @description TODO
+ **/
+
+public class JsonUtil {
+
+    private static Pattern linePattern = Pattern.compile("_(\\w)");
+
+    /** 下划线转驼峰 */
+    public static String lineToHump(String str) {
+        str = str.toLowerCase();
+        Matcher matcher = linePattern.matcher(str);
+        StringBuffer sb = new StringBuffer();
+        while (matcher.find()) {
+            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
+        }
+        matcher.appendTail(sb);
+        return sb.toString();
+    }
+
+    /** 驼峰转下划线(简单写法,效率低于{@link #humpToLine2(String)}) */
+    public static String humpToLine(String str) {
+        return str.replaceAll("[A-Z]", "_$0").toLowerCase();
+    }
+
+    private static Pattern humpPattern = Pattern.compile("[A-Z]");
+
+    /** 驼峰转下划线,效率比上面高 */
+    public static String humpToLine2(String str) {
+        Matcher matcher = humpPattern.matcher(str);
+        StringBuffer sb = new StringBuffer();
+        while (matcher.find()) {
+            matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
+        }
+        matcher.appendTail(sb);
+        return sb.toString();
+    }
+
+    public static void main(String[] args) {
+        String lineToHump = lineToHump("{\"device_code\":\"12321312332313\",\"company_code\":\"\",\"device_name\":\"1223\",\"transmission_mode\":\"1\",\"device_types\":\"3\",\"device_model\":\"6\",\"data\":\"1\",\"device_install\":\"1\",\"device_floor\":\"1\",\"sim\":\"11\",\"manufactor_name\":\"无锡蓝天\",\"installer\":\"1\",\"magnification\":\"\",\"model_corresponding_method\":\"\",\"ports\":\"0\",\"highest_floor\":\"1\",\"lowest_floor\":\"1\",\"top_level_label\":\"0\",\"ownerId\":\"0\",\"device_type\":\"Smoke\",\"is_secure\":\"0\",\"status\":\"1\",\"protocol_type\":\"CoPA\",\"location\":\"黄浦区\",\"model\":\"\",\"psk\":\"c78af7fe1406eaad0898cce15b6e15f1\",\"iot_id\":\"7662fe48-2cdb-467f-869e-dd6a6babf8c4\",\"ownerCode\":\"YT100036000010000001\",\"number\":\"10000001\"}");
+        System.out.println(lineToHump);// fParentNoLeader
+
+    }
+
+}

+ 75 - 0
src/main/java/com/usky/utils/JsonUtils.java

@@ -0,0 +1,75 @@
+package com.usky.utils;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+
+public class JsonUtils {
+
+    public static final ObjectMapper mapper = new ObjectMapper();
+
+    private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);
+
+    public static String toString(Object obj) {
+        if (obj == null) {
+            return null;
+        }
+        if (obj.getClass() == String.class) {
+            return (String) obj;
+        }
+        try {
+            return mapper.writeValueAsString(obj);
+        } catch (JsonProcessingException e) {
+            logger.error("json序列化出错:" + obj, e);
+            return null;
+        }
+    }
+
+    public static <T> T toBean(String json, Class<T> tClass) {
+        try {
+            return mapper.readValue(json, tClass);
+        } catch (IOException e) {
+            logger.error("json解析出错:" + json, e);
+            return null;
+        }
+    }
+
+    public static <E> List<E> toList(String json, Class<E> eClass) {
+        try {
+            return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, eClass));
+        } catch (IOException e) {
+            logger.error("json解析出错:" + json, e);
+            return null;
+        }
+    }
+
+    public static <K, V> Map<K, V> toMap(String json, Class<K> kClass, Class<V> vClass) {
+        try {
+            return mapper.readValue(json, mapper.getTypeFactory().constructMapType(Map.class, kClass, vClass));
+        } catch (IOException e) {
+            logger.error("json解析出错:" + json, e);
+            return null;
+        }
+    }
+
+    public static <T> T nativeRead(String json, TypeReference<T> type) {
+        try {
+            return mapper.readValue(json, type);
+        } catch (IOException e) {
+            logger.error("json解析出错:" + json, e);
+            return null;
+        }
+    }
+
+}

+ 37 - 0
src/main/java/com/usky/utils/ListUtil.java

@@ -0,0 +1,37 @@
+package com.usky.utils;
+
+import java.util.List;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/2/22 15:41
+ * @description 集合判断工具类
+ **/
+public class ListUtil {
+    /**
+     * 判断集合是否为空
+     * @param list
+     * @return
+     */
+    public static boolean isNotBlank(List list) {
+        if (list == null || list.isEmpty()) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     *
+     * @param list
+     * @return
+     */
+    public static boolean isBlank(List list) {
+        if (list == null || list.isEmpty()) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}

+ 30 - 0
src/main/java/com/usky/utils/LoginResult.java

@@ -0,0 +1,30 @@
+package com.usky.utils;
+
+public class LoginResult {
+	private String V_LOGINNAME;
+	private String V_PASSWORD;
+	private String Result;
+	public LoginResult() {
+		this.Result="";
+		this.V_LOGINNAME="";
+		this.V_PASSWORD= "";
+	}
+	public String getV_LOGINNAME() {
+		return V_LOGINNAME;
+	}
+	public void setV_LOGINNAME(String v_LOGINNAME) {
+		V_LOGINNAME = v_LOGINNAME;
+	}
+	public String getV_PASSWORD() {
+		return V_PASSWORD;
+	}
+	public void setV_PASSWORD(String v_PASSWORD) {
+		V_PASSWORD = v_PASSWORD;
+	}
+	public String getResult() {
+		return Result;
+	}
+	public void setResult(String result) {
+		Result = result;
+	}
+}

+ 39 - 0
src/main/java/com/usky/utils/MD5Util.java

@@ -0,0 +1,39 @@
+package com.usky.utils;
+
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class MD5Util {
+	public final static String MD5(String s){
+		char hexDigits[] ={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+		try{
+			byte[] btInput = s.getBytes();
+			MessageDigest mdInst = MessageDigest.getInstance("MD5");
+			mdInst.update(btInput);
+			byte[] md = mdInst.digest();
+			int j = md.length;
+			char str[] = new char[j*2];
+			int k = 0;
+			for(int i=0;i<j;i++){
+				byte byte0 = md[i];
+				str[k++] = hexDigits[byte0 >>> 4 & 0xf];
+				str[k++] = hexDigits[byte0 & 0xf];
+			}
+			return new String(str);
+		} catch (Exception e){
+			return null;
+		}
+	}
+	public static String EncoderByMd5(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException{
+		byte[] secretBytes = null; 
+		MessageDigest md5=MessageDigest.getInstance("MD5");
+		 md5.update(str.getBytes());
+		 secretBytes = md5.digest();
+		 String newstr = new BigInteger(1,secretBytes).toString(16);
+		 for(int i=0;i<32-newstr.length();i++)
+			 newstr = "0"+newstr;
+		 return newstr;
+	}
+}

+ 99 - 0
src/main/java/com/usky/utils/MD5Utils.java

@@ -0,0 +1,99 @@
+package com.usky.utils;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MD5Utils {
+ 
+    /**
+     * MD5加密字符串(32位大写)
+     *
+     * @param string 需要进行MD5加密的字符串
+     * @return 加密后的字符串(大写)
+     */
+    public static String md5Encrypt32Upper(String string) {
+        byte[] hash;
+        try {
+            //创建一个MD5算法对象,并获得MD5字节数组,16*8=128位
+            hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("Huh, MD5 should be supported?", e);
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("Huh, UTF-8 should be supported?", e);
+        }
+ 
+        //转换为十六进制字符串
+        StringBuilder hex = new StringBuilder(hash.length * 2);
+        for (byte b : hash) {
+            if ((b & 0xFF) < 0x10) {hex.append("0");}
+            hex.append(Integer.toHexString(b & 0xFF));
+        }
+        return hex.toString().toUpperCase();
+    }
+ 
+    /**
+     * MD5加密字符串(32位小写)
+     *
+     * @param string 需要进行MD5加密的字符串
+     * @return 加密后的字符串(小写)
+     */
+    public static String md5Encrypt32Lower(String string) {
+        byte[] hash;
+        try {
+            //创建一个MD5算法对象,并获得MD5字节数组,16*8=128位
+            hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("Huh, MD5 should be supported?", e);
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("Huh, UTF-8 should be supported?", e);
+        }
+ 
+        //转换为十六进制字符串
+        StringBuilder hex = new StringBuilder(hash.length * 2);
+        for (byte b : hash) {
+            if ((b & 0xFF) < 0x10) {hex.append("0");}
+            hex.append(Integer.toHexString(b & 0xFF));
+        }
+        return hex.toString().toLowerCase();
+    }
+ 
+    /**
+     * 将二进制字节数组转换为十六进制字符串
+     *
+     * @param bytes 二进制字节数组
+     * @return 十六进制字符串
+     */
+    public static String bytesToHex(byte[] bytes) {
+        StringBuffer hexStr = new StringBuffer();
+        int num;
+        for (int i = 0; i < bytes.length; i++) {
+            num = bytes[i];
+            if (num < 0) {
+                num += 256;
+            }
+            if (num < 16) {
+                hexStr.append("0");
+            }
+            hexStr.append(Integer.toHexString(num));
+        }
+        return hexStr.toString().toUpperCase();
+    }
+ 
+    /**
+     * Unicode中文编码转换成字符串
+     */
+    public static String unicodeToString(String str) {
+        Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
+        Matcher matcher = pattern.matcher(str);
+        char ch;
+        while (matcher.find()) {
+            ch = (char) Integer.parseInt(matcher.group(2), 16);
+            str = str.replace(matcher.group(1), ch + "");
+        }
+        return str;
+    }
+
+}

+ 88 - 0
src/main/java/com/usky/utils/MapUtils.java

@@ -0,0 +1,88 @@
+package com.usky.utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Map;
+
+/**
+ * @author   辅助类
+ **/
+public final class MapUtils {
+
+	/**
+	 * @author
+	 * @para
+	 * @return string
+	 **/
+	public static String getStr(Object obj) {
+		if (obj == null) {
+			return null;
+		} else {
+			return StringUtils.trimToEmpty(obj.toString());
+		}
+	}
+
+	/**
+	 * @author   验证map设定非空字段是否为空
+	 * @param params
+	 *            :目标map,args:校验数组
+	 * @return boolean
+	 **/
+	public static boolean isEmptyMap(Map<String, ?> params, String[] args) {
+		if (params == null || args == null || params.isEmpty() || args.length == 0) {
+			return true;
+		}
+
+		boolean bool = false;
+		for (String arg : args) {
+			if (StringUtils.isBlank(getStr(params.get(arg)))) {
+				bool = true;
+				break;
+			}
+		}
+		return bool;
+	}
+	
+	/**
+	 * @author   判断Map是否为非NULL或者非Empty
+	 * @param map
+	 * @return boolean
+	 * **/
+	public static boolean isNotBlank(Map<String,?> map) {
+		if (map != null && !map.isEmpty()) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+	
+	/**
+	 * @author   判断Map是否为NULL或者Empty
+	 * @param map
+	 * @return boolean
+	 * **/
+	public static boolean isBlank(Map<String,?> map) {
+		return !isNotBlank(map);
+	}
+	
+	/**
+	 * @author   验证map设定的value是否全是整形数值
+	 * @param params
+	 *            :目标map,args:校验数组
+	 * @return boolean true 是, false 不是
+	 **/
+	public static boolean isNumericMap(Map<String, ?> params, String[] args) {
+		if (params == null || args == null || params.isEmpty() || args.length == 0) {
+			return false;
+		}
+
+		boolean bool = true;
+		for (String arg : args) {
+			if (!StringUtils.isNumeric(getStr(params.get(arg)))) {
+				bool = false;
+				break;
+			}
+		}
+		return bool;
+	}
+}

+ 128 - 0
src/main/java/com/usky/utils/Page.java

@@ -0,0 +1,128 @@
+package com.usky.utils;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.util.List;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/2/24 9:01
+ * @description TODO
+ **/
+@ApiModel(value="分页返回结果集")
+public class Page<T> {
+    @ApiModelProperty("每页显示条数")
+    private int pageSize; //每页显示条数
+    @ApiModelProperty("总条数")
+    private int totalCount; //总条数
+    @ApiModelProperty("开始条数")
+    private int start; //开始条数
+    @ApiModelProperty("当前页")
+    private int pageNo;//当前页
+    @ApiModelProperty("总页数")
+    private int totalPages; //总页数
+    @ApiModelProperty("数据")
+    private List<T> pageList;//数据
+
+    public Page(int totalCount, Integer pageSize) {
+        if (pageSize == null) {
+            this.pageSize = 20;
+        } else {
+            this.pageSize = pageSize;
+        }
+        this.totalCount = totalCount;
+    }
+
+    /**
+     * 获取下一条
+     *
+     * @return
+     */
+    public int getCurrentPageNo() {
+        return start / pageSize + 1;
+    }
+
+    /**
+     * 是否有下一条
+     *
+     * @return
+     */
+    public boolean getHasNextPage() {
+        return getCurrentPageNo() < totalPages;
+    }
+
+    /**
+     * 当前页是否大于一
+     *
+     * @return
+     */
+    public boolean getHasPavPage() {
+        return getCurrentPageNo() > 1;
+    }
+
+    /**
+     * 获取总页数
+     *
+     * @return
+     */
+    public int getTotalPages() {
+        totalPages = totalCount / pageSize;
+
+        if (totalCount % pageSize != 0) {
+            totalPages++;
+        }
+
+        return totalPages;
+    }
+
+    public int getStart(int pageNo) {
+        this.pageNo = pageNo;
+        if (pageNo < 1) {
+            pageNo = 1;
+        }
+        start = (pageNo - 1) * pageSize;
+        return start;
+    }
+
+    public int getPageSize() {
+        return pageSize;
+    }
+
+    public void setPageSize(int pageSize) {
+        this.pageSize = pageSize;
+    }
+
+    public int getTotalCount() {
+        return totalCount;
+    }
+
+    public void setTotalCount(int totalCount) {
+        this.totalCount = totalCount;
+    }
+
+    public void setStart(int start) {
+        this.start = start;
+    }
+
+    public List<T> getPageList() {
+        return pageList;
+    }
+
+    public void setPageList(List<T> pageList) {
+        this.pageList = pageList;
+    }
+
+    public void setTotalPages(int totalPages) {
+        this.totalPages = totalPages;
+    }
+
+    public int getPageNo() {
+        return pageNo;
+    }
+
+    public void setPageNo(int pageNo) {
+        this.pageNo = pageNo;
+    }
+}

+ 130 - 0
src/main/java/com/usky/utils/PageData.java

@@ -0,0 +1,130 @@
+package com.usky.utils;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.util.List;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/2/24 9:01
+ * @description TODO
+ **/
+@ApiModel(value="分页返回结果集")
+public class PageData<T> {
+    @ApiModelProperty("每页显示条数")
+    private int pageSize; //每页显示条数
+    @ApiModelProperty("总条数")
+    private int totalCount; //总条数
+    @ApiModelProperty("开始条数")
+    private int start; //开始条数
+    @ApiModelProperty("当前页")
+    private int pageNo;//当前页
+    @ApiModelProperty("总页数")
+    private int totalPages; //总页数
+    @ApiModelProperty("数据")
+    private T pageList;//数据
+
+    public PageData(int totalCount, Integer pageSize) {
+        if (pageSize == null) {
+            this.pageSize = 20;
+        } else {
+            this.pageSize = pageSize;
+        }
+        this.totalCount = totalCount;
+    }
+
+    /**
+     * 获取下一条
+     *
+     * @return
+     */
+    public int getCurrentPageNo() {
+        return start / pageSize + 1;
+    }
+
+    /**
+     * 是否有下一条
+     *
+     * @return
+     */
+    public boolean getHasNextPage() {
+        return getCurrentPageNo() < totalPages;
+    }
+
+    /**
+     * 当前页是否大于一
+     *
+     * @return
+     */
+    public boolean getHasPavPage() {
+        return getCurrentPageNo() > 1;
+    }
+
+    /**
+     * 获取总页数
+     *
+     * @return
+     */
+    public int getTotalPages() {
+        totalPages = totalCount / pageSize;
+
+        if (totalCount % pageSize != 0) {
+            totalPages++;
+        }
+
+        return totalPages;
+    }
+
+    public int getStart(int pageNo) {
+        this.pageNo = pageNo;
+        if (pageNo < 1) {
+            pageNo = 1;
+        }
+        start = (pageNo - 1) * pageSize;
+        return start;
+    }
+
+    public int getPageSize() {
+        return pageSize;
+    }
+
+    public void setPageSize(int pageSize) {
+        this.pageSize = pageSize;
+    }
+
+    public int getTotalCount() {
+        return totalCount;
+    }
+
+    public void setTotalCount(int totalCount) {
+        this.totalCount = totalCount;
+    }
+
+    public void setStart(int start) {
+        this.start = start;
+    }
+
+
+
+    public void setPageList(T pageList) {
+        this.pageList = pageList;
+    }
+
+    public T getPageList() {
+        return pageList;
+    }
+
+    public void setTotalPages(int totalPages) {
+        this.totalPages = totalPages;
+    }
+
+    public int getPageNo() {
+        return pageNo;
+    }
+
+    public void setPageNo(int pageNo) {
+        this.pageNo = pageNo;
+    }
+}

+ 618 - 0
src/main/java/com/usky/utils/RedisUtil.java

@@ -0,0 +1,618 @@
+package com.usky.utils;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.*;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * redis 工具类
+ *
+ * @Author Scott
+ */
+@Component
+public class RedisUtil {
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+
+    /**
+     * 指定缓存失效时间
+     *
+     * @param key  键
+     * @param time 时间(秒)
+     * @return
+     */
+    public boolean expire(String key, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.expire(key, time, TimeUnit.SECONDS);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 根据key 获取过期时间
+     *
+     * @param key 键 不能为null
+     * @return 时间(秒) 返回0代表为永久有效
+     */
+    public long getExpire(String key) {
+        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 判断key是否存在
+     *
+     * @param key 键
+     * @return true 存在 false不存在
+     */
+    public boolean hasKey(String key) {
+        try {
+            return redisTemplate.hasKey(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除缓存
+     *
+     * @param key 可以传一个值 或多个
+     */
+    @SuppressWarnings("unchecked")
+    public void del(String... key) {
+        if (key != null && key.length > 0) {
+            if (key.length == 1) {
+                redisTemplate.delete(key[0]);
+            } else {
+                redisTemplate.delete(CollectionUtils.arrayToList(key));
+            }
+        }
+    }
+
+    // ============================String=============================
+
+    /**
+     * 普通缓存获取
+     *
+     * @param key 键
+     * @return 值
+     */
+    public Object get(String key) {
+        return key == null ? null : redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 普通缓存放入
+     *
+     * @param key   键
+     * @param value 值
+     * @return true成功 false失败
+     */
+    public boolean set(String key, Object value) {
+        try {
+            redisTemplate.opsForValue().set(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+
+    }
+
+    /**
+     * 普通缓存放入并设置时间
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
+     * @return true成功 false 失败
+     */
+    public boolean set(String key, Object value, long time) {
+        try {
+            if (time > 0) {
+                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+            } else {
+                set(key, value);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 递增
+     *
+     * @param key 键
+     * @param by  要增加几(大于0)
+     * @return
+     */
+    public long incr(String key, long delta) {
+        if (delta < 0) {
+            throw new RuntimeException("递增因子必须大于0");
+        }
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+
+    /**
+     * 递减
+     *
+     * @param key 键
+     * @param by  要减少几(小于0)
+     * @return
+     */
+    public long decr(String key, long delta) {
+        if (delta < 0) {
+            throw new RuntimeException("递减因子必须大于0");
+        }
+        return redisTemplate.opsForValue().increment(key, -delta);
+    }
+
+    // ================================Map=================================
+
+    /**
+     * HashGet
+     *
+     * @param key  键 不能为null
+     * @param item 项 不能为null
+     * @return 值
+     */
+    public Object hget(String key, String item) {
+        return redisTemplate.opsForHash().get(key, item);
+    }
+
+    /**
+     * 获取hashKey对应的所有键值
+     *
+     * @param key 键
+     * @return 对应的多个键值
+     */
+    public Map<Object, Object> hmget(String key) {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * HashSet
+     *
+     * @param key 键
+     * @param map 对应多个键值
+     * @return true 成功 false 失败
+     */
+    public boolean hmset(String key, Map<String, Object> map) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * HashSet 并设置时间
+     *
+     * @param key  键
+     * @param map  对应多个键值
+     * @param time 时间(秒)
+     * @return true成功 false失败
+     */
+    public boolean hmset(String key, Map<String, Object> map, long time) {
+        try {
+            redisTemplate.opsForHash().putAll(key, map);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     *
+     * @param key   键
+     * @param item  项
+     * @param value 值
+     * @return true 成功 false失败
+     */
+    public boolean hset(String key, String item, Object value) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 向一张hash表中放入数据,如果不存在将创建
+     *
+     * @param key   键
+     * @param item  项
+     * @param value 值
+     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
+     * @return true 成功 false失败
+     */
+    public boolean hset(String key, String item, Object value, long time) {
+        try {
+            redisTemplate.opsForHash().put(key, item, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 删除hash表中的值
+     *
+     * @param key  键 不能为null
+     * @param item 项 可以使多个 不能为null
+     */
+    public void hdel(String key, Object... item) {
+        redisTemplate.opsForHash().delete(key, item);
+    }
+
+    /**
+     * 判断hash表中是否有该项的值
+     *
+     * @param key  键 不能为null
+     * @param item 项 不能为null
+     * @return true 存在 false不存在
+     */
+    public boolean hHasKey(String key, String item) {
+        return redisTemplate.opsForHash().hasKey(key, item);
+    }
+
+    /**
+     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
+     *
+     * @param key  键
+     * @param item 项
+     * @param by   要增加几(大于0)
+     * @return
+     */
+    public double hincr(String key, String item, double by) {
+        return redisTemplate.opsForHash().increment(key, item, by);
+    }
+
+    /**
+     * hash递减
+     *
+     * @param key  键
+     * @param item 项
+     * @param by   要减少记(小于0)
+     * @return
+     */
+    public double hdecr(String key, String item, double by) {
+        return redisTemplate.opsForHash().increment(key, item, -by);
+    }
+
+    // ============================set=============================
+
+    /**
+     * 根据key获取Set中的所有值
+     *
+     * @param key 键
+     * @return
+     */
+    public Set<Object> sGet(String key) {
+        try {
+            return redisTemplate.opsForSet().members(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 根据value从一个set中查询,是否存在
+     *
+     * @param key   键
+     * @param value 值
+     * @return true 存在 false不存在
+     */
+    public boolean sHasKey(String key, Object value) {
+        try {
+            return redisTemplate.opsForSet().isMember(key, value);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将数据放入set缓存
+     *
+     * @param key    键
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public long sSet(String key, Object... values) {
+        try {
+            return redisTemplate.opsForSet().add(key, values);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 将set数据放入缓存
+     *
+     * @param key    键
+     * @param time   时间(秒)
+     * @param values 值 可以是多个
+     * @return 成功个数
+     */
+    public long sSetAndTime(String key, long time, Object... values) {
+        try {
+            Long count = redisTemplate.opsForSet().add(key, values);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return count;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 获取set缓存的长度
+     *
+     * @param key 键
+     * @return
+     */
+    public long sGetSetSize(String key) {
+        try {
+            return redisTemplate.opsForSet().size(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 移除值为value的
+     *
+     * @param key    键
+     * @param values 值 可以是多个
+     * @return 移除的个数
+     */
+    public long setRemove(String key, Object... values) {
+        try {
+            Long count = redisTemplate.opsForSet().remove(key, values);
+            return count;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+    // ===============================list=================================
+
+    /**
+     * 获取list缓存的内容
+     *
+     * @param key   键
+     * @param start 开始
+     * @param end   结束 0 到 -1代表所有值
+     * @return
+     */
+    public List<Object> lGet(String key, long start, long end) {
+        try {
+            return redisTemplate.opsForList().range(key, start, end);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 获取list缓存的长度
+     *
+     * @param key 键
+     * @return
+     */
+    public long lGetListSize(String key) {
+        try {
+            return redisTemplate.opsForList().size(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 通过索引 获取list中的值
+     *
+     * @param key   键
+     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
+     * @return
+     */
+    public Object lGetIndex(String key, long index) {
+        try {
+            return redisTemplate.opsForList().index(key, index);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @return
+     */
+    public boolean lSet(String key, Object value) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒)
+     * @return
+     */
+    public boolean lSet(String key, Object value, long time) {
+        try {
+            redisTemplate.opsForList().rightPush(key, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @param
+     * @return
+     */
+    public boolean lSet(String key, List<Object> value) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 将list放入缓存
+     *
+     * @param key   键
+     * @param value 值
+     * @param time  时间(秒)
+     * @return
+     */
+    public boolean lSet(String key, List<Object> value, long time) {
+        try {
+            redisTemplate.opsForList().rightPushAll(key, value);
+            if (time > 0) {
+                expire(key, time);
+            }
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 根据索引修改list中的某条数据
+     *
+     * @param key   键
+     * @param index 索引
+     * @param value 值
+     * @return
+     */
+    public boolean lUpdateIndex(String key, long index, Object value) {
+        try {
+            redisTemplate.opsForList().set(key, index, value);
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 移除N个值为value
+     *
+     * @param key   键
+     * @param count 移除多少个
+     * @param value 值
+     * @return 移除的个数
+     */
+    public long lRemove(String key, long count, Object value) {
+        try {
+            Long remove = redisTemplate.opsForList().remove(key, count, value);
+            return remove;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+
+    /**
+     * 获取指定前缀的一系列key
+     * 使用scan命令代替keys, Redis是单线程处理,keys命令在KEY数量较多时,
+     * 操作效率极低【时间复杂度为O(N)】,该命令一旦执行会严重阻塞线上其它命令的正常请求
+     *
+     * @param keyPrefix
+     * @return
+     */
+    private Set<String> keys(String keyPrefix) {
+        String realKey = keyPrefix + "*";
+
+        try {
+            return redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
+                Set<String> binaryKeys = new HashSet<>();
+                Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(realKey).count(Integer.MAX_VALUE).build());
+                while (cursor.hasNext()) {
+                    binaryKeys.add(new String(cursor.next()));
+                }
+
+                return binaryKeys;
+            });
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    /**
+     * 删除指定前缀的一系列key
+     *
+     * @param keyPrefix
+     */
+    public void removeAll(String keyPrefix) {
+        try {
+            Set<String> keys = keys(keyPrefix);
+            redisTemplate.delete(keys);
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 33 - 0
src/main/java/com/usky/utils/RequestUtils.java

@@ -0,0 +1,33 @@
+package com.usky.utils;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.context.request.ServletWebRequest;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/11/26 9:36
+ * @description TODO
+ **/
+public class RequestUtils {
+
+    public static HttpServletRequest getRequest() {
+
+        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+    }
+    public static HttpServletResponse getResponse(){
+
+        HttpServletResponse resp = ((ServletWebRequest)RequestContextHolder.getRequestAttributes()).getResponse();
+
+
+
+        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+     //   HttpServletResponse response = attrs.getResponse();
+        HttpServletRequest request = attrs.getRequest();
+
+        return resp;
+    }
+}

+ 106 - 0
src/main/java/com/usky/utils/Result.java

@@ -0,0 +1,106 @@
+package com.usky.utils;
+
+import com.usky.constant.CommonConstant;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 统一数据结果集
+ *
+ * @param <T>
+ */
+@Data
+@ApiModel(value = "接口返回对象", description = "接口返回对象")
+public class Result<T> implements Serializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 成功标志
+     */
+    @ApiModelProperty(value = "成功标志")
+    private boolean success = true;
+
+    /**
+     * 返回处理消息
+     */
+    @ApiModelProperty(value = "返回处理消息")
+    private String message = "操作成功!";
+
+    /**
+     * 返回代码
+     */
+    @ApiModelProperty(value = "返回代码")
+    private Integer code = 0;
+
+    /**
+     * 返回数据对象 data
+     */
+    @ApiModelProperty(value = "返回数据对象")
+    private T data;
+
+    /**
+     * 时间戳
+     */
+    @ApiModelProperty(value = "时间戳")
+    private long timestamp = System.currentTimeMillis();
+
+    public Result() {
+
+    }
+
+    public Result<T> success(String message) {
+        this.message = message;
+        this.code = CommonConstant.SC_OK_200;
+        this.success = true;
+        return this;
+    }
+
+
+    public static <T> Result<T> OK() {
+        Result<T> r = new Result<T>();
+        r.setSuccess(true);
+        r.setCode(CommonConstant.SC_OK_200);
+        r.setMessage("操作成功");
+        return r;
+    }
+
+    public static <T> Result<T> OK(String msg) {
+        Result<T> r = new Result<T>();
+        r.setSuccess(true);
+        r.setCode(CommonConstant.SC_OK_200);
+        r.setMessage(msg);
+        return r;
+    }
+
+
+    public static <T> Result<T> OK(T data) {
+        Result<T> r = new Result<T>();
+        r.setSuccess(true);
+        r.setCode(CommonConstant.SC_OK_200);
+        r.setData(data);
+        return r;
+    }
+
+    public static <T> Result<T> OK(String msg, T data) {
+        Result<T> r = new Result<T>();
+        r.setSuccess(true);
+        r.setCode(CommonConstant.SC_OK_200);
+        r.setMessage(msg);
+        r.setData(data);
+        return r;
+    }
+
+    public static Result<Object> error(String msg) {
+        return error(CommonConstant.SC_INTERNAL_SERVER_ERROR_500, msg);
+    }
+
+    public static Result<Object> error(int code, String msg) {
+        Result<Object> r = new Result<Object>();
+        r.setCode(code);
+        r.setMessage(msg);
+        r.setSuccess(false);
+        return r;
+    }
+}

+ 137 - 0
src/main/java/com/usky/utils/ResultBase.java

@@ -0,0 +1,137 @@
+package com.usky.utils;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/2/20 14:59
+ * @description 接口统一返回结果
+ **/
+@ApiModel(value="统一操作结果集")
+@Data
+public class ResultBase<T> implements Serializable {
+    private static final long serialVersionUID = 1L;
+    @ApiModelProperty(value = "版本号")
+    private String VER = "1.00";
+    /**
+     * 成功标志
+     */
+    @ApiModelProperty(value = "成功标志")
+    private boolean success = true;
+
+    /**
+     * 返回处理消息
+     */
+    @ApiModelProperty(value = "返回处理消息")
+    private String message = "操作成功!";
+
+    /**
+     * 返回代码
+     */
+    @ApiModelProperty(value = "返回代码")
+    private Integer code = 0;
+
+    /**
+     * 返回数据对象 data
+     */
+    @ApiModelProperty(value = "返回数据对象")
+    private T data;
+
+    /**
+     * 时间戳
+     */
+    @ApiModelProperty(value = "时间戳")
+    private long timestamp = System.currentTimeMillis();
+    public ResultBase() {
+
+    }
+
+    public ResultBase<T> success(String message) {
+        this.message = message;
+        this.code = 200;
+        this.success = true;
+        return this;
+    }
+
+    public static ResultBase<Object> ok() {
+        ResultBase<Object> r = new ResultBase<Object>();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setMessage("成功");
+        return r;
+    }
+    public static ResultBase<Object> ok(String msg) {
+        ResultBase<Object> r = new ResultBase<Object>();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setMessage(msg);
+        return r;
+    }
+
+
+    public static ResultBase<Object> ok(Object data) {
+        ResultBase<Object> r = new ResultBase<Object>();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setData(data);
+        return r;
+    }
+
+    public static<T> ResultBase<T> OK() {
+        ResultBase<T> r = new ResultBase<T>();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setMessage("成功");
+        return r;
+    }
+
+    public static<T> ResultBase<T> OK(T data) {
+        ResultBase<T> r = new ResultBase<T>();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setData(data);
+        return r;
+    }
+
+    public static<T> ResultBase<T> OK(String msg, T data) {
+        ResultBase<T> r = new ResultBase<T>();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setMessage(msg);
+        r.setData(data);
+        return r;
+    }
+
+    public static ResultBase<Object> error(String msg) {
+        return error(500, msg);
+    }
+
+    public static ResultBase<Object> error(int code, String msg) {
+        ResultBase<Object> r = new ResultBase<Object>();
+        r.setCode(code);
+        r.setMessage(msg);
+        r.setSuccess(false);
+        return r;
+    }
+
+    public ResultBase<T> error500(String message) {
+        this.message = message;
+        this.code = 500;
+        this.success = false;
+        return this;
+    }
+    /**
+     * 无权限访问返回结果
+     */
+    public static ResultBase<Object> noauth(String msg) {
+        return error(510, msg);
+    }
+
+
+}

+ 122 - 0
src/main/java/com/usky/utils/ResultSocket.java

@@ -0,0 +1,122 @@
+package com.usky.utils;
+
+import com.usky.constant.CommonConstant;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * WebSocket统一数据结果集
+ *
+ * @param <T>
+ */
+@Data
+@ApiModel(value = "接口返回对象", description = "接口返回对象")
+public class ResultSocket<T> implements Serializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 成功标志
+     */
+    @ApiModelProperty(value = "成功标志")
+    private boolean success = true;
+
+    /**
+     * 返回处理消息
+     */
+    @ApiModelProperty(value = "返回处理消息")
+    private String message = "操作成功!";
+
+    /**
+     * 返回代码
+     */
+    @ApiModelProperty(value = "返回代码")
+    private Integer code = 0;
+    /**
+     * 数据推送类型
+     */
+    @ApiModelProperty(value = "数据类型 0 告警,1设备状态 ")
+    private Integer type = 0;
+
+    /**
+     * 返回数据对象 data
+     */
+    @ApiModelProperty(value = "返回数据对象")
+    private T data;
+
+    /**
+     * 时间戳
+     */
+    @ApiModelProperty(value = "时间戳")
+    private long timestamp = System.currentTimeMillis();
+
+    public ResultSocket() {
+
+    }
+
+    public ResultSocket<T> success(String message) {
+        this.message = message;
+        this.code = CommonConstant.SC_OK_200;
+        this.success = true;
+        this.type = 0;
+        return this;
+    }
+
+
+    public static <T> ResultSocket<T> OK() {
+        ResultSocket<T> r = new ResultSocket<T>();
+        r.setSuccess(true);
+        r.setCode(CommonConstant.SC_OK_200);
+        r.setMessage("操作成功");
+        return r;
+    }
+
+    public static <T> ResultSocket<T> OK(String msg) {
+        ResultSocket<T> r = new ResultSocket<T>();
+        r.setSuccess(true);
+        r.setCode(CommonConstant.SC_OK_200);
+        r.setMessage(msg);
+        return r;
+    }
+
+
+    public static <T> ResultSocket<T> OK(T data) {
+        ResultSocket<T> r = new ResultSocket<T>();
+        r.setSuccess(true);
+        r.setCode(CommonConstant.SC_OK_200);
+        r.setData(data);
+        return r;
+    }
+
+    public static <T> ResultSocket<T> OK(T data, Integer type) {
+        ResultSocket<T> r = new ResultSocket<T>();
+        r.setSuccess(true);
+        r.setCode(CommonConstant.SC_OK_200);
+        r.setData(data);
+        r.setType(type);
+        return r;
+    }
+
+    public static <T> ResultSocket<T> OK(String msg, T data) {
+        ResultSocket<T> r = new ResultSocket<T>();
+        r.setSuccess(true);
+        r.setCode(CommonConstant.SC_OK_200);
+        r.setMessage(msg);
+        r.setData(data);
+        return r;
+    }
+
+
+    public static ResultSocket<Object> error(String msg) {
+        return error(CommonConstant.SC_INTERNAL_SERVER_ERROR_500, msg);
+    }
+
+    public static ResultSocket<Object> error(int code, String msg) {
+        ResultSocket<Object> r = new ResultSocket<Object>();
+        r.setCode(code);
+        r.setMessage(msg);
+        r.setSuccess(false);
+        return r;
+    }
+}

+ 83 - 0
src/main/java/com/usky/utils/ResultSw.java

@@ -0,0 +1,83 @@
+package com.usky.utils;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Objects;
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/2/20 14:59
+ * @description 接口统一返回结果
+ **/
+
+@ApiModel(value="统一操作结果集swagger专用")
+public class ResultSw<T> implements Serializable {
+    /** 状态码 */
+    @ApiModelProperty("状态码")
+    private boolean flag;//后端返回结果正常为true,发生异常返回false
+    /** 状态描述 */
+    @ApiModelProperty("状态描述")
+    private String msg;
+    /** 业务数据 */
+    @ApiModelProperty("业务数据")
+    private List<T> data;
+
+
+    public ResultSw() {
+    }
+
+    public ResultSw(boolean flag, String msg, List<T> data) {
+        this.flag = flag;
+        this.msg = msg;
+        this.data = data;
+    }
+
+    public boolean isFlag() {
+        return flag;
+    }
+
+    public void setFlag(boolean flag) {
+        this.flag = flag;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ResultSw<?> resultSw = (ResultSw<?>) o;
+        return flag == resultSw.flag && Objects.equals(msg, resultSw.msg) && Objects.equals(data, resultSw.data);
+    }
+
+    @Override
+    public String toString() {
+        return "ResultSw{" +
+                "flag=" + flag +
+                ", msg='" + msg + '\'' +
+                ", data=" + data +
+                '}';
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(flag, msg, data);
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public List<T> getData() {
+        return data;
+    }
+
+    public void setData(List<T> data) {
+        this.data = data;
+    }
+}

+ 83 - 0
src/main/java/com/usky/utils/ResultSwaager.java

@@ -0,0 +1,83 @@
+package com.usky.utils;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/2/20 14:59
+ * @description 接口统一返回结果
+ **/
+@ApiModel(value="统一操作结果集swagger专用")
+public class ResultSwaager<T> implements Serializable {
+    /** 状态码 */
+    @ApiModelProperty("状态码")
+    private boolean flag;//后端返回结果正常为true,发生异常返回false
+    /** 状态描述 */
+    @ApiModelProperty("状态描述")
+    private String msg;
+    /** 业务数据 */
+    @ApiModelProperty("业务数据")
+    private T data;
+
+    @Override
+    public String toString() {
+        return "Result{" +
+                "flag=" + flag +
+                ", msg='" + msg + '\'' +
+                ", data=" + data +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ResultSwaager result = (ResultSwaager) o;
+        return flag == result.flag &&
+                Objects.equals(msg, result.msg) &&
+                Objects.equals(data, result.data);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(flag, msg, data);
+    }
+
+    public boolean isFlag() {
+        return flag;
+    }
+
+    public void setFlag(boolean flag) {
+        this.flag = flag;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public Object getData() {
+        return data;
+    }
+
+    public void setData(T data) {
+        this.data = data;
+    }
+
+    public ResultSwaager() {
+    }
+
+    public ResultSwaager(boolean flag, String msg, T data) {
+        this.flag = flag;
+        this.msg = msg;
+        this.data = data;
+    }
+}

+ 76 - 0
src/main/java/com/usky/utils/SmsUtils.java

@@ -0,0 +1,76 @@
+package com.usky.utils;
+
+import com.aliyuncs.CommonRequest;
+import com.aliyuncs.CommonResponse;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.exceptions.ClientException;
+import com.aliyuncs.exceptions.ServerException;
+import com.aliyuncs.http.MethodType;
+import com.aliyuncs.http.ProtocolType;
+import com.usky.config.sms.SmsProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+import static com.usky.config.sms.SmsConstants.*;
+
+@Slf4j
+@Component
+public class SmsUtils {
+    @Autowired
+    private IAcsClient acsClient;
+    @Autowired
+    private SmsProperties prop;
+
+
+    public Result sendMessage(String phoneNumbers, String signName, String template, String param) {
+        String s = null;
+        // 准备发短信的请求
+        CommonRequest request = new CommonRequest();
+        request.setProtocol(ProtocolType.HTTPS);
+        request.setMethod(MethodType.POST);
+        request.setDomain(prop.getDomain());
+        request.setVersion(prop.getVersion());
+        request.setAction(prop.getAction());
+        request.putQueryParameter(SMS_PARAM_REGION_ID, prop.getRegionID());
+        request.putQueryParameter(SMS_PARAM_KEY_PHONE, phoneNumbers);
+        request.putQueryParameter(SMS_PARAM_KEY_SIGN_NAME, signName);
+        request.putQueryParameter(SMS_PARAM_KEY_TEMPLATE_CODE, template);
+        request.putQueryParameter(SMS_PARAM_KEY_TEMPLATE_PARAM, param);
+        // 发起请求,获取响应
+        CommonResponse response = null;
+        try {
+            response = acsClient.getCommonResponse(request);
+            // 解析响应结果
+            if (response.getHttpStatus() >= 300) {
+                s = "验证码获取失败,请稍后重试";
+                log.error("【SMS服务】发送短信失败。响应信息:{}", response.getData());
+                return Result.error(s);
+            }
+            // 获取响应体
+            Map<String, String> resp = JsonUtils.toMap(response.getData(), String.class, String.class);
+            // 判断是否是成功
+            if (!StringUtils.equals(OK, resp.get(SMS_RESPONSE_KEY_CODE))) {
+                // 不成功,
+                s = "验证码获取失败,请稍后重试";
+                log.error("【SMS服务】发送短信失败,失败代码:{},原因{}",
+                        resp.get(SMS_RESPONSE_KEY_CODE), resp.get(SMS_RESPONSE_KEY_MESSAGE));
+                return Result.error(s);
+            } else {
+                s = "验证码获取成功";
+                return Result.OK(s);
+            }
+
+      //      log.info("【SMS服务】发送短信成功,手机号:{}, 响应:{}", phoneNumbers, response.getData());
+        } catch (ServerException e) {
+            log.error("【短信服务】发送短信失败,阿里服务端异常,{}", e.getMessage(), e);
+        } catch (ClientException e) {
+            log.error("【短信服务】发送短信失败,客户端异常,{}", e.getMessage(), e);
+
+        }
+        return null;
+    }
+}

+ 79 - 0
src/main/java/com/usky/utils/SpringContextUtils.java

@@ -0,0 +1,79 @@
+package com.usky.utils;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Component
+public class SpringContextUtils implements ApplicationContextAware {
+
+	/**
+	 * 上下文对象实例
+	 */
+	private static ApplicationContext applicationContext;
+
+	@Override
+	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+		SpringContextUtils.applicationContext = applicationContext;
+	}
+
+	/**
+	 * 获取applicationContext
+	 *
+	 * @return
+	 */
+	public static ApplicationContext getApplicationContext() {
+		return applicationContext;
+	}
+
+	/**
+	  * 获取HttpServletRequest
+	 */
+	public static HttpServletRequest getHttpServletRequest() {
+		return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+	}
+
+
+	public static String getOrigin(){
+		HttpServletRequest request = getHttpServletRequest();
+		return request.getHeader("Origin");
+	}
+	
+	/**
+	 * 通过name获取 Bean.
+	 *
+	 * @param name
+	 * @return
+	 */
+	public static Object getBean(String name) {
+		return getApplicationContext().getBean(name);
+	}
+
+	/**
+	 * 通过class获取Bean.
+	 *
+	 * @param clazz
+	 * @param       <T>
+	 * @return
+	 */
+	public static <T> T getBean(Class<T> clazz) {
+		return getApplicationContext().getBean(clazz);
+	}
+
+	/**
+	 * 通过name,以及Clazz返回指定的Bean
+	 *
+	 * @param name
+	 * @param clazz
+	 * @param       <T>
+	 * @return
+	 */
+	public static <T> T getBean(String name, Class<T> clazz) {
+		return getApplicationContext().getBean(name, clazz);
+	}
+}

+ 23 - 0
src/main/java/com/usky/utils/TimeUtil.java

@@ -0,0 +1,23 @@
+package com.usky.utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/5/31 13:01
+ * @description TODO
+ **/
+public class TimeUtil {
+    public static String getTime() {
+        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        String format = df.format(new Date());
+        return format;
+    }
+    public static String getTime(Date date) {
+        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        String format = df.format(date);
+        return format;
+    }
+}

+ 16 - 0
src/main/java/com/usky/utils/UUIDUtils.java

@@ -0,0 +1,16 @@
+package com.usky.utils;
+
+import java.util.UUID;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2020/11/26 9:09
+ * @description TODO
+ **/
+public class UUIDUtils {
+
+    public static String getUUID() {
+        return UUID.randomUUID().toString().replace("-", "").toLowerCase();
+    }
+}

+ 65 - 0
src/main/java/com/usky/webScoket/HttpAuthHandler.java

@@ -0,0 +1,65 @@
+package com.usky.webScoket;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.springframework.web.socket.handler.TextWebSocketHandler;
+
+import java.time.LocalDateTime;
+
+@Component
+@Slf4j
+public class HttpAuthHandler extends TextWebSocketHandler {
+    /**
+     * socket 建立成功事件
+     *
+     * @param session
+     * @throws Exception
+     */
+    @Override
+    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+        String id = session.getId();
+        System.out.println("id = " + id);
+      //  String query = session.getUri().getQuery();
+        if (null !=id) {
+            WsSessionManager.add(id, session);
+        }
+    }
+
+    /**
+     * 接收消息事件
+     * @param session
+     * @param message
+     * @throws Exception
+     */
+    @Override
+    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+        // 获得客户端传来的消息
+        String payload = message.getPayload();
+        Object token = session.getAttributes().get("token");
+        System.out.println("server 接收到 " + token + " 发送的 " + payload);
+  //  WsSessionManager.sendMessageToAll(new TextMessage("server 发送给 " + token + " 消息 " + payload + " " + LocalDateTime.now().toString()));
+    }
+
+    /**
+     * socket 断开连接时
+     *
+     * @param session
+     * @param status
+     * @throws Exception
+     */
+    @Override
+    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
+    //    String query = session.getUri().getQuery();
+        String id = session.getId();
+            // 用户退出,移除缓存
+            WsSessionManager.remove(id);
+            log.info("===================连接断开了===============");
+
+    }
+
+
+
+}

+ 59 - 0
src/main/java/com/usky/webScoket/MyInterceptor.java

@@ -0,0 +1,59 @@
+package com.usky.webScoket;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.server.HandshakeInterceptor;
+
+import java.util.Map;
+
+/**
+ * @author buhao
+ * @version MyInterceptor.java, v 0.1 2019-10-17 19:21 buhao
+ */
+@Component
+@Slf4j
+public class MyInterceptor implements HandshakeInterceptor {
+
+    /**
+     * 握手前
+     *
+     * @param request
+     * @param response
+     * @param wsHandler
+     * @param attributes
+     * @return
+     * @throws Exception
+     */
+    @Override
+    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
+   //  if (request instanceof ServletServerHttpRequest) {
+   //      ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
+   //      HttpSession session = servletRequest.getServletRequest().getSession(false);
+   //      if (session.getAttribute("keys") != null) {
+   //          String key = (String) session.getAttribute("keys");
+   //          key = "123456";
+   //      } else {
+   //          return false;
+   //      }
+   //  }
+     return true;
+    }
+
+    /**
+     * 握手后
+     *
+     * @param request
+     * @param response
+     * @param wsHandler
+     * @param exception
+     */
+    @Override
+    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
+
+        log.info("================webSocket连接建立=============");
+    }
+
+}

+ 27 - 0
src/main/java/com/usky/webScoket/WebSocketConfig.java

@@ -0,0 +1,27 @@
+package com.usky.webScoket;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
+import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
+
+/**
+ * @author buhao
+ * @version WebSocketConfig.java, v 0.1 2019-10-17 15:43 buhao
+ */
+@Configuration
+@EnableWebSocket
+public class WebSocketConfig implements WebSocketConfigurer {
+    @Autowired
+    private HttpAuthHandler httpAuthHandler;
+    @Autowired
+    private MyInterceptor myInterceptor;
+    @Override
+    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
+        registry
+                .addHandler(httpAuthHandler, "/top/WS/alarmPush")
+                .addInterceptors(myInterceptor)
+                .setAllowedOrigins("*");
+    }
+}

+ 94 - 0
src/main/java/com/usky/webScoket/WsSessionManager.java

@@ -0,0 +1,94 @@
+package com.usky.webScoket;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+@Slf4j
+public class WsSessionManager {
+    /**
+     * 保存连接 session 的地方
+     */
+    private static ConcurrentHashMap<String, WebSocketSession> SESSION_POOL = new ConcurrentHashMap<>();
+
+    /**
+     * 添加 session
+     *
+     * @param key
+     */
+    public static void add(String key, WebSocketSession session) {
+        // 添加 session
+        SESSION_POOL.put(key, session);
+    }
+
+    /**
+     * 删除 session,会返回删除的 session
+     *
+     * @param key
+     * @return
+     */
+    public static WebSocketSession remove(String key) {
+        // 删除 session
+        return SESSION_POOL.remove(key);
+    }
+
+    /**
+     * 删除并同步关闭连接
+     *
+     * @param key
+     */
+    public static void removeAndClose(String key) {
+        WebSocketSession session = remove(key);
+        if (session != null) {
+            try {
+                // 关闭连接
+                session.close();
+            } catch (IOException e) {
+                // todo: 关闭出现异常处理
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * 获得 session
+     *
+     * @param key
+     * @return
+     */
+    public static WebSocketSession get(String key) {
+        // 获得 session
+        return SESSION_POOL.get(key);
+    }
+
+    public static void sendMessageToAll(final TextMessage message) {
+        //对用户发送的消息内容进行转义
+        //获取到所有在线用户的SocketSession对象
+        Set<Map.Entry<String, WebSocketSession>> entrySet = SESSION_POOL.entrySet();
+        for (Map.Entry<String, WebSocketSession> entry : entrySet) {
+            //某用户的WebSocketSession
+            final WebSocketSession webSocketSession = entry.getValue();
+            //判断连接是否仍然打开的
+            if (webSocketSession.isOpen()) {
+                //开启多线程发送消息
+                new Thread(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            if (webSocketSession.isOpen()) {
+                                webSocketSession.sendMessage(message);
+                            }
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }).start();
+
+            }
+        }
+    }
+
+
+}

+ 287 - 0
src/main/java/com/usky/xml/CDL.java

@@ -0,0 +1,287 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+/**
+ * This provides static methods to convert comma delimited text into a
+ * JSONArray, and to convert a JSONArray into comma delimited text. Comma
+ * delimited text is a very popular format for data interchange. It is
+ * understood by most database, spreadsheet, and organizer programs.
+ * <p>
+ * Each row of text represents a row in a table or a data record. Each row
+ * ends with a NEWLINE character. Each row contains one or more values.
+ * Values are separated by commas. A value can contain any character except
+ * for comma, unless is is wrapped in single quotes or double quotes.
+ * <p>
+ * The first row usually contains the names of the columns.
+ * <p>
+ * A comma delimited list can be converted into a JSONArray of JSONObjects.
+ * The names for the elements in the JSONObjects can be taken from the names
+ * in the first row.
+ * @author JSON.org
+ * @version 2016-05-01
+ */
+public class CDL {
+
+    /**
+     * Get the next value. The value can be wrapped in quotes. The value can
+     * be empty.
+     * @param x A JSONTokener of the source text.
+     * @return The value string, or null if empty.
+     * @throws JSONException if the quoted string is badly formed.
+     */
+    private static String getValue(JSONTokener x) throws JSONException {
+        char c;
+        char q;
+        StringBuilder sb;
+        do {
+            c = x.next();
+        } while (c == ' ' || c == '\t');
+        switch (c) {
+        case 0:
+            return null;
+        case '"':
+        case '\'':
+            q = c;
+            sb = new StringBuilder();
+            for (;;) {
+                c = x.next();
+                if (c == q) {
+                    //Handle escaped double-quote
+                    char nextC = x.next();
+                    if(nextC != '\"') {
+                        // if our quote was the end of the file, don't step
+                        if(nextC > 0) {
+                            x.back();
+                        }
+                        break;
+                    }
+                }
+                if (c == 0 || c == '\n' || c == '\r') {
+                    throw x.syntaxError("Missing close quote '" + q + "'.");
+                }
+                sb.append(c);
+            }
+            return sb.toString();
+        case ',':
+            x.back();
+            return "";
+        default:
+            x.back();
+            return x.nextTo(',');
+        }
+    }
+
+    /**
+     * Produce a JSONArray of strings from a row of comma delimited values.
+     * @param x A JSONTokener of the source text.
+     * @return A JSONArray of strings.
+     * @throws JSONException if a called function fails
+     */
+    public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException {
+        JSONArray ja = new JSONArray();
+        for (;;) {
+            String value = getValue(x);
+            char c = x.next();
+            if (value == null ||
+                    (ja.length() == 0 && value.length() == 0 && c != ',')) {
+                return null;
+            }
+            ja.put(value);
+            for (;;) {
+                if (c == ',') {
+                    break;
+                }
+                if (c != ' ') {
+                    if (c == '\n' || c == '\r' || c == 0) {
+                        return ja;
+                    }
+                    throw x.syntaxError("Bad character '" + c + "' (" +
+                            (int)c + ").");
+                }
+                c = x.next();
+            }
+        }
+    }
+
+    /**
+     * Produce a JSONObject from a row of comma delimited text, using a
+     * parallel JSONArray of strings to provides the names of the elements.
+     * @param names A JSONArray of names. This is commonly obtained from the
+     *  first row of a comma delimited text file using the rowToJSONArray
+     *  method.
+     * @param x A JSONTokener of the source text.
+     * @return A JSONObject combining the names and values.
+     * @throws JSONException if a called function fails
+     */
+    public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x)
+            throws JSONException {
+        JSONArray ja = rowToJSONArray(x);
+        return ja != null ? ja.toJSONObject(names) :  null;
+    }
+
+    /**
+     * Produce a comma delimited text row from a JSONArray. Values containing
+     * the comma character will be quoted. Troublesome characters may be
+     * removed.
+     * @param ja A JSONArray of strings.
+     * @return A string ending in NEWLINE.
+     */
+    public static String rowToString(JSONArray ja) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < ja.length(); i += 1) {
+            if (i > 0) {
+                sb.append(',');
+            }
+            Object object = ja.opt(i);
+            if (object != null) {
+                String string = object.toString();
+                if (string.length() > 0 && (string.indexOf(',') >= 0 ||
+                        string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 ||
+                        string.indexOf(0) >= 0 || string.charAt(0) == '"')) {
+                    sb.append('"');
+                    int length = string.length();
+                    for (int j = 0; j < length; j += 1) {
+                        char c = string.charAt(j);
+                        if (c >= ' ' && c != '"') {
+                            sb.append(c);
+                        }
+                    }
+                    sb.append('"');
+                } else {
+                    sb.append(string);
+                }
+            }
+        }
+        sb.append('\n');
+        return sb.toString();
+    }
+
+    /**
+     * Produce a JSONArray of JSONObjects from a comma delimited text string,
+     * using the first row as a source of names.
+     * @param string The comma delimited text.
+     * @return A JSONArray of JSONObjects.
+     * @throws JSONException if a called function fails
+     */
+    public static JSONArray toJSONArray(String string) throws JSONException {
+        return toJSONArray(new JSONTokener(string));
+    }
+
+    /**
+     * Produce a JSONArray of JSONObjects from a comma delimited text string,
+     * using the first row as a source of names.
+     * @param x The JSONTokener containing the comma delimited text.
+     * @return A JSONArray of JSONObjects.
+     * @throws JSONException if a called function fails
+     */
+    public static JSONArray toJSONArray(JSONTokener x) throws JSONException {
+        return toJSONArray(rowToJSONArray(x), x);
+    }
+
+    /**
+     * Produce a JSONArray of JSONObjects from a comma delimited text string
+     * using a supplied JSONArray as the source of element names.
+     * @param names A JSONArray of strings.
+     * @param string The comma delimited text.
+     * @return A JSONArray of JSONObjects.
+     * @throws JSONException if a called function fails
+     */
+    public static JSONArray toJSONArray(JSONArray names, String string)
+            throws JSONException {
+        return toJSONArray(names, new JSONTokener(string));
+    }
+
+    /**
+     * Produce a JSONArray of JSONObjects from a comma delimited text string
+     * using a supplied JSONArray as the source of element names.
+     * @param names A JSONArray of strings.
+     * @param x A JSONTokener of the source text.
+     * @return A JSONArray of JSONObjects.
+     * @throws JSONException if a called function fails
+     */
+    public static JSONArray toJSONArray(JSONArray names, JSONTokener x)
+            throws JSONException {
+        if (names == null || names.length() == 0) {
+            return null;
+        }
+        JSONArray ja = new JSONArray();
+        for (;;) {
+            JSONObject jo = rowToJSONObject(names, x);
+            if (jo == null) {
+                break;
+            }
+            ja.put(jo);
+        }
+        if (ja.length() == 0) {
+            return null;
+        }
+        return ja;
+    }
+
+
+    /**
+     * Produce a comma delimited text from a JSONArray of JSONObjects. The
+     * first row will be a list of names obtained by inspecting the first
+     * JSONObject.
+     * @param ja A JSONArray of JSONObjects.
+     * @return A comma delimited text.
+     * @throws JSONException if a called function fails
+     */
+    public static String toString(JSONArray ja) throws JSONException {
+        JSONObject jo = ja.optJSONObject(0);
+        if (jo != null) {
+            JSONArray names = jo.names();
+            if (names != null) {
+                return rowToString(names) + toString(names, ja);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Produce a comma delimited text from a JSONArray of JSONObjects using
+     * a provided list of names. The list of names is not included in the
+     * output.
+     * @param names A JSONArray of strings.
+     * @param ja A JSONArray of JSONObjects.
+     * @return A comma delimited text.
+     * @throws JSONException if a called function fails
+     */
+    public static String toString(JSONArray names, JSONArray ja)
+            throws JSONException {
+        if (names == null || names.length() == 0) {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < ja.length(); i += 1) {
+            JSONObject jo = ja.optJSONObject(i);
+            if (jo != null) {
+                sb.append(rowToString(jo.toJSONArray(names)));
+            }
+        }
+        return sb.toString();
+    }
+}

+ 224 - 0
src/main/java/com/usky/xml/Cookie.java

@@ -0,0 +1,224 @@
+package com.usky.xml;
+
+import java.util.Locale;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/**
+ * Convert a web browser cookie specification to a JSONObject and back.
+ * JSON and Cookies are both notations for name/value pairs.
+ * See also: <a href="https://tools.ietf.org/html/rfc6265">https://tools.ietf.org/html/rfc6265</a>
+ * @author JSON.org
+ * @version 2015-12-09
+ */
+public class Cookie {
+
+    /**
+     * Produce a copy of a string in which the characters '+', '%', '=', ';'
+     * and control characters are replaced with "%hh". This is a gentle form
+     * of URL encoding, attempting to cause as little distortion to the
+     * string as possible. The characters '=' and ';' are meta characters in
+     * cookies. By convention, they are escaped using the URL-encoding. This is
+     * only a convention, not a standard. Often, cookies are expected to have
+     * encoded values. We encode '=' and ';' because we must. We encode '%' and
+     * '+' because they are meta characters in URL encoding.
+     * @param string The source string.
+     * @return       The escaped result.
+     */
+    public static String escape(String string) {
+        char            c;
+        String          s = string.trim();
+        int             length = s.length();
+        StringBuilder   sb = new StringBuilder(length);
+        for (int i = 0; i < length; i += 1) {
+            c = s.charAt(i);
+            if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') {
+                sb.append('%');
+                sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16));
+                sb.append(Character.forDigit((char)(c & 0x0f), 16));
+            } else {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+
+    /**
+     * Convert a cookie specification string into a JSONObject. The string
+     * must contain a name value pair separated by '='. The name and the value
+     * will be unescaped, possibly converting '+' and '%' sequences. The
+     * cookie properties may follow, separated by ';', also represented as
+     * name=value (except the Attribute properties like "Secure" or "HttpOnly",
+     * which do not have a value. The value {@link Boolean#TRUE} will be used for these).
+     * The name will be stored under the key "name", and the value will be
+     * stored under the key "value". This method does not do checking or
+     * validation of the parameters. It only converts the cookie string into
+     * a JSONObject. All attribute names are converted to lower case keys in the
+     * JSONObject (HttpOnly =&gt; httponly). If an attribute is specified more than
+     * once, only the value found closer to the end of the cookie-string is kept.
+     * @param string The cookie specification string.
+     * @return A JSONObject containing "name", "value", and possibly other
+     *  members.
+     * @throws JSONException If there is an error parsing the Cookie String.
+     * Cookie strings must have at least one '=' character and the 'name'
+     * portion of the cookie must not be blank.
+     */
+    public static JSONObject toJSONObject(String string) {
+        final JSONObject     jo = new JSONObject();
+        String         name;
+        Object         value;
+        
+        
+        JSONTokener x = new JSONTokener(string);
+        
+        name = unescape(x.nextTo('=').trim());
+        //per RFC6265, if the name is blank, the cookie should be ignored.
+        if("".equals(name)) {
+            throw new JSONException("Cookies must have a 'name'");
+        }
+        jo.put("name", name);
+        // per RFC6265, if there is no '=', the cookie should be ignored.
+        // the 'next' call here throws an exception if the '=' is not found.
+        x.next('=');
+        jo.put("value", unescape(x.nextTo(';')).trim());
+        // discard the ';'
+        x.next();
+        // parse the remaining cookie attributes
+        while (x.more()) {
+            name = unescape(x.nextTo("=;")).trim().toLowerCase(Locale.ROOT);
+            // don't allow a cookies attributes to overwrite it's name or value.
+            if("name".equalsIgnoreCase(name)) {
+                throw new JSONException("Illegal attribute name: 'name'");
+            }
+            if("value".equalsIgnoreCase(name)) {
+                throw new JSONException("Illegal attribute name: 'value'");
+            }
+            // check to see if it's a flag property
+            if (x.next() != '=') {
+                value = Boolean.TRUE;
+            } else {
+                value = unescape(x.nextTo(';')).trim();
+                x.next();
+            }
+            // only store non-blank attributes
+            if(!"".equals(name) && !"".equals(value)) {
+                jo.put(name, value);
+            }
+        }
+        return jo;
+    }
+
+
+    /**
+     * Convert a JSONObject into a cookie specification string. The JSONObject
+     * must contain "name" and "value" members (case insensitive).
+     * If the JSONObject contains other members, they will be appended to the cookie
+     * specification string. User-Agents are instructed to ignore unknown attributes,
+     * so ensure your JSONObject is using only known attributes.
+     * See also: <a href="https://tools.ietf.org/html/rfc6265">https://tools.ietf.org/html/rfc6265</a>
+     * @param jo A JSONObject
+     * @return A cookie specification string
+     * @throws JSONException thrown if the cookie has no name.
+     */
+    public static String toString(JSONObject jo) throws JSONException {
+        StringBuilder sb = new StringBuilder();
+        
+        String name = null;
+        Object value = null;
+        for(String key : jo.keySet()){
+            if("name".equalsIgnoreCase(key)) {
+                name = jo.getString(key).trim();
+            }
+            if("value".equalsIgnoreCase(key)) {
+                value=jo.getString(key).trim();
+            }
+            if(name != null && value != null) {
+                break;
+            }
+        }
+        
+        if(name == null || "".equals(name.trim())) {
+            throw new JSONException("Cookie does not have a name");
+        }
+        if(value == null) {
+            value = "";
+        }
+        
+        sb.append(escape(name));
+        sb.append("=");
+        sb.append(escape((String)value));
+        
+        for(String key : jo.keySet()){
+            if("name".equalsIgnoreCase(key)
+                    || "value".equalsIgnoreCase(key)) {
+                // already processed above
+                continue;
+            }
+            value = jo.opt(key);
+            if(value instanceof Boolean) {
+                if(Boolean.TRUE.equals(value)) {
+                    sb.append(';').append(escape(key));
+                }
+                // don't emit false values
+            } else {
+                sb.append(';')
+                    .append(escape(key))
+                    .append('=')
+                    .append(escape(value.toString()));
+            }
+        }
+        
+        return sb.toString();
+    }
+
+    /**
+     * Convert <code>%</code><i>hh</i> sequences to single characters, and
+     * convert plus to space.
+     * @param string A string that may contain
+     *      <code>+</code>&nbsp;<small>(plus)</small> and
+     *      <code>%</code><i>hh</i> sequences.
+     * @return The unescaped string.
+     */
+    public static String unescape(String string) {
+        int length = string.length();
+        StringBuilder sb = new StringBuilder(length);
+        for (int i = 0; i < length; ++i) {
+            char c = string.charAt(i);
+            if (c == '+') {
+                c = ' ';
+            } else if (c == '%' && i + 2 < length) {
+                int d = JSONTokener.dehexchar(string.charAt(i + 1));
+                int e = JSONTokener.dehexchar(string.charAt(i + 2));
+                if (d >= 0 && e >= 0) {
+                    c = (char)(d * 16 + e);
+                    i += 2;
+                }
+            }
+            sb.append(c);
+        }
+        return sb.toString();
+    }
+}

+ 86 - 0
src/main/java/com/usky/xml/CookieList.java

@@ -0,0 +1,86 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+/**
+ * Convert a web browser cookie list string to a JSONObject and back.
+ * @author JSON.org
+ * @version 2015-12-09
+ */
+public class CookieList {
+
+    /**
+     * Convert a cookie list into a JSONObject. A cookie list is a sequence
+     * of name/value pairs. The names are separated from the values by '='.
+     * The pairs are separated by ';'. The names and the values
+     * will be unescaped, possibly converting '+' and '%' sequences.
+     *
+     * To add a cookie to a cookie list,
+     * cookielistJSONObject.put(cookieJSONObject.getString("name"),
+     *     cookieJSONObject.getString("value"));
+     * @param string  A cookie list string
+     * @return A JSONObject
+     * @throws JSONException if a called function fails
+     */
+    public static JSONObject toJSONObject(String string) throws JSONException {
+        JSONObject jo = new JSONObject();
+        JSONTokener x = new JSONTokener(string);
+        while (x.more()) {
+            String name = Cookie.unescape(x.nextTo('='));
+            x.next('=');
+            jo.put(name, Cookie.unescape(x.nextTo(';')));
+            x.next();
+        }
+        return jo;
+    }
+
+    /**
+     * Convert a JSONObject into a cookie list. A cookie list is a sequence
+     * of name/value pairs. The names are separated from the values by '='.
+     * The pairs are separated by ';'. The characters '%', '+', '=', and ';'
+     * in the names and values are replaced by "%hh".
+     * @param jo A JSONObject
+     * @return A cookie list string
+     * @throws JSONException if a called function fails
+     */
+    public static String toString(JSONObject jo) throws JSONException {
+        boolean             b = false;
+        final StringBuilder sb = new StringBuilder();
+        // Don't use the new entrySet API to maintain Android support
+        for (final String key : jo.keySet()) {
+            final Object value = jo.opt(key);
+            if (!JSONObject.NULL.equals(value)) {
+                if (b) {
+                    sb.append(';');
+                }
+                sb.append(Cookie.escape(key));
+                sb.append("=");
+                sb.append(Cookie.escape(value.toString()));
+                b = true;
+            }
+        }
+        return sb.toString();
+    }
+}

+ 162 - 0
src/main/java/com/usky/xml/HTTP.java

@@ -0,0 +1,162 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import java.util.Locale;
+
+/**
+ * Convert an HTTP header to a JSONObject and back.
+ * @author JSON.org
+ * @version 2015-12-09
+ */
+public class HTTP {
+
+    /** Carriage return/line feed. */
+    public static final String CRLF = "\r\n";
+
+    /**
+     * Convert an HTTP header string into a JSONObject. It can be a request
+     * header or a response header. A request header will contain
+     * <pre>{
+     *    Method: "POST" (for example),
+     *    "Request-URI": "/" (for example),
+     *    "HTTP-Version": "HTTP/1.1" (for example)
+     * }</pre>
+     * A response header will contain
+     * <pre>{
+     *    "HTTP-Version": "HTTP/1.1" (for example),
+     *    "Status-Code": "200" (for example),
+     *    "Reason-Phrase": "OK" (for example)
+     * }</pre>
+     * In addition, the other parameters in the header will be captured, using
+     * the HTTP field names as JSON names, so that <pre>{@code
+     *    Date: Sun, 26 May 2002 18:06:04 GMT
+     *    Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s
+     *    Cache-Control: no-cache}</pre>
+     * become
+     * <pre>{@code
+     *    Date: "Sun, 26 May 2002 18:06:04 GMT",
+     *    Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s",
+     *    "Cache-Control": "no-cache",
+     * ...}</pre>
+     * It does no further checking or conversion. It does not parse dates.
+     * It does not do '%' transforms on URLs.
+     * @param string An HTTP header string.
+     * @return A JSONObject containing the elements and attributes
+     * of the XML string.
+     * @throws JSONException if a called function fails
+     */
+    public static JSONObject toJSONObject(String string) throws JSONException {
+        JSONObject     jo = new JSONObject();
+        HTTPTokener x = new HTTPTokener(string);
+        String         token;
+
+        token = x.nextToken();
+        if (token.toUpperCase(Locale.ROOT).startsWith("HTTP")) {
+
+// Response
+
+            jo.put("HTTP-Version", token);
+            jo.put("Status-Code", x.nextToken());
+            jo.put("Reason-Phrase", x.nextTo('\0'));
+            x.next();
+
+        } else {
+
+// Request
+
+            jo.put("Method", token);
+            jo.put("Request-URI", x.nextToken());
+            jo.put("HTTP-Version", x.nextToken());
+        }
+
+// Fields
+
+        while (x.more()) {
+            String name = x.nextTo(':');
+            x.next(':');
+            jo.put(name, x.nextTo('\0'));
+            x.next();
+        }
+        return jo;
+    }
+
+
+    /**
+     * Convert a JSONObject into an HTTP header. A request header must contain
+     * <pre>{
+     *    Method: "POST" (for example),
+     *    "Request-URI": "/" (for example),
+     *    "HTTP-Version": "HTTP/1.1" (for example)
+     * }</pre>
+     * A response header must contain
+     * <pre>{
+     *    "HTTP-Version": "HTTP/1.1" (for example),
+     *    "Status-Code": "200" (for example),
+     *    "Reason-Phrase": "OK" (for example)
+     * }</pre>
+     * Any other members of the JSONObject will be output as HTTP fields.
+     * The result will end with two CRLF pairs.
+     * @param jo A JSONObject
+     * @return An HTTP header string.
+     * @throws JSONException if the object does not contain enough
+     *  information.
+     */
+    public static String toString(JSONObject jo) throws JSONException {
+        StringBuilder       sb = new StringBuilder();
+        if (jo.has("Status-Code") && jo.has("Reason-Phrase")) {
+            sb.append(jo.getString("HTTP-Version"));
+            sb.append(' ');
+            sb.append(jo.getString("Status-Code"));
+            sb.append(' ');
+            sb.append(jo.getString("Reason-Phrase"));
+        } else if (jo.has("Method") && jo.has("Request-URI")) {
+            sb.append(jo.getString("Method"));
+            sb.append(' ');
+            sb.append('"');
+            sb.append(jo.getString("Request-URI"));
+            sb.append('"');
+            sb.append(' ');
+            sb.append(jo.getString("HTTP-Version"));
+        } else {
+            throw new JSONException("Not enough material for an HTTP header.");
+        }
+        sb.append(CRLF);
+        // Don't use the new entrySet API to maintain Android support
+        for (final String key : jo.keySet()) {
+            String value = jo.optString(key);
+            if (!"HTTP-Version".equals(key)      && !"Status-Code".equals(key) &&
+                    !"Reason-Phrase".equals(key) && !"Method".equals(key) &&
+                    !"Request-URI".equals(key)   && !JSONObject.NULL.equals(value)) {
+                sb.append(key);
+                sb.append(": ");
+                sb.append(jo.optString(key));
+                sb.append(CRLF);
+            }
+        }
+        sb.append(CRLF);
+        return sb.toString();
+    }
+}

+ 77 - 0
src/main/java/com/usky/xml/HTTPTokener.java

@@ -0,0 +1,77 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/**
+ * The HTTPTokener extends the JSONTokener to provide additional methods
+ * for the parsing of HTTP headers.
+ * @author JSON.org
+ * @version 2015-12-09
+ */
+public class HTTPTokener extends JSONTokener {
+
+    /**
+     * Construct an HTTPTokener from a string.
+     * @param string A source string.
+     */
+    public HTTPTokener(String string) {
+        super(string);
+    }
+
+
+    /**
+     * Get the next token or string. This is used in parsing HTTP headers.
+     * @return A String.
+     * @throws JSONException if a syntax error occurs
+     */
+    public String nextToken() throws JSONException {
+        char c;
+        char q;
+        StringBuilder sb = new StringBuilder();
+        do {
+            c = next();
+        } while (Character.isWhitespace(c));
+        if (c == '"' || c == '\'') {
+            q = c;
+            for (;;) {
+                c = next();
+                if (c < ' ') {
+                    throw syntaxError("Unterminated string.");
+                }
+                if (c == q) {
+                    return sb.toString();
+                }
+                sb.append(c);
+            }
+        }
+        for (;;) {
+            if (c == 0 || Character.isWhitespace(c)) {
+                return sb.toString();
+            }
+            sb.append(c);
+            c = next();
+        }
+    }
+}

+ 1710 - 0
src/main/java/com/usky/xml/JSONArray.java

@@ -0,0 +1,1710 @@
+package com.usky.xml;
+
+/*
+ Copyright (c) 2002 JSON.org
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ The Software shall be used for Good, not Evil.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.*;
+
+
+/**
+ * A JSONArray is an ordered sequence of values. Its external text form is a
+ * string wrapped in square brackets with commas separating the values. The
+ * internal form is an object having <code>get</code> and <code>opt</code>
+ * methods for accessing the values by index, and <code>put</code> methods for
+ * adding or replacing values. The values can be any of these types:
+ * <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>,
+ * <code>Number</code>, <code>String</code>, or the
+ * <code>JSONObject.NULL object</code>.
+ * <p>
+ * The constructor can convert a JSON text into a Java object. The
+ * <code>toString</code> method converts to JSON text.
+ * <p>
+ * A <code>get</code> method returns a value if one can be found, and throws an
+ * exception if one cannot be found. An <code>opt</code> method returns a
+ * default value instead of throwing an exception, and so is useful for
+ * obtaining optional values.
+ * <p>
+ * The generic <code>get()</code> and <code>opt()</code> methods return an
+ * object which you can cast or query for type. There are also typed
+ * <code>get</code> and <code>opt</code> methods that do type checking and type
+ * coercion for you.
+ * <p>
+ * The texts produced by the <code>toString</code> methods strictly conform to
+ * JSON syntax rules. The constructors are more forgiving in the texts they will
+ * accept:
+ * <ul>
+ * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just
+ * before the closing bracket.</li>
+ * <li>The <code>null</code> value will be inserted when there is <code>,</code>
+ * &nbsp;<small>(comma)</small> elision.</li>
+ * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single
+ * quote)</small>.</li>
+ * <li>Strings do not need to be quoted at all if they do not begin with a quote
+ * or single quote, and if they do not contain leading or trailing spaces, and
+ * if they do not contain any of these characters:
+ * <code>{ } [ ] / \ : , #</code> and if they do not look like numbers and
+ * if they are not the reserved words <code>true</code>, <code>false</code>, or
+ * <code>null</code>.</li>
+ * </ul>
+ *
+ * @author JSON.org
+ * @version 2016-08/15
+ */
+public class JSONArray implements Iterable<Object> {
+
+    /**
+     * The arrayList where the JSONArray's properties are kept.
+     */
+    private final ArrayList<Object> myArrayList;
+
+    /**
+     * Construct an empty JSONArray.
+     */
+    public JSONArray() {
+        this.myArrayList = new ArrayList<Object>();
+    }
+
+    /**
+     * Construct a JSONArray from a JSONTokener.
+     *
+     * @param x
+     *            A JSONTokener
+     * @throws JSONException
+     *             If there is a syntax error.
+     */
+    public JSONArray(JSONTokener x) throws JSONException {
+        this();
+        if (x.nextClean() != '[') {
+            throw x.syntaxError("A JSONArray text must start with '['");
+        }
+        
+        char nextChar = x.nextClean();
+        if (nextChar == 0) {
+            // array is unclosed. No ']' found, instead EOF
+            throw x.syntaxError("Expected a ',' or ']'");
+        }
+        if (nextChar != ']') {
+            x.back();
+            for (;;) {
+                if (x.nextClean() == ',') {
+                    x.back();
+                    this.myArrayList.add(JSONObject.NULL);
+                } else {
+                    x.back();
+                    this.myArrayList.add(x.nextValue());
+                }
+                switch (x.nextClean()) {
+                case 0:
+                    // array is unclosed. No ']' found, instead EOF
+                    throw x.syntaxError("Expected a ',' or ']'");
+                case ',':
+                    nextChar = x.nextClean();
+                    if (nextChar == 0) {
+                        // array is unclosed. No ']' found, instead EOF
+                        throw x.syntaxError("Expected a ',' or ']'");
+                    }
+                    if (nextChar == ']') {
+                        return;
+                    }
+                    x.back();
+                    break;
+                case ']':
+                    return;
+                default:
+                    throw x.syntaxError("Expected a ',' or ']'");
+                }
+            }
+        }
+    }
+
+    /**
+     * Construct a JSONArray from a source JSON text.
+     *
+     * @param source
+     *            A string that begins with <code>[</code>&nbsp;<small>(left
+     *            bracket)</small> and ends with <code>]</code>
+     *            &nbsp;<small>(right bracket)</small>.
+     * @throws JSONException
+     *             If there is a syntax error.
+     */
+    public JSONArray(String source) throws JSONException {
+        this(new JSONTokener(source));
+    }
+
+    /**
+     * Construct a JSONArray from a Collection.
+     *
+     * @param collection
+     *            A Collection.
+     */
+    public JSONArray(Collection<?> collection) {
+        if (collection == null) {
+            this.myArrayList = new ArrayList<Object>();
+        } else {
+            this.myArrayList = new ArrayList<Object>(collection.size());
+            this.addAll(collection, true);
+        }
+    }
+
+    /**
+     * Construct a JSONArray from an Iterable. This is a shallow copy.
+     *
+     * @param iter
+     *            A Iterable collection.
+     */
+    public JSONArray(Iterable<?> iter) {
+        this();
+        if (iter == null) {
+            return;
+        }
+        this.addAll(iter, true);
+    }
+
+    /**
+     * Construct a JSONArray from another JSONArray. This is a shallow copy.
+     *
+     * @param array
+     *            A array.
+     */
+    public JSONArray(JSONArray array) {
+        if (array == null) {
+            this.myArrayList = new ArrayList<Object>();
+        } else {
+            // shallow copy directly the internal array lists as any wrapping
+            // should have been done already in the original JSONArray
+            this.myArrayList = new ArrayList<Object>(array.myArrayList);
+        }
+    }
+
+    /**
+     * Construct a JSONArray from an array.
+     *
+     * @param array
+     *            Array. If the parameter passed is null, or not an array, an
+     *            exception will be thrown.
+     *
+     * @throws JSONException
+     *            If not an array or if an array value is non-finite number.
+     * @throws NullPointerException
+     *            Thrown if the array parameter is null.
+     */
+    public JSONArray(Object array) throws JSONException {
+        this();
+        if (!array.getClass().isArray()) {
+            throw new JSONException(
+                    "JSONArray initial value should be a string or collection or array.");
+        }
+        this.addAll(array, true);
+    }
+
+    /**
+     * Construct a JSONArray with the specified initial capacity.
+     *
+     * @param initialCapacity
+     *            the initial capacity of the JSONArray.
+     * @throws JSONException
+     *             If the initial capacity is negative.
+     */
+    public JSONArray(int initialCapacity) throws JSONException {
+    	if (initialCapacity < 0) {
+            throw new JSONException(
+                    "JSONArray initial capacity cannot be negative.");
+    	}
+    	this.myArrayList = new ArrayList<Object>(initialCapacity);
+    }
+
+    @Override
+    public Iterator<Object> iterator() {
+        return this.myArrayList.iterator();
+    }
+
+    /**
+     * Get the object value associated with an index.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return An object value.
+     * @throws JSONException
+     *             If there is no value for the index.
+     */
+    public Object get(int index) throws JSONException {
+        Object object = this.opt(index);
+        if (object == null) {
+            throw new JSONException("JSONArray[" + index + "] not found.");
+        }
+        return object;
+    }
+
+    /**
+     * Get the boolean value associated with an index. The string values "true"
+     * and "false" are converted to boolean.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The truth.
+     * @throws JSONException
+     *             If there is no value for the index or if the value is not
+     *             convertible to boolean.
+     */
+    public boolean getBoolean(int index) throws JSONException {
+        Object object = this.get(index);
+        if (object.equals(Boolean.FALSE)
+                || (object instanceof String && ((String) object)
+                        .equalsIgnoreCase("false"))) {
+            return false;
+        } else if (object.equals(Boolean.TRUE)
+                || (object instanceof String && ((String) object)
+                        .equalsIgnoreCase("true"))) {
+            return true;
+        }
+        throw wrongValueFormatException(index, "boolean", null);
+    }
+
+    /**
+     * Get the double value associated with an index.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The value.
+     * @throws JSONException
+     *             If the key is not found or if the value cannot be converted
+     *             to a number.
+     */
+    public double getDouble(int index) throws JSONException {
+        final Object object = this.get(index);
+        if(object instanceof Number) {
+            return ((Number)object).doubleValue();
+        }
+        try {
+            return Double.parseDouble(object.toString());
+        } catch (Exception e) {
+            throw wrongValueFormatException(index, "double", e);
+        }
+    }
+
+    /**
+     * Get the float value associated with a key.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The numeric value.
+     * @throws JSONException
+     *             if the key is not found or if the value is not a Number
+     *             object and cannot be converted to a number.
+     */
+    public float getFloat(int index) throws JSONException {
+        final Object object = this.get(index);
+        if(object instanceof Number) {
+            return ((Float)object).floatValue();
+        }
+        try {
+            return Float.parseFloat(object.toString());
+        } catch (Exception e) {
+            throw wrongValueFormatException(index, "float", e);
+        }
+    }
+
+    /**
+     * Get the Number value associated with a key.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The numeric value.
+     * @throws JSONException
+     *             if the key is not found or if the value is not a Number
+     *             object and cannot be converted to a number.
+     */
+    public Number getNumber(int index) throws JSONException {
+        Object object = this.get(index);
+        try {
+            if (object instanceof Number) {
+                return (Number)object;
+            }
+            return JSONObject.stringToNumber(object.toString());
+        } catch (Exception e) {
+            throw wrongValueFormatException(index, "number", e);
+        }
+    }
+
+    /**
+     * Get the enum value associated with an index.
+     * 
+     * @param <E>
+     *            Enum Type
+     * @param clazz
+     *            The type of enum to retrieve.
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The enum value at the index location
+     * @throws JSONException
+     *            if the key is not found or if the value cannot be converted
+     *            to an enum.
+     */
+    public <E extends Enum<E>> E getEnum(Class<E> clazz, int index) throws JSONException {
+        E val = optEnum(clazz, index);
+        if(val==null) {
+            // JSONException should really take a throwable argument.
+            // If it did, I would re-implement this with the Enum.valueOf
+            // method and place any thrown exception in the JSONException
+            throw wrongValueFormatException(index, "enum of type "
+                    + JSONObject.quote(clazz.getSimpleName()), null);
+        }
+        return val;
+    }
+
+    /**
+     * Get the BigDecimal value associated with an index. If the value is float
+     * or double, the the {@link BigDecimal#BigDecimal(double)} constructor
+     * will be used. See notes on the constructor for conversion issues that
+     * may arise.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The value.
+     * @throws JSONException
+     *             If the key is not found or if the value cannot be converted
+     *             to a BigDecimal.
+     */
+    public BigDecimal getBigDecimal (int index) throws JSONException {
+        Object object = this.get(index);
+        BigDecimal val = JSONObject.objectToBigDecimal(object, null);
+        if(val == null) {
+            throw wrongValueFormatException(index, "BigDecimal", object, null);
+        }
+        return val;
+    }
+
+    /**
+     * Get the BigInteger value associated with an index.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The value.
+     * @throws JSONException
+     *             If the key is not found or if the value cannot be converted
+     *             to a BigInteger.
+     */
+    public BigInteger getBigInteger (int index) throws JSONException {
+        Object object = this.get(index);
+        BigInteger val = JSONObject.objectToBigInteger(object, null);
+        if(val == null) {
+            throw wrongValueFormatException(index, "BigInteger", object, null);
+        }
+        return val;
+    }
+
+    /**
+     * Get the int value associated with an index.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The value.
+     * @throws JSONException
+     *             If the key is not found or if the value is not a number.
+     */
+    public int getInt(int index) throws JSONException {
+        final Object object = this.get(index);
+        if(object instanceof Number) {
+            return ((Number)object).intValue();
+        }
+        try {
+            return Integer.parseInt(object.toString());
+        } catch (Exception e) {
+            throw wrongValueFormatException(index, "int", e);
+        }
+    }
+
+    /**
+     * Get the JSONArray associated with an index.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return A JSONArray value.
+     * @throws JSONException
+     *             If there is no value for the index. or if the value is not a
+     *             JSONArray
+     */
+    public JSONArray getJSONArray(int index) throws JSONException {
+        Object object = this.get(index);
+        if (object instanceof JSONArray) {
+            return (JSONArray) object;
+        }
+        throw wrongValueFormatException(index, "JSONArray", null);
+    }
+
+    /**
+     * Get the JSONObject associated with an index.
+     *
+     * @param index
+     *            subscript
+     * @return A JSONObject value.
+     * @throws JSONException
+     *             If there is no value for the index or if the value is not a
+     *             JSONObject
+     */
+    public JSONObject getJSONObject(int index) throws JSONException {
+        Object object = this.get(index);
+        if (object instanceof JSONObject) {
+            return (JSONObject) object;
+        }
+        throw wrongValueFormatException(index, "JSONObject", null);
+    }
+
+    /**
+     * Get the long value associated with an index.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The value.
+     * @throws JSONException
+     *             If the key is not found or if the value cannot be converted
+     *             to a number.
+     */
+    public long getLong(int index) throws JSONException {
+        final Object object = this.get(index);
+        if(object instanceof Number) {
+            return ((Number)object).longValue();
+        }
+        try {
+            return Long.parseLong(object.toString());
+        } catch (Exception e) {
+            throw wrongValueFormatException(index, "long", e);
+        }
+    }
+
+    /**
+     * Get the string associated with an index.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return A string value.
+     * @throws JSONException
+     *             If there is no string value for the index.
+     */
+    public String getString(int index) throws JSONException {
+        Object object = this.get(index);
+        if (object instanceof String) {
+            return (String) object;
+        }
+        throw wrongValueFormatException(index, "String", null);
+    }
+
+    /**
+     * Determine if the value is <code>null</code>.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return true if the value at the index is <code>null</code>, or if there is no value.
+     */
+    public boolean isNull(int index) {
+        return JSONObject.NULL.equals(this.opt(index));
+    }
+
+    /**
+     * Make a string from the contents of this JSONArray. The
+     * <code>separator</code> string is inserted between each element. Warning:
+     * This method assumes that the data structure is acyclical.
+     *
+     * @param separator
+     *            A string that will be inserted between the elements.
+     * @return a string.
+     * @throws JSONException
+     *             If the array contains an invalid number.
+     */
+    public String join(String separator) throws JSONException {
+        int len = this.length();
+        if (len == 0) {
+            return "";
+        }
+        
+        StringBuilder sb = new StringBuilder(
+                   JSONObject.valueToString(this.myArrayList.get(0)));
+
+        for (int i = 1; i < len; i++) {
+            sb.append(separator)
+              .append(JSONObject.valueToString(this.myArrayList.get(i)));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Get the number of elements in the JSONArray, included nulls.
+     *
+     * @return The length (or size).
+     */
+    public int length() {
+        return this.myArrayList.size();
+    }
+
+    /**
+     * Removes all of the elements from this JSONArray.
+     * The JSONArray will be empty after this call returns.
+     */
+    public void clear() {
+        this.myArrayList.clear();
+    }
+
+    /**
+     * Get the optional object value associated with an index.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1. If not, null is returned.
+     * @return An object value, or null if there is no object at that index.
+     */
+    public Object opt(int index) {
+        return (index < 0 || index >= this.length()) ? null : this.myArrayList
+                .get(index);
+    }
+
+    /**
+     * Get the optional boolean value associated with an index. It returns false
+     * if there is no value at that index, or if the value is not Boolean.TRUE
+     * or the String "true".
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The truth.
+     */
+    public boolean optBoolean(int index) {
+        return this.optBoolean(index, false);
+    }
+
+    /**
+     * Get the optional boolean value associated with an index. It returns the
+     * defaultValue if there is no value at that index or if it is not a Boolean
+     * or the String "true" or "false" (case insensitive).
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @param defaultValue
+     *            A boolean default.
+     * @return The truth.
+     */
+    public boolean optBoolean(int index, boolean defaultValue) {
+        try {
+            return this.getBoolean(index);
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Get the optional double value associated with an index. NaN is returned
+     * if there is no value for the index, or if the value is not a number and
+     * cannot be converted to a number.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The value.
+     */
+    public double optDouble(int index) {
+        return this.optDouble(index, Double.NaN);
+    }
+
+    /**
+     * Get the optional double value associated with an index. The defaultValue
+     * is returned if there is no value for the index, or if the value is not a
+     * number and cannot be converted to a number.
+     *
+     * @param index
+     *            subscript
+     * @param defaultValue
+     *            The default value.
+     * @return The value.
+     */
+    public double optDouble(int index, double defaultValue) {
+        final Number val = this.optNumber(index, null);
+        if (val == null) {
+            return defaultValue;
+        }
+        final double doubleValue = val.doubleValue();
+        // if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) {
+        // return defaultValue;
+        // }
+        return doubleValue;
+    }
+
+    /**
+     * Get the optional float value associated with an index. NaN is returned
+     * if there is no value for the index, or if the value is not a number and
+     * cannot be converted to a number.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The value.
+     */
+    public float optFloat(int index) {
+        return this.optFloat(index, Float.NaN);
+    }
+
+    /**
+     * Get the optional float value associated with an index. The defaultValue
+     * is returned if there is no value for the index, or if the value is not a
+     * number and cannot be converted to a number.
+     *
+     * @param index
+     *            subscript
+     * @param defaultValue
+     *            The default value.
+     * @return The value.
+     */
+    public float optFloat(int index, float defaultValue) {
+        final Number val = this.optNumber(index, null);
+        if (val == null) {
+            return defaultValue;
+        }
+        final float floatValue = val.floatValue();
+        // if (Float.isNaN(floatValue) || Float.isInfinite(floatValue)) {
+        // return floatValue;
+        // }
+        return floatValue;
+    }
+
+    /**
+     * Get the optional int value associated with an index. Zero is returned if
+     * there is no value for the index, or if the value is not a number and
+     * cannot be converted to a number.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The value.
+     */
+    public int optInt(int index) {
+        return this.optInt(index, 0);
+    }
+
+    /**
+     * Get the optional int value associated with an index. The defaultValue is
+     * returned if there is no value for the index, or if the value is not a
+     * number and cannot be converted to a number.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @param defaultValue
+     *            The default value.
+     * @return The value.
+     */
+    public int optInt(int index, int defaultValue) {
+        final Number val = this.optNumber(index, null);
+        if (val == null) {
+            return defaultValue;
+        }
+        return val.intValue();
+    }
+
+    /**
+     * Get the enum value associated with a key.
+     * 
+     * @param <E>
+     *            Enum Type
+     * @param clazz
+     *            The type of enum to retrieve.
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The enum value at the index location or null if not found
+     */
+    public <E extends Enum<E>> E optEnum(Class<E> clazz, int index) {
+        return this.optEnum(clazz, index, null);
+    }
+
+    /**
+     * Get the enum value associated with a key.
+     * 
+     * @param <E>
+     *            Enum Type
+     * @param clazz
+     *            The type of enum to retrieve.
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @param defaultValue
+     *            The default in case the value is not found
+     * @return The enum value at the index location or defaultValue if
+     *            the value is not found or cannot be assigned to clazz
+     */
+    public <E extends Enum<E>> E optEnum(Class<E> clazz, int index, E defaultValue) {
+        try {
+            Object val = this.opt(index);
+            if (JSONObject.NULL.equals(val)) {
+                return defaultValue;
+            }
+            if (clazz.isAssignableFrom(val.getClass())) {
+                // we just checked it!
+                @SuppressWarnings("unchecked")
+                E myE = (E) val;
+                return myE;
+            }
+            return Enum.valueOf(clazz, val.toString());
+        } catch (IllegalArgumentException e) {
+            return defaultValue;
+        } catch (NullPointerException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Get the optional BigInteger value associated with an index. The 
+     * defaultValue is returned if there is no value for the index, or if the 
+     * value is not a number and cannot be converted to a number.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @param defaultValue
+     *            The default value.
+     * @return The value.
+     */
+    public BigInteger optBigInteger(int index, BigInteger defaultValue) {
+        Object val = this.opt(index);
+        return JSONObject.objectToBigInteger(val, defaultValue);
+    }
+
+    /**
+     * Get the optional BigDecimal value associated with an index. The 
+     * defaultValue is returned if there is no value for the index, or if the 
+     * value is not a number and cannot be converted to a number. If the value
+     * is float or double, the the {@link BigDecimal#BigDecimal(double)}
+     * constructor will be used. See notes on the constructor for conversion
+     * issues that may arise.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @param defaultValue
+     *            The default value.
+     * @return The value.
+     */
+    public BigDecimal optBigDecimal(int index, BigDecimal defaultValue) {
+        Object val = this.opt(index);
+        return JSONObject.objectToBigDecimal(val, defaultValue);
+    }
+
+    /**
+     * Get the optional JSONArray associated with an index.
+     *
+     * @param index
+     *            subscript
+     * @return A JSONArray value, or null if the index has no value, or if the
+     *         value is not a JSONArray.
+     */
+    public JSONArray optJSONArray(int index) {
+        Object o = this.opt(index);
+        return o instanceof JSONArray ? (JSONArray) o : null;
+    }
+
+    /**
+     * Get the optional JSONObject associated with an index. Null is returned if
+     * the key is not found, or null if the index has no value, or if the value
+     * is not a JSONObject.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return A JSONObject value.
+     */
+    public JSONObject optJSONObject(int index) {
+        Object o = this.opt(index);
+        return o instanceof JSONObject ? (JSONObject) o : null;
+    }
+
+    /**
+     * Get the optional long value associated with an index. Zero is returned if
+     * there is no value for the index, or if the value is not a number and
+     * cannot be converted to a number.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The value.
+     */
+    public long optLong(int index) {
+        return this.optLong(index, 0);
+    }
+
+    /**
+     * Get the optional long value associated with an index. The defaultValue is
+     * returned if there is no value for the index, or if the value is not a
+     * number and cannot be converted to a number.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @param defaultValue
+     *            The default value.
+     * @return The value.
+     */
+    public long optLong(int index, long defaultValue) {
+        final Number val = this.optNumber(index, null);
+        if (val == null) {
+            return defaultValue;
+        }
+        return val.longValue();
+    }
+
+    /**
+     * Get an optional {@link Number} value associated with a key, or <code>null</code>
+     * if there is no such key or if the value is not a number. If the value is a string,
+     * an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method
+     * would be used in cases where type coercion of the number value is unwanted.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return An object which is the value.
+     */
+    public Number optNumber(int index) {
+        return this.optNumber(index, null);
+    }
+
+    /**
+     * Get an optional {@link Number} value associated with a key, or the default if there
+     * is no such key or if the value is not a number. If the value is a string,
+     * an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method
+     * would be used in cases where type coercion of the number value is unwanted.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @param defaultValue
+     *            The default.
+     * @return An object which is the value.
+     */
+    public Number optNumber(int index, Number defaultValue) {
+        Object val = this.opt(index);
+        if (JSONObject.NULL.equals(val)) {
+            return defaultValue;
+        }
+        if (val instanceof Number){
+            return (Number) val;
+        }
+        
+        if (val instanceof String) {
+            try {
+                return JSONObject.stringToNumber((String) val);
+            } catch (Exception e) {
+                return defaultValue;
+            }
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Get the optional string value associated with an index. It returns an
+     * empty string if there is no value at that index. If the value is not a
+     * string and is not null, then it is converted to a string.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return A String value.
+     */
+    public String optString(int index) {
+        return this.optString(index, "");
+    }
+
+    /**
+     * Get the optional string associated with an index. The defaultValue is
+     * returned if the key is not found.
+     *
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @param defaultValue
+     *            The default value.
+     * @return A String value.
+     */
+    public String optString(int index, String defaultValue) {
+        Object object = this.opt(index);
+        return JSONObject.NULL.equals(object) ? defaultValue : object
+                .toString();
+    }
+
+    /**
+     * Append a boolean value. This increases the array's length by one.
+     *
+     * @param value
+     *            A boolean value.
+     * @return this.
+     */
+    public JSONArray put(boolean value) {
+        return this.put(value ? Boolean.TRUE : Boolean.FALSE);
+    }
+
+    /**
+     * Put a value in the JSONArray, where the value will be a JSONArray which
+     * is produced from a Collection.
+     *
+     * @param value
+     *            A Collection value.
+     * @return this.
+     * @throws JSONException
+     *            If the value is non-finite number.
+     */
+    public JSONArray put(Collection<?> value) {
+        return this.put(new JSONArray(value));
+    }
+
+    /**
+     * Append a double value. This increases the array's length by one.
+     *
+     * @param value
+     *            A double value.
+     * @return this.
+     * @throws JSONException
+     *             if the value is not finite.
+     */
+    public JSONArray put(double value) throws JSONException {
+        return this.put(Double.valueOf(value));
+    }
+    
+    /**
+     * Append a float value. This increases the array's length by one.
+     *
+     * @param value
+     *            A float value.
+     * @return this.
+     * @throws JSONException
+     *             if the value is not finite.
+     */
+    public JSONArray put(float value) throws JSONException {
+        return this.put(Float.valueOf(value));
+    }
+
+    /**
+     * Append an int value. This increases the array's length by one.
+     *
+     * @param value
+     *            An int value.
+     * @return this.
+     */
+    public JSONArray put(int value) {
+        return this.put(Integer.valueOf(value));
+    }
+
+    /**
+     * Append an long value. This increases the array's length by one.
+     *
+     * @param value
+     *            A long value.
+     * @return this.
+     */
+    public JSONArray put(long value) {
+        return this.put(Long.valueOf(value));
+    }
+
+    /**
+     * Put a value in the JSONArray, where the value will be a JSONObject which
+     * is produced from a Map.
+     *
+     * @param value
+     *            A Map value.
+     * @return this.
+     * @throws JSONException
+     *            If a value in the map is non-finite number.
+     * @throws NullPointerException
+     *            If a key in the map is <code>null</code>
+     */
+    public JSONArray put(Map<?, ?> value) {
+        return this.put(new JSONObject(value));
+    }
+
+    /**
+     * Append an object value. This increases the array's length by one.
+     *
+     * @param value
+     *            An object value. The value should be a Boolean, Double,
+     *            Integer, JSONArray, JSONObject, Long, or String, or the
+     *            JSONObject.NULL object.
+     * @return this.
+     * @throws JSONException
+     *            If the value is non-finite number.
+     */
+    public JSONArray put(Object value) {
+        JSONObject.testValidity(value);
+        this.myArrayList.add(value);
+        return this;
+    }
+
+    /**
+     * Put or replace a boolean value in the JSONArray. If the index is greater
+     * than the length of the JSONArray, then null elements will be added as
+     * necessary to pad it out.
+     *
+     * @param index
+     *            The subscript.
+     * @param value
+     *            A boolean value.
+     * @return this.
+     * @throws JSONException
+     *             If the index is negative.
+     */
+    public JSONArray put(int index, boolean value) throws JSONException {
+        return this.put(index, value ? Boolean.TRUE : Boolean.FALSE);
+    }
+
+    /**
+     * Put a value in the JSONArray, where the value will be a JSONArray which
+     * is produced from a Collection.
+     *
+     * @param index
+     *            The subscript.
+     * @param value
+     *            A Collection value.
+     * @return this.
+     * @throws JSONException
+     *             If the index is negative or if the value is non-finite.
+     */
+    public JSONArray put(int index, Collection<?> value) throws JSONException {
+        return this.put(index, new JSONArray(value));
+    }
+
+    /**
+     * Put or replace a double value. If the index is greater than the length of
+     * the JSONArray, then null elements will be added as necessary to pad it
+     * out.
+     *
+     * @param index
+     *            The subscript.
+     * @param value
+     *            A double value.
+     * @return this.
+     * @throws JSONException
+     *             If the index is negative or if the value is non-finite.
+     */
+    public JSONArray put(int index, double value) throws JSONException {
+        return this.put(index, Double.valueOf(value));
+    }
+
+    /**
+     * Put or replace a float value. If the index is greater than the length of
+     * the JSONArray, then null elements will be added as necessary to pad it
+     * out.
+     *
+     * @param index
+     *            The subscript.
+     * @param value
+     *            A float value.
+     * @return this.
+     * @throws JSONException
+     *             If the index is negative or if the value is non-finite.
+     */
+    public JSONArray put(int index, float value) throws JSONException {
+        return this.put(index, Float.valueOf(value));
+    }
+
+    /**
+     * Put or replace an int value. If the index is greater than the length of
+     * the JSONArray, then null elements will be added as necessary to pad it
+     * out.
+     *
+     * @param index
+     *            The subscript.
+     * @param value
+     *            An int value.
+     * @return this.
+     * @throws JSONException
+     *             If the index is negative.
+     */
+    public JSONArray put(int index, int value) throws JSONException {
+        return this.put(index, Integer.valueOf(value));
+    }
+
+    /**
+     * Put or replace a long value. If the index is greater than the length of
+     * the JSONArray, then null elements will be added as necessary to pad it
+     * out.
+     *
+     * @param index
+     *            The subscript.
+     * @param value
+     *            A long value.
+     * @return this.
+     * @throws JSONException
+     *             If the index is negative.
+     */
+    public JSONArray put(int index, long value) throws JSONException {
+        return this.put(index, Long.valueOf(value));
+    }
+
+    /**
+     * Put a value in the JSONArray, where the value will be a JSONObject that
+     * is produced from a Map.
+     *
+     * @param index
+     *            The subscript.
+     * @param value
+     *            The Map value.
+     * @return this.
+     * @throws JSONException
+     *             If the index is negative or if the the value is an invalid
+     *             number.
+     * @throws NullPointerException
+     *             If a key in the map is <code>null</code>
+     */
+    public JSONArray put(int index, Map<?, ?> value) throws JSONException {
+        this.put(index, new JSONObject(value));
+        return this;
+    }
+
+    /**
+     * Put or replace an object value in the JSONArray. If the index is greater
+     * than the length of the JSONArray, then null elements will be added as
+     * necessary to pad it out.
+     *
+     * @param index
+     *            The subscript.
+     * @param value
+     *            The value to put into the array. The value should be a
+     *            Boolean, Double, Integer, JSONArray, JSONObject, Long, or
+     *            String, or the JSONObject.NULL object.
+     * @return this.
+     * @throws JSONException
+     *             If the index is negative or if the the value is an invalid
+     *             number.
+     */
+    public JSONArray put(int index, Object value) throws JSONException {
+        if (index < 0) {
+            throw new JSONException("JSONArray[" + index + "] not found.");
+        }
+        if (index < this.length()) {
+            JSONObject.testValidity(value);
+            this.myArrayList.set(index, value);
+            return this;
+        }
+        if(index == this.length()){
+            // simple append
+            return this.put(value);
+        }
+        // if we are inserting past the length, we want to grow the array all at once
+        // instead of incrementally.
+        this.myArrayList.ensureCapacity(index + 1);
+        while (index != this.length()) {
+            // we don't need to test validity of NULL objects
+            this.myArrayList.add(JSONObject.NULL);
+        }
+        return this.put(value);
+    }
+
+    /**
+     * Put a collection's elements in to the JSONArray.
+     *
+     * @param collection
+     *            A Collection.
+     * @return this. 
+     */
+    public JSONArray putAll(Collection<?> collection) {
+        this.addAll(collection, false);
+        return this;
+    }
+    
+    /**
+     * Put an Iterable's elements in to the JSONArray.
+     *
+     * @param iter
+     *            An Iterable.
+     * @return this. 
+     */
+    public JSONArray putAll(Iterable<?> iter) {
+        this.addAll(iter, false);
+        return this;
+    }
+
+    /**
+     * Put a JSONArray's elements in to the JSONArray.
+     *
+     * @param array
+     *            A JSONArray.
+     * @return this. 
+     */
+    public JSONArray putAll(JSONArray array) {
+        // directly copy the elements from the source array to this one
+        // as all wrapping should have been done already in the source.
+        this.myArrayList.addAll(array.myArrayList);
+        return this;
+    }
+
+    /**
+     * Put an array's elements in to the JSONArray.
+     *
+     * @param array
+     *            Array. If the parameter passed is null, or not an array or Iterable, an
+     *            exception will be thrown.
+     * @return this. 
+     *
+     * @throws JSONException
+     *            If not an array, JSONArray, Iterable or if an value is non-finite number.
+     * @throws NullPointerException
+     *            Thrown if the array parameter is null.
+     */
+    public JSONArray putAll(Object array) throws JSONException {
+        this.addAll(array, false);
+        return this;
+    }
+    
+    /**
+     * Creates a JSONPointer using an initialization string and tries to 
+     * match it to an item within this JSONArray. For example, given a
+     * JSONArray initialized with this document:
+     * <pre>
+     * [
+     *     {"b":"c"}
+     * ]
+     * </pre>
+     * and this JSONPointer string: 
+     * <pre>
+     * "/0/b"
+     * </pre>
+     * Then this method will return the String "c"
+     * A JSONPointerException may be thrown from code called by this method.
+     *
+     * @param jsonPointer string that can be used to create a JSONPointer
+     * @return the item matched by the JSONPointer, otherwise null
+     */
+    public Object query(String jsonPointer) {
+        return query(new JSONPointer(jsonPointer));
+    }
+    
+    /**
+     * Uses a user initialized JSONPointer  and tries to 
+     * match it to an item within this JSONArray. For example, given a
+     * JSONArray initialized with this document:
+     * <pre>
+     * [
+     *     {"b":"c"}
+     * ]
+     * </pre>
+     * and this JSONPointer: 
+     * <pre>
+     * "/0/b"
+     * </pre>
+     * Then this method will return the String "c"
+     * A JSONPointerException may be thrown from code called by this method.
+     *
+     * @param jsonPointer string that can be used to create a JSONPointer
+     * @return the item matched by the JSONPointer, otherwise null
+     */
+    public Object query(JSONPointer jsonPointer) {
+        return jsonPointer.queryFrom(this);
+    }
+    
+    /**
+     * Queries and returns a value from this object using {@code jsonPointer}, or
+     * returns null if the query fails due to a missing key.
+     * 
+     * @param jsonPointer the string representation of the JSON pointer
+     * @return the queried value or {@code null}
+     * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
+     */
+    public Object optQuery(String jsonPointer) {
+    	return optQuery(new JSONPointer(jsonPointer));
+    }
+    
+    /**
+     * Queries and returns a value from this object using {@code jsonPointer}, or
+     * returns null if the query fails due to a missing key.
+     * 
+     * @param jsonPointer The JSON pointer
+     * @return the queried value or {@code null}
+     * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
+     */
+    public Object optQuery(JSONPointer jsonPointer) {
+        try {
+            return jsonPointer.queryFrom(this);
+        } catch (JSONPointerException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Remove an index and close the hole.
+     *
+     * @param index
+     *            The index of the element to be removed.
+     * @return The value that was associated with the index, or null if there
+     *         was no value.
+     */
+    public Object remove(int index) {
+        return index >= 0 && index < this.length()
+            ? this.myArrayList.remove(index)
+            : null;
+    }
+
+    /**
+     * Determine if two JSONArrays are similar.
+     * They must contain similar sequences.
+     *
+     * @param other The other JSONArray
+     * @return true if they are equal
+     */
+    public boolean similar(Object other) {
+        if (!(other instanceof JSONArray)) {
+            return false;
+        }
+        int len = this.length();
+        if (len != ((JSONArray)other).length()) {
+            return false;
+        }
+        for (int i = 0; i < len; i += 1) {
+            Object valueThis = this.myArrayList.get(i);
+            Object valueOther = ((JSONArray)other).myArrayList.get(i);
+            if(valueThis == valueOther) {
+            	continue;
+            }
+            if(valueThis == null) {
+            	return false;
+            }
+            if (valueThis instanceof JSONObject) {
+                if (!((JSONObject)valueThis).similar(valueOther)) {
+                    return false;
+                }
+            } else if (valueThis instanceof JSONArray) {
+                if (!((JSONArray)valueThis).similar(valueOther)) {
+                    return false;
+                }
+            } else if (valueThis instanceof Number && valueOther instanceof Number) {
+                return JSONObject.isNumberSimilar((Number)valueThis, (Number)valueOther);
+            } else if (!valueThis.equals(valueOther)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Produce a JSONObject by combining a JSONArray of names with the values of
+     * this JSONArray.
+     *
+     * @param names
+     *            A JSONArray containing a list of key strings. These will be
+     *            paired with the values.
+     * @return A JSONObject, or null if there are no names or if this JSONArray
+     *         has no values.
+     * @throws JSONException
+     *             If any of the names are null.
+     */
+    public JSONObject toJSONObject(JSONArray names) throws JSONException {
+        if (names == null || names.isEmpty() || this.isEmpty()) {
+            return null;
+        }
+        JSONObject jo = new JSONObject(names.length());
+        for (int i = 0; i < names.length(); i += 1) {
+            jo.put(names.getString(i), this.opt(i));
+        }
+        return jo;
+    }
+
+    /**
+     * Make a JSON text of this JSONArray. For compactness, no unnecessary
+     * whitespace is added. If it is not possible to produce a syntactically
+     * correct JSON text then null will be returned instead. This could occur if
+     * the array contains an invalid number.
+     * <p><b>
+     * Warning: This method assumes that the data structure is acyclical.
+     * </b>
+     *
+     * @return a printable, displayable, transmittable representation of the
+     *         array.
+     */
+    @Override
+    public String toString() {
+        try {
+            return this.toString(0);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * Make a pretty-printed JSON text of this JSONArray.
+     * 
+     * <p>If <pre> {@code indentFactor > 0}</pre> and the {@link JSONArray} has only
+     * one element, then the array will be output on a single line:
+     * <pre>{@code [1]}</pre>
+     * 
+     * <p>If an array has 2 or more elements, then it will be output across
+     * multiple lines: <pre>{@code
+     * [
+     * 1,
+     * "value 2",
+     * 3
+     * ]
+     * }</pre>
+     * <p><b>
+     * Warning: This method assumes that the data structure is acyclical.
+     * </b>
+     * 
+     * @param indentFactor
+     *            The number of spaces to add to each level of indentation.
+     * @return a printable, displayable, transmittable representation of the
+     *         object, beginning with <code>[</code>&nbsp;<small>(left
+     *         bracket)</small> and ending with <code>]</code>
+     *         &nbsp;<small>(right bracket)</small>.
+     * @throws JSONException if a called function fails
+     */
+    public String toString(int indentFactor) throws JSONException {
+        StringWriter sw = new StringWriter();
+        synchronized (sw.getBuffer()) {
+            return this.write(sw, indentFactor, 0).toString();
+        }
+    }
+
+    /**
+     * Write the contents of the JSONArray as JSON text to a writer. For
+     * compactness, no whitespace is added.
+     * <p><b>
+     * Warning: This method assumes that the data structure is acyclical.
+     *</b>
+     * @param writer the writer object
+     * @return The writer.
+     * @throws JSONException if a called function fails
+     */
+    public Writer write(Writer writer) throws JSONException {
+        return this.write(writer, 0, 0);
+    }
+
+    /**
+     * Write the contents of the JSONArray as JSON text to a writer.
+     * 
+     * <p>If <pre>{@code indentFactor > 0}</pre> and the {@link JSONArray} has only
+     * one element, then the array will be output on a single line:
+     * <pre>{@code [1]}</pre>
+     * 
+     * <p>If an array has 2 or more elements, then it will be output across
+     * multiple lines: <pre>{@code
+     * [
+     * 1,
+     * "value 2",
+     * 3
+     * ]
+     * }</pre>
+     * <p><b>
+     * Warning: This method assumes that the data structure is acyclical.
+     * </b>
+     *
+     * @param writer
+     *            Writes the serialized JSON
+     * @param indentFactor
+     *            The number of spaces to add to each level of indentation.
+     * @param indent
+     *            The indentation of the top level.
+     * @return The writer.
+     * @throws JSONException if a called function fails or unable to write
+     */
+    public Writer write(Writer writer, int indentFactor, int indent)
+            throws JSONException {
+        try {
+            boolean needsComma = false;
+            int length = this.length();
+            writer.write('[');
+
+            if (length == 1) {
+                try {
+                    JSONObject.writeValue(writer, this.myArrayList.get(0),
+                            indentFactor, indent);
+                } catch (Exception e) {
+                    throw new JSONException("Unable to write JSONArray value at index: 0", e);
+                }
+            } else if (length != 0) {
+                final int newIndent = indent + indentFactor;
+
+                for (int i = 0; i < length; i += 1) {
+                    if (needsComma) {
+                        writer.write(',');
+                    }
+                    if (indentFactor > 0) {
+                        writer.write('\n');
+                    }
+                    JSONObject.indent(writer, newIndent);
+                    try {
+                        JSONObject.writeValue(writer, this.myArrayList.get(i),
+                                indentFactor, newIndent);
+                    } catch (Exception e) {
+                        throw new JSONException("Unable to write JSONArray value at index: " + i, e);
+                    }
+                    needsComma = true;
+                }
+                if (indentFactor > 0) {
+                    writer.write('\n');
+                }
+                JSONObject.indent(writer, indent);
+            }
+            writer.write(']');
+            return writer;
+        } catch (IOException e) {
+            throw new JSONException(e);
+        }
+    }
+
+    /**
+     * Returns a java.util.List containing all of the elements in this array.
+     * If an element in the array is a JSONArray or JSONObject it will also
+     * be converted to a List and a Map respectively.
+     * <p>
+     * Warning: This method assumes that the data structure is acyclical.
+     *
+     * @return a java.util.List containing the elements of this array
+     */
+    public List<Object> toList() {
+        List<Object> results = new ArrayList<Object>(this.myArrayList.size());
+        for (Object element : this.myArrayList) {
+            if (element == null || JSONObject.NULL.equals(element)) {
+                results.add(null);
+            } else if (element instanceof JSONArray) {
+                results.add(((JSONArray) element).toList());
+            } else if (element instanceof JSONObject) {
+                results.add(((JSONObject) element).toMap());
+            } else {
+                results.add(element);
+            }
+        }
+        return results;
+    }
+
+    /**
+     * Check if JSONArray is empty.
+     *
+     * @return true if JSONArray is empty, otherwise false.
+     */
+    public boolean isEmpty() {
+        return this.myArrayList.isEmpty();
+    }
+
+    /**
+     * Add a collection's elements to the JSONArray.
+     *
+     * @param collection
+     *            A Collection.
+     * @param wrap
+     *            {@code true} to call {@link JSONObject#wrap(Object)} for each item,
+     *            {@code false} to add the items directly
+     *            
+     */
+    private void addAll(Collection<?> collection, boolean wrap) {
+        this.myArrayList.ensureCapacity(this.myArrayList.size() + collection.size());
+        if (wrap) {
+            for (Object o: collection){
+                this.put(JSONObject.wrap(o));
+            }
+        } else {
+            for (Object o: collection){
+                this.put(o);
+            }
+        }
+    }
+
+    /**
+     * Add an Iterable's elements to the JSONArray.
+     *
+     * @param iter
+     *            An Iterable.
+     * @param wrap
+     *            {@code true} to call {@link JSONObject#wrap(Object)} for each item,
+     *            {@code false} to add the items directly
+     */
+    private void addAll(Iterable<?> iter, boolean wrap) {
+        if (wrap) {
+            for (Object o: iter){
+                this.put(JSONObject.wrap(o));
+            }
+        } else {
+            for (Object o: iter){
+                this.put(o);
+            }
+        }
+    }
+    
+    /**
+     * Add an array's elements to the JSONArray.
+     *
+     * @param array
+     *            Array. If the parameter passed is null, or not an array,
+     *            JSONArray, Collection, or Iterable, an exception will be
+     *            thrown.
+     * @param wrap
+     *            {@code true} to call {@link JSONObject#wrap(Object)} for each item,
+     *            {@code false} to add the items directly
+     *
+     * @throws JSONException
+     *            If not an array or if an array value is non-finite number.
+     * @throws NullPointerException
+     *            Thrown if the array parameter is null.
+     */
+    private void addAll(Object array, boolean wrap) throws JSONException {
+        if (array.getClass().isArray()) {
+            int length = Array.getLength(array);
+            this.myArrayList.ensureCapacity(this.myArrayList.size() + length);
+            if (wrap) {
+                for (int i = 0; i < length; i += 1) {
+                    this.put(JSONObject.wrap(Array.get(array, i)));
+                }
+            } else {
+                for (int i = 0; i < length; i += 1) {
+                    this.put(Array.get(array, i));
+                }
+            }
+        } else if (array instanceof JSONArray) {
+            // use the built in array list `addAll` as all object
+            // wrapping should have been completed in the original
+            // JSONArray
+            this.myArrayList.addAll(((JSONArray)array).myArrayList);
+        } else if (array instanceof Collection) {
+            this.addAll((Collection<?>)array, wrap);
+        } else if (array instanceof Iterable) {
+            this.addAll((Iterable<?>)array, wrap);
+        } else {
+            throw new JSONException(
+                    "JSONArray initial value should be a string or collection or array.");
+        }
+    }
+    
+    /**
+     * Create a new JSONException in a common format for incorrect conversions.
+     * @param idx index of the item
+     * @param valueType the type of value being coerced to
+     * @param cause optional cause of the coercion failure
+     * @return JSONException that can be thrown.
+     */
+    private static JSONException wrongValueFormatException(
+            int idx,
+            String valueType,
+            Throwable cause) {
+        return new JSONException(
+                "JSONArray[" + idx + "] is not a " + valueType + "."
+                , cause);
+    }
+    
+    /**
+     * Create a new JSONException in a common format for incorrect conversions.
+     * @param idx index of the item
+     * @param valueType the type of value being coerced to
+     * @param cause optional cause of the coercion failure
+     * @return JSONException that can be thrown.
+     */
+    private static JSONException wrongValueFormatException(
+            int idx,
+            String valueType,
+            Object value,
+            Throwable cause) {
+        return new JSONException(
+                "JSONArray[" + idx + "] is not a " + valueType + " (" + value + ")."
+                , cause);
+    }
+
+}

+ 69 - 0
src/main/java/com/usky/xml/JSONException.java

@@ -0,0 +1,69 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+/**
+ * The JSONException is thrown by the JSON.org classes when things are amiss.
+ *
+ * @author JSON.org
+ * @version 2015-12-09
+ */
+public class JSONException extends RuntimeException {
+    /** Serialization ID */
+    private static final long serialVersionUID = 0;
+
+    /**
+     * Constructs a JSONException with an explanatory message.
+     *
+     * @param message
+     *            Detail about the reason for the exception.
+     */
+    public JSONException(final String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a JSONException with an explanatory message and cause.
+     * 
+     * @param message
+     *            Detail about the reason for the exception.
+     * @param cause
+     *            The cause.
+     */
+    public JSONException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructs a new JSONException with the specified cause.
+     * 
+     * @param cause
+     *            The cause.
+     */
+    public JSONException(final Throwable cause) {
+        super(cause.getMessage(), cause);
+    }
+
+}

+ 542 - 0
src/main/java/com/usky/xml/JSONML.java

@@ -0,0 +1,542 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2008 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/**
+ * This provides static methods to convert an XML text into a JSONArray or
+ * JSONObject, and to covert a JSONArray or JSONObject into an XML text using
+ * the JsonML transform.
+ *
+ * @author JSON.org
+ * @version 2016-01-30
+ */
+public class JSONML {
+    /**
+     * Parse XML values and store them in a JSONArray.
+     * @param x       The XMLTokener containing the source string.
+     * @param arrayForm true if array form, false if object form.
+     * @param ja      The JSONArray that is containing the current tag or null
+     *     if we are at the outermost level.
+     * @param keepStrings	Don't type-convert text nodes and attribute values
+     * @return A JSONArray if the value is the outermost tag, otherwise null.
+     * @throws JSONException if a parsing error occurs
+     */
+    private static Object parse(
+        XMLTokener x,
+        boolean    arrayForm,
+        JSONArray  ja,
+        boolean keepStrings
+    ) throws JSONException {
+        String     attribute;
+        char       c;
+        String     closeTag = null;
+        int        i;
+        JSONArray  newja = null;
+        JSONObject newjo = null;
+        Object     token;
+        String     tagName = null;
+
+// Test for and skip past these forms:
+//      <!-- ... -->
+//      <![  ... ]]>
+//      <!   ...   >
+//      <?   ...  ?>
+
+        while (true) {
+            if (!x.more()) {
+                throw x.syntaxError("Bad XML");
+            }
+            token = x.nextContent();
+            if (token == XML.LT) {
+                token = x.nextToken();
+                if (token instanceof Character) {
+                    if (token == XML.SLASH) {
+
+// Close tag </
+
+                        token = x.nextToken();
+                        if (!(token instanceof String)) {
+                            throw new JSONException(
+                                    "Expected a closing name instead of '" +
+                                    token + "'.");
+                        }
+                        if (x.nextToken() != XML.GT) {
+                            throw x.syntaxError("Misshaped close tag");
+                        }
+                        return token;
+                    } else if (token == XML.BANG) {
+
+// <!
+
+                        c = x.next();
+                        if (c == '-') {
+                            if (x.next() == '-') {
+                                x.skipPast("-->");
+                            } else {
+                                x.back();
+                            }
+                        } else if (c == '[') {
+                            token = x.nextToken();
+                            if (token.equals("CDATA") && x.next() == '[') {
+                                if (ja != null) {
+                                    ja.put(x.nextCDATA());
+                                }
+                            } else {
+                                throw x.syntaxError("Expected 'CDATA['");
+                            }
+                        } else {
+                            i = 1;
+                            do {
+                                token = x.nextMeta();
+                                if (token == null) {
+                                    throw x.syntaxError("Missing '>' after '<!'.");
+                                } else if (token == XML.LT) {
+                                    i += 1;
+                                } else if (token == XML.GT) {
+                                    i -= 1;
+                                }
+                            } while (i > 0);
+                        }
+                    } else if (token == XML.QUEST) {
+
+// <?
+
+                        x.skipPast("?>");
+                    } else {
+                        throw x.syntaxError("Misshaped tag");
+                    }
+
+// Open tag <
+
+                } else {
+                    if (!(token instanceof String)) {
+                        throw x.syntaxError("Bad tagName '" + token + "'.");
+                    }
+                    tagName = (String)token;
+                    newja = new JSONArray();
+                    newjo = new JSONObject();
+                    if (arrayForm) {
+                        newja.put(tagName);
+                        if (ja != null) {
+                            ja.put(newja);
+                        }
+                    } else {
+                        newjo.put("tagName", tagName);
+                        if (ja != null) {
+                            ja.put(newjo);
+                        }
+                    }
+                    token = null;
+                    for (;;) {
+                        if (token == null) {
+                            token = x.nextToken();
+                        }
+                        if (token == null) {
+                            throw x.syntaxError("Misshaped tag");
+                        }
+                        if (!(token instanceof String)) {
+                            break;
+                        }
+
+// attribute = value
+
+                        attribute = (String)token;
+                        if (!arrayForm && ("tagName".equals(attribute) || "childNode".equals(attribute))) {
+                            throw x.syntaxError("Reserved attribute.");
+                        }
+                        token = x.nextToken();
+                        if (token == XML.EQ) {
+                            token = x.nextToken();
+                            if (!(token instanceof String)) {
+                                throw x.syntaxError("Missing value");
+                            }
+                            newjo.accumulate(attribute, keepStrings ? ((String)token) :XML.stringToValue((String)token));
+                            token = null;
+                        } else {
+                            newjo.accumulate(attribute, "");
+                        }
+                    }
+                    if (arrayForm && newjo.length() > 0) {
+                        newja.put(newjo);
+                    }
+
+// Empty tag <.../>
+
+                    if (token == XML.SLASH) {
+                        if (x.nextToken() != XML.GT) {
+                            throw x.syntaxError("Misshaped tag");
+                        }
+                        if (ja == null) {
+                            if (arrayForm) {
+                                return newja;
+                            }
+                            return newjo;
+                        }
+
+// Content, between <...> and </...>
+
+                    } else {
+                        if (token != XML.GT) {
+                            throw x.syntaxError("Misshaped tag");
+                        }
+                        closeTag = (String)parse(x, arrayForm, newja, keepStrings);
+                        if (closeTag != null) {
+                            if (!closeTag.equals(tagName)) {
+                                throw x.syntaxError("Mismatched '" + tagName +
+                                        "' and '" + closeTag + "'");
+                            }
+                            tagName = null;
+                            if (!arrayForm && newja.length() > 0) {
+                                newjo.put("childNodes", newja);
+                            }
+                            if (ja == null) {
+                                if (arrayForm) {
+                                    return newja;
+                                }
+                                return newjo;
+                            }
+                        }
+                    }
+                }
+            } else {
+                if (ja != null) {
+                    ja.put(token instanceof String
+                        ? keepStrings ? XML.unescape((String)token) :XML.stringToValue((String)token)
+                        : token);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Convert a well-formed (but not necessarily valid) XML string into a
+     * JSONArray using the JsonML transform. Each XML tag is represented as
+     * a JSONArray in which the first element is the tag name. If the tag has
+     * attributes, then the second element will be JSONObject containing the
+     * name/value pairs. If the tag contains children, then strings and
+     * JSONArrays will represent the child tags.
+     * Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
+     * @param string The source string.
+     * @return A JSONArray containing the structured data from the XML string.
+     * @throws JSONException Thrown on error converting to a JSONArray
+     */
+    public static JSONArray toJSONArray(String string) throws JSONException {
+        return (JSONArray)parse(new XMLTokener(string), true, null, false);
+    }
+
+
+    /**
+     * Convert a well-formed (but not necessarily valid) XML string into a
+     * JSONArray using the JsonML transform. Each XML tag is represented as
+     * a JSONArray in which the first element is the tag name. If the tag has
+     * attributes, then the second element will be JSONObject containing the
+     * name/value pairs. If the tag contains children, then strings and
+     * JSONArrays will represent the child tags.
+     * As opposed to toJSONArray this method does not attempt to convert 
+     * any text node or attribute value to any type 
+     * but just leaves it as a string.
+     * Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
+     * @param string The source string.
+     * @param keepStrings If true, then values will not be coerced into boolean
+     *  or numeric values and will instead be left as strings
+     * @return A JSONArray containing the structured data from the XML string.
+     * @throws JSONException Thrown on error converting to a JSONArray
+     */
+    public static JSONArray toJSONArray(String string, boolean keepStrings) throws JSONException {
+        return (JSONArray)parse(new XMLTokener(string), true, null, keepStrings);
+    }
+
+
+    /**
+     * Convert a well-formed (but not necessarily valid) XML string into a
+     * JSONArray using the JsonML transform. Each XML tag is represented as
+     * a JSONArray in which the first element is the tag name. If the tag has
+     * attributes, then the second element will be JSONObject containing the
+     * name/value pairs. If the tag contains children, then strings and
+     * JSONArrays will represent the child content and tags.
+     * As opposed to toJSONArray this method does not attempt to convert 
+     * any text node or attribute value to any type 
+     * but just leaves it as a string.
+     * Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
+     * @param x An XMLTokener.
+     * @param keepStrings If true, then values will not be coerced into boolean
+     *  or numeric values and will instead be left as strings
+     * @return A JSONArray containing the structured data from the XML string.
+     * @throws JSONException Thrown on error converting to a JSONArray
+     */
+    public static JSONArray toJSONArray(XMLTokener x, boolean keepStrings) throws JSONException {
+        return (JSONArray)parse(x, true, null, keepStrings);
+    }
+
+
+    /**
+     * Convert a well-formed (but not necessarily valid) XML string into a
+     * JSONArray using the JsonML transform. Each XML tag is represented as
+     * a JSONArray in which the first element is the tag name. If the tag has
+     * attributes, then the second element will be JSONObject containing the
+     * name/value pairs. If the tag contains children, then strings and
+     * JSONArrays will represent the child content and tags.
+     * Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
+     * @param x An XMLTokener.
+     * @return A JSONArray containing the structured data from the XML string.
+     * @throws JSONException Thrown on error converting to a JSONArray
+     */
+    public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
+        return (JSONArray)parse(x, true, null, false);
+    }
+
+
+    /**
+     * Convert a well-formed (but not necessarily valid) XML string into a
+     * JSONObject using the JsonML transform. Each XML tag is represented as
+     * a JSONObject with a "tagName" property. If the tag has attributes, then
+     * the attributes will be in the JSONObject as properties. If the tag
+     * contains children, the object will have a "childNodes" property which
+     * will be an array of strings and JsonML JSONObjects.
+
+     * Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
+     * @param string The XML source text.
+     * @return A JSONObject containing the structured data from the XML string.
+     * @throws JSONException Thrown on error converting to a JSONObject
+     */
+    public static JSONObject toJSONObject(String string) throws JSONException {
+        return (JSONObject)parse(new XMLTokener(string), false, null, false);
+    }
+    
+    
+    /**
+     * Convert a well-formed (but not necessarily valid) XML string into a
+     * JSONObject using the JsonML transform. Each XML tag is represented as
+     * a JSONObject with a "tagName" property. If the tag has attributes, then
+     * the attributes will be in the JSONObject as properties. If the tag
+     * contains children, the object will have a "childNodes" property which
+     * will be an array of strings and JsonML JSONObjects.
+
+     * Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
+     * @param string The XML source text.
+     * @param keepStrings If true, then values will not be coerced into boolean
+     *  or numeric values and will instead be left as strings
+     * @return A JSONObject containing the structured data from the XML string.
+     * @throws JSONException Thrown on error converting to a JSONObject
+     */
+    public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException {
+        return (JSONObject)parse(new XMLTokener(string), false, null, keepStrings);
+    }
+
+    
+    /**
+     * Convert a well-formed (but not necessarily valid) XML string into a
+     * JSONObject using the JsonML transform. Each XML tag is represented as
+     * a JSONObject with a "tagName" property. If the tag has attributes, then
+     * the attributes will be in the JSONObject as properties. If the tag
+     * contains children, the object will have a "childNodes" property which
+     * will be an array of strings and JsonML JSONObjects.
+
+     * Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
+     * @param x An XMLTokener of the XML source text.
+     * @return A JSONObject containing the structured data from the XML string.
+     * @throws JSONException Thrown on error converting to a JSONObject
+     */
+    public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
+           return (JSONObject)parse(x, false, null, false);
+    }
+
+
+    /**
+     * Convert a well-formed (but not necessarily valid) XML string into a
+     * JSONObject using the JsonML transform. Each XML tag is represented as
+     * a JSONObject with a "tagName" property. If the tag has attributes, then
+     * the attributes will be in the JSONObject as properties. If the tag
+     * contains children, the object will have a "childNodes" property which
+     * will be an array of strings and JsonML JSONObjects.
+
+     * Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
+     * @param x An XMLTokener of the XML source text.
+     * @param keepStrings If true, then values will not be coerced into boolean
+     *  or numeric values and will instead be left as strings
+     * @return A JSONObject containing the structured data from the XML string.
+     * @throws JSONException Thrown on error converting to a JSONObject
+     */
+    public static JSONObject toJSONObject(XMLTokener x, boolean keepStrings) throws JSONException {
+           return (JSONObject)parse(x, false, null, keepStrings);
+    }
+
+
+    /**
+     * Reverse the JSONML transformation, making an XML text from a JSONArray.
+     * @param ja A JSONArray.
+     * @return An XML string.
+     * @throws JSONException Thrown on error converting to a string
+     */
+    public static String toString(JSONArray ja) throws JSONException {
+        int                 i;
+        JSONObject          jo;
+        int                 length;
+        Object              object;
+        StringBuilder        sb = new StringBuilder();
+        String              tagName;
+
+// Emit <tagName
+
+        tagName = ja.getString(0);
+        XML.noSpace(tagName);
+        tagName = XML.escape(tagName);
+        sb.append('<');
+        sb.append(tagName);
+
+        object = ja.opt(1);
+        if (object instanceof JSONObject) {
+            i = 2;
+            jo = (JSONObject)object;
+
+// Emit the attributes
+
+            // Don't use the new entrySet API to maintain Android support
+            for (final String key : jo.keySet()) {
+                final Object value = jo.opt(key);
+                XML.noSpace(key);
+                if (value != null) {
+                    sb.append(' ');
+                    sb.append(XML.escape(key));
+                    sb.append('=');
+                    sb.append('"');
+                    sb.append(XML.escape(value.toString()));
+                    sb.append('"');
+                }
+            }
+        } else {
+            i = 1;
+        }
+
+// Emit content in body
+
+        length = ja.length();
+        if (i >= length) {
+            sb.append('/');
+            sb.append('>');
+        } else {
+            sb.append('>');
+            do {
+                object = ja.get(i);
+                i += 1;
+                if (object != null) {
+                    if (object instanceof String) {
+                        sb.append(XML.escape(object.toString()));
+                    } else if (object instanceof JSONObject) {
+                        sb.append(toString((JSONObject)object));
+                    } else if (object instanceof JSONArray) {
+                        sb.append(toString((JSONArray)object));
+                    } else {
+                        sb.append(object.toString());
+                    }
+                }
+            } while (i < length);
+            sb.append('<');
+            sb.append('/');
+            sb.append(tagName);
+            sb.append('>');
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Reverse the JSONML transformation, making an XML text from a JSONObject.
+     * The JSONObject must contain a "tagName" property. If it has children,
+     * then it must have a "childNodes" property containing an array of objects.
+     * The other properties are attributes with string values.
+     * @param jo A JSONObject.
+     * @return An XML string.
+     * @throws JSONException Thrown on error converting to a string
+     */
+    public static String toString(JSONObject jo) throws JSONException {
+        StringBuilder sb = new StringBuilder();
+        int                 i;
+        JSONArray           ja;
+        int                 length;
+        Object              object;
+        String              tagName;
+        Object              value;
+
+//Emit <tagName
+
+        tagName = jo.optString("tagName");
+        if (tagName == null) {
+            return XML.escape(jo.toString());
+        }
+        XML.noSpace(tagName);
+        tagName = XML.escape(tagName);
+        sb.append('<');
+        sb.append(tagName);
+
+//Emit the attributes
+
+        // Don't use the new entrySet API to maintain Android support
+        for (final String key : jo.keySet()) {
+            if (!"tagName".equals(key) && !"childNodes".equals(key)) {
+                XML.noSpace(key);
+                value = jo.opt(key);
+                if (value != null) {
+                    sb.append(' ');
+                    sb.append(XML.escape(key));
+                    sb.append('=');
+                    sb.append('"');
+                    sb.append(XML.escape(value.toString()));
+                    sb.append('"');
+                }
+            }
+        }
+
+//Emit content in body
+
+        ja = jo.optJSONArray("childNodes");
+        if (ja == null) {
+            sb.append('/');
+            sb.append('>');
+        } else {
+            sb.append('>');
+            length = ja.length();
+            for (i = 0; i < length; i += 1) {
+                object = ja.get(i);
+                if (object != null) {
+                    if (object instanceof String) {
+                        sb.append(XML.escape(object.toString()));
+                    } else if (object instanceof JSONObject) {
+                        sb.append(toString((JSONObject)object));
+                    } else if (object instanceof JSONArray) {
+                        sb.append(toString((JSONArray)object));
+                    } else {
+                        sb.append(object.toString());
+                    }
+                }
+            }
+            sb.append('<');
+            sb.append('/');
+            sb.append(tagName);
+            sb.append('>');
+        }
+        return sb.toString();
+    }
+}

+ 2616 - 0
src/main/java/com/usky/xml/JSONObject.java

@@ -0,0 +1,2616 @@
+package com.usky.xml;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.regex.Pattern;
+
+/**
+ * A JSONObject is an unordered collection of name/value pairs. Its external
+ * form is a string wrapped in curly braces with colons between the names and
+ * values, and commas between the values and names. The internal form is an
+ * object having <code>get</code> and <code>opt</code> methods for accessing
+ * the values by name, and <code>put</code> methods for adding or replacing
+ * values by name. The values can be any of these types: <code>Boolean</code>,
+ * <code>JSONArray</code>, <code>JSONObject</code>, <code>Number</code>,
+ * <code>String</code>, or the <code>JSONObject.NULL</code> object. A
+ * JSONObject constructor can be used to convert an external form JSON text
+ * into an internal form whose values can be retrieved with the
+ * <code>get</code> and <code>opt</code> methods, or to convert values into a
+ * JSON text using the <code>put</code> and <code>toString</code> methods. A
+ * <code>get</code> method returns a value if one can be found, and throws an
+ * exception if one cannot be found. An <code>opt</code> method returns a
+ * default value instead of throwing an exception, and so is useful for
+ * obtaining optional values.
+ * <p>
+ * The generic <code>get()</code> and <code>opt()</code> methods return an
+ * object, which you can cast or query for type. There are also typed
+ * <code>get</code> and <code>opt</code> methods that do type checking and type
+ * coercion for you. The opt methods differ from the get methods in that they
+ * do not throw. Instead, they return a specified value, such as null.
+ * <p>
+ * The <code>put</code> methods add or replace values in an object. For
+ * example,
+ *
+ * <pre>
+ * myString = new JSONObject()
+ *         .put(&quot;JSON&quot;, &quot;Hello, World!&quot;).toString();
+ * </pre>
+ *
+ * produces the string <code>{"JSON": "Hello, World"}</code>.
+ * <p>
+ * The texts produced by the <code>toString</code> methods strictly conform to
+ * the JSON syntax rules. The constructors are more forgiving in the texts they
+ * will accept:
+ * <ul>
+ * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just
+ * before the closing brace.</li>
+ * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single
+ * quote)</small>.</li>
+ * <li>Strings do not need to be quoted at all if they do not begin with a
+ * quote or single quote, and if they do not contain leading or trailing
+ * spaces, and if they do not contain any of these characters:
+ * <code>{ } [ ] / \ : , #</code> and if they do not look like numbers and
+ * if they are not the reserved words <code>true</code>, <code>false</code>,
+ * or <code>null</code>.</li>
+ * </ul>
+ *
+ * @author JSON.org
+ * @version 2016-08-15
+ */
+public class JSONObject {
+    /**
+     * JSONObject.NULL is equivalent to the value that JavaScript calls null,
+     * whilst Java's null is equivalent to the value that JavaScript calls
+     * undefined.
+     */
+    private static final class Null {
+
+        /**
+         * There is only intended to be a single instance of the NULL object,
+         * so the clone method returns itself.
+         *
+         * @return NULL.
+         */
+        @Override
+        protected final Object clone() {
+            return this;
+        }
+
+        /**
+         * A Null object is equal to the null value and to itself.
+         *
+         * @param object
+         *            An object to test for nullness.
+         * @return true if the object parameter is the JSONObject.NULL object or
+         *         null.
+         */
+        @Override
+        public boolean equals(Object object) {
+            return object == null || object == this;
+        }
+        /**
+         * A Null object is equal to the null value and to itself.
+         *
+         * @return always returns 0.
+         */
+        @Override
+        public int hashCode() {
+            return 0;
+        }
+
+        /**
+         * Get the "null" string value.
+         *
+         * @return The string "null".
+         */
+        @Override
+        public String toString() {
+            return "null";
+        }
+    }
+
+    /**
+     *  Regular Expression Pattern that matches JSON Numbers. This is primarily used for
+     *  output to guarantee that we are always writing valid JSON.
+     */
+    static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?");
+
+    /**
+     * The map where the JSONObject's properties are kept.
+     */
+    private final Map<String, Object> map;
+
+    /**
+     * It is sometimes more convenient and less ambiguous to have a
+     * <code>NULL</code> object than to use Java's <code>null</code> value.
+     * <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.
+     * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
+     */
+    public static final Object NULL = new Null();
+
+    /**
+     * Construct an empty JSONObject.
+     */
+    public JSONObject() {
+        // HashMap is used on purpose to ensure that elements are unordered by
+        // the specification.
+        // JSON tends to be a portable transfer format to allows the container
+        // implementations to rearrange their items for a faster element
+        // retrieval based on associative access.
+        // Therefore, an implementation mustn't rely on the order of the item.
+        this.map = new HashMap<String, Object>();
+    }
+
+    /**
+     * Construct a JSONObject from a subset of another JSONObject. An array of
+     * strings is used to identify the keys that should be copied. Missing keys
+     * are ignored.
+     *
+     * @param jo
+     *            A JSONObject.
+     * @param names
+     *            An array of strings.
+     */
+    public JSONObject(JSONObject jo, String ... names) {
+        this(names.length);
+        for (int i = 0; i < names.length; i += 1) {
+            try {
+                this.putOnce(names[i], jo.opt(names[i]));
+            } catch (Exception ignore) {
+            }
+        }
+    }
+
+    /**
+     * Construct a JSONObject from a JSONTokener.
+     *
+     * @param x
+     *            A JSONTokener object containing the source string.
+     * @throws JSONException
+     *             If there is a syntax error in the source string or a
+     *             duplicated key.
+     */
+    public JSONObject(JSONTokener x) throws JSONException {
+        this();
+        char c;
+        String key;
+
+        if (x.nextClean() != '{') {
+            throw x.syntaxError("A JSONObject text must begin with '{'");
+        }
+        for (;;) {
+            c = x.nextClean();
+            switch (c) {
+            case 0:
+                throw x.syntaxError("A JSONObject text must end with '}'");
+            case '}':
+                return;
+            default:
+                x.back();
+                key = x.nextValue().toString();
+            }
+
+            // The key is followed by ':'.
+
+            c = x.nextClean();
+            if (c != ':') {
+                throw x.syntaxError("Expected a ':' after a key");
+            }
+
+            // Use syntaxError(..) to include error location
+
+            if (key != null) {
+                // Check if key exists
+                if (this.opt(key) != null) {
+                    // key already exists
+                    throw x.syntaxError("Duplicate key \"" + key + "\"");
+                }
+                // Only add value if non-null
+                Object value = x.nextValue();
+                if (value!=null) {
+                    this.put(key, value);
+                }
+            }
+
+            // Pairs are separated by ','.
+
+            switch (x.nextClean()) {
+            case ';':
+            case ',':
+                if (x.nextClean() == '}') {
+                    return;
+                }
+                x.back();
+                break;
+            case '}':
+                return;
+            default:
+                throw x.syntaxError("Expected a ',' or '}'");
+            }
+        }
+    }
+
+    /**
+     * Construct a JSONObject from a Map.
+     *
+     * @param m
+     *            A map object that can be used to initialize the contents of
+     *            the JSONObject.
+     * @throws JSONException
+     *            If a value in the map is non-finite number.
+     * @throws NullPointerException
+     *            If a key in the map is <code>null</code>
+     */
+    public JSONObject(Map<?, ?> m) {
+        if (m == null) {
+            this.map = new HashMap<String, Object>();
+        } else {
+            this.map = new HashMap<String, Object>(m.size());
+        	for (final Entry<?, ?> e : m.entrySet()) {
+        	    if(e.getKey() == null) {
+        	        throw new NullPointerException("Null key.");
+        	    }
+                final Object value = e.getValue();
+                if (value != null) {
+                    this.map.put(String.valueOf(e.getKey()), wrap(value));
+                }
+            }
+        }
+    }
+
+    /**
+     * Construct a JSONObject from an Object using bean getters. It reflects on
+     * all of the public methods of the object. For each of the methods with no
+     * parameters and a name starting with <code>"get"</code> or
+     * <code>"is"</code> followed by an uppercase letter, the method is invoked,
+     * and a key and the value returned from the getter method are put into the
+     * new JSONObject.
+     * <p>
+     * The key is formed by removing the <code>"get"</code> or <code>"is"</code>
+     * prefix. If the second remaining character is not upper case, then the
+     * first character is converted to lower case.
+     * <p>
+     * Methods that are <code>static</code>, return <code>void</code>,
+     * have parameters, or are "bridge" methods, are ignored.
+     * <p>
+     * For example, if an object has a method named <code>"getName"</code>, and
+     * if the result of calling <code>object.getName()</code> is
+     * <code>"Larry Fine"</code>, then the JSONObject will contain
+     * <code>"name": "Larry Fine"</code>.
+     * <p>
+     * The {@link JSONPropertyName} annotation can be used on a bean getter to
+     * override key name used in the JSONObject. For example, using the object
+     * above with the <code>getName</code> method, if we annotated it with:
+     * <pre>
+     * &#64;JSONPropertyName("FullName")
+     * public String getName() { return this.name; }
+     * </pre>
+     * The resulting JSON object would contain <code>"FullName": "Larry Fine"</code>
+     * <p>
+     * Similarly, the {@link JSONPropertyName} annotation can be used on non-
+     * <code>get</code> and <code>is</code> methods. We can also override key
+     * name used in the JSONObject as seen below even though the field would normally
+     * be ignored:
+     * <pre>
+     * &#64;JSONPropertyName("FullName")
+     * public String fullName() { return this.name; }
+     * </pre>
+     * The resulting JSON object would contain <code>"FullName": "Larry Fine"</code>
+     * <p>
+     * The {@link JSONPropertyIgnore} annotation can be used to force the bean property
+     * to not be serialized into JSON. If both {@link JSONPropertyIgnore} and
+     * {@link JSONPropertyName} are defined on the same method, a depth comparison is
+     * performed and the one closest to the concrete class being serialized is used.
+     * If both annotations are at the same level, then the {@link JSONPropertyIgnore}
+     * annotation takes precedent and the field is not serialized.
+     * For example, the following declaration would prevent the <code>getName</code>
+     * method from being serialized:
+     * <pre>
+     * &#64;JSONPropertyName("FullName")
+     * &#64;JSONPropertyIgnore
+     * public String getName() { return this.name; }
+     * </pre>
+     * <p>
+     *
+     * @param bean
+     *            An object that has getter methods that should be used to make
+     *            a JSONObject.
+     */
+    public JSONObject(Object bean) {
+        this();
+        this.populateMap(bean);
+    }
+
+    /**
+     * Construct a JSONObject from an Object, using reflection to find the
+     * public members. The resulting JSONObject's keys will be the strings from
+     * the names array, and the values will be the field values associated with
+     * those keys in the object. If a key is not found or not visible, then it
+     * will not be copied into the new JSONObject.
+     *
+     * @param object
+     *            An object that has fields that should be used to make a
+     *            JSONObject.
+     * @param names
+     *            An array of strings, the names of the fields to be obtained
+     *            from the object.
+     */
+    public JSONObject(Object object, String ... names) {
+        this(names.length);
+        Class<?> c = object.getClass();
+        for (int i = 0; i < names.length; i += 1) {
+            String name = names[i];
+            try {
+                this.putOpt(name, c.getField(name).get(object));
+            } catch (Exception ignore) {
+            }
+        }
+    }
+
+    /**
+     * Construct a JSONObject from a source JSON text string. This is the most
+     * commonly used JSONObject constructor.
+     *
+     * @param source
+     *            A string beginning with <code>{</code>&nbsp;<small>(left
+     *            brace)</small> and ending with <code>}</code>
+     *            &nbsp;<small>(right brace)</small>.
+     * @exception JSONException
+     *                If there is a syntax error in the source string or a
+     *                duplicated key.
+     */
+    public JSONObject(String source) throws JSONException {
+        this(new JSONTokener(source));
+    }
+
+    /**
+     * Construct a JSONObject from a ResourceBundle.
+     *
+     * @param baseName
+     *            The ResourceBundle base name.
+     * @param locale
+     *            The Locale to load the ResourceBundle for.
+     * @throws JSONException
+     *             If any JSONExceptions are detected.
+     */
+    public JSONObject(String baseName, Locale locale) throws JSONException {
+        this();
+        ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale,
+                Thread.currentThread().getContextClassLoader());
+
+// Iterate through the keys in the bundle.
+
+        Enumeration<String> keys = bundle.getKeys();
+        while (keys.hasMoreElements()) {
+            Object key = keys.nextElement();
+            if (key != null) {
+
+// Go through the path, ensuring that there is a nested JSONObject for each
+// segment except the last. Add the value using the last segment's name into
+// the deepest nested JSONObject.
+
+                String[] path = ((String) key).split("\\.");
+                int last = path.length - 1;
+                JSONObject target = this;
+                for (int i = 0; i < last; i += 1) {
+                    String segment = path[i];
+                    JSONObject nextTarget = target.optJSONObject(segment);
+                    if (nextTarget == null) {
+                        nextTarget = new JSONObject();
+                        target.put(segment, nextTarget);
+                    }
+                    target = nextTarget;
+                }
+                target.put(path[last], bundle.getString((String) key));
+            }
+        }
+    }
+
+    /**
+     * Constructor to specify an initial capacity of the internal map. Useful for library
+     * internal calls where we know, or at least can best guess, how big this JSONObject
+     * will be.
+     *
+     * @param initialCapacity initial capacity of the internal map.
+     */
+    protected JSONObject(int initialCapacity){
+        this.map = new HashMap<String, Object>(initialCapacity);
+    }
+
+    /**
+     * Accumulate values under a key. It is similar to the put method except
+     * that if there is already an object stored under the key then a JSONArray
+     * is stored under the key to hold all of the accumulated values. If there
+     * is already a JSONArray, then the new value is appended to it. In
+     * contrast, the put method replaces the previous value.
+     *
+     * If only one value is accumulated that is not a JSONArray, then the result
+     * will be the same as using put. But if multiple values are accumulated,
+     * then the result will be like append.
+     *
+     * @param key
+     *            A key string.
+     * @param value
+     *            An object to be accumulated under the key.
+     * @return this.
+     * @throws JSONException
+     *            If the value is non-finite number.
+     * @throws NullPointerException
+     *            If the key is <code>null</code>.
+     */
+    public JSONObject accumulate(String key, Object value) throws JSONException {
+        testValidity(value);
+        Object object = this.opt(key);
+        if (object == null) {
+            this.put(key,
+                    value instanceof JSONArray ? new JSONArray().put(value)
+                            : value);
+        } else if (object instanceof JSONArray) {
+            ((JSONArray) object).put(value);
+        } else {
+            this.put(key, new JSONArray().put(object).put(value));
+        }
+        return this;
+    }
+
+    /**
+     * Append values to the array under a key. If the key does not exist in the
+     * JSONObject, then the key is put in the JSONObject with its value being a
+     * JSONArray containing the value parameter. If the key was already
+     * associated with a JSONArray, then the value parameter is appended to it.
+     *
+     * @param key
+     *            A key string.
+     * @param value
+     *            An object to be accumulated under the key.
+     * @return this.
+     * @throws JSONException
+     *            If the value is non-finite number or if the current value associated with
+     *             the key is not a JSONArray.
+     * @throws NullPointerException
+     *            If the key is <code>null</code>.
+     */
+    public JSONObject append(String key, Object value) throws JSONException {
+        testValidity(value);
+        Object object = this.opt(key);
+        if (object == null) {
+            this.put(key, new JSONArray().put(value));
+        } else if (object instanceof JSONArray) {
+            this.put(key, ((JSONArray) object).put(value));
+        } else {
+            throw wrongValueFormatException(key, "JSONArray", null, null);
+        }
+        return this;
+    }
+
+    /**
+     * Produce a string from a double. The string "null" will be returned if the
+     * number is not finite.
+     *
+     * @param d
+     *            A double.
+     * @return A String.
+     */
+    public static String doubleToString(double d) {
+        if (Double.isInfinite(d) || Double.isNaN(d)) {
+            return "null";
+        }
+
+// Shave off trailing zeros and decimal point, if possible.
+
+        String string = Double.toString(d);
+        if (string.indexOf('.') > 0 && string.indexOf('e') < 0
+                && string.indexOf('E') < 0) {
+            while (string.endsWith("0")) {
+                string = string.substring(0, string.length() - 1);
+            }
+            if (string.endsWith(".")) {
+                string = string.substring(0, string.length() - 1);
+            }
+        }
+        return string;
+    }
+
+    /**
+     * Get the value object associated with a key.
+     *
+     * @param key
+     *            A key string.
+     * @return The object associated with the key.
+     * @throws JSONException
+     *             if the key is not found.
+     */
+    public Object get(String key) throws JSONException {
+        if (key == null) {
+            throw new JSONException("Null key.");
+        }
+        Object object = this.opt(key);
+        if (object == null) {
+            throw new JSONException("JSONObject[" + quote(key) + "] not found.");
+        }
+        return object;
+    }
+
+    /**
+     * Get the enum value associated with a key.
+     *
+     * @param <E>
+     *            Enum Type
+     * @param clazz
+     *           The type of enum to retrieve.
+     * @param key
+     *           A key string.
+     * @return The enum value associated with the key
+     * @throws JSONException
+     *             if the key is not found or if the value cannot be converted
+     *             to an enum.
+     */
+    public <E extends Enum<E>> E getEnum(Class<E> clazz, String key) throws JSONException {
+        E val = optEnum(clazz, key);
+        if(val==null) {
+            // JSONException should really take a throwable argument.
+            // If it did, I would re-implement this with the Enum.valueOf
+            // method and place any thrown exception in the JSONException
+            throw wrongValueFormatException(key, "enum of type " + quote(clazz.getSimpleName()), null);
+        }
+        return val;
+    }
+
+    /**
+     * Get the boolean value associated with a key.
+     *
+     * @param key
+     *            A key string.
+     * @return The truth.
+     * @throws JSONException
+     *             if the value is not a Boolean or the String "true" or
+     *             "false".
+     */
+    public boolean getBoolean(String key) throws JSONException {
+        Object object = this.get(key);
+        if (object.equals(Boolean.FALSE)
+                || (object instanceof String && ((String) object)
+                        .equalsIgnoreCase("false"))) {
+            return false;
+        } else if (object.equals(Boolean.TRUE)
+                || (object instanceof String && ((String) object)
+                        .equalsIgnoreCase("true"))) {
+            return true;
+        }
+        throw wrongValueFormatException(key, "Boolean", null);
+    }
+
+    /**
+     * Get the BigInteger value associated with a key.
+     *
+     * @param key
+     *            A key string.
+     * @return The numeric value.
+     * @throws JSONException
+     *             if the key is not found or if the value cannot
+     *             be converted to BigInteger.
+     */
+    public BigInteger getBigInteger(String key) throws JSONException {
+        Object object = this.get(key);
+        BigInteger ret = objectToBigInteger(object, null);
+        if (ret != null) {
+            return ret;
+        }
+        throw wrongValueFormatException(key, "BigInteger", object, null);
+    }
+
+    /**
+     * Get the BigDecimal value associated with a key. If the value is float or
+     * double, the the {@link BigDecimal#BigDecimal(double)} constructor will
+     * be used. See notes on the constructor for conversion issues that may
+     * arise.
+     *
+     * @param key
+     *            A key string.
+     * @return The numeric value.
+     * @throws JSONException
+     *             if the key is not found or if the value
+     *             cannot be converted to BigDecimal.
+     */
+    public BigDecimal getBigDecimal(String key) throws JSONException {
+        Object object = this.get(key);
+        BigDecimal ret = objectToBigDecimal(object, null);
+        if (ret != null) {
+            return ret;
+        }
+        throw wrongValueFormatException(key, "BigDecimal", object, null);
+    }
+
+    /**
+     * Get the double value associated with a key.
+     *
+     * @param key
+     *            A key string.
+     * @return The numeric value.
+     * @throws JSONException
+     *             if the key is not found or if the value is not a Number
+     *             object and cannot be converted to a number.
+     */
+    public double getDouble(String key) throws JSONException {
+        final Object object = this.get(key);
+        if(object instanceof Number) {
+            return ((Number)object).doubleValue();
+        }
+        try {
+            return Double.parseDouble(object.toString());
+        } catch (Exception e) {
+            throw wrongValueFormatException(key, "double", e);
+        }
+    }
+
+    /**
+     * Get the float value associated with a key.
+     *
+     * @param key
+     *            A key string.
+     * @return The numeric value.
+     * @throws JSONException
+     *             if the key is not found or if the value is not a Number
+     *             object and cannot be converted to a number.
+     */
+    public float getFloat(String key) throws JSONException {
+        final Object object = this.get(key);
+        if(object instanceof Number) {
+            return ((Number)object).floatValue();
+        }
+        try {
+            return Float.parseFloat(object.toString());
+        } catch (Exception e) {
+            throw wrongValueFormatException(key, "float", e);
+        }
+    }
+
+    /**
+     * Get the Number value associated with a key.
+     *
+     * @param key
+     *            A key string.
+     * @return The numeric value.
+     * @throws JSONException
+     *             if the key is not found or if the value is not a Number
+     *             object and cannot be converted to a number.
+     */
+    public Number getNumber(String key) throws JSONException {
+        Object object = this.get(key);
+        try {
+            if (object instanceof Number) {
+                return (Number)object;
+            }
+            return stringToNumber(object.toString());
+        } catch (Exception e) {
+            throw wrongValueFormatException(key, "number", e);
+        }
+    }
+
+    /**
+     * Get the int value associated with a key.
+     *
+     * @param key
+     *            A key string.
+     * @return The integer value.
+     * @throws JSONException
+     *             if the key is not found or if the value cannot be converted
+     *             to an integer.
+     */
+    public int getInt(String key) throws JSONException {
+        final Object object = this.get(key);
+        if(object instanceof Number) {
+            return ((Number)object).intValue();
+        }
+        try {
+            return Integer.parseInt(object.toString());
+        } catch (Exception e) {
+            throw wrongValueFormatException(key, "int", e);
+        }
+    }
+
+    /**
+     * Get the JSONArray value associated with a key.
+     *
+     * @param key
+     *            A key string.
+     * @return A JSONArray which is the value.
+     * @throws JSONException
+     *             if the key is not found or if the value is not a JSONArray.
+     */
+    public JSONArray getJSONArray(String key) throws JSONException {
+        Object object = this.get(key);
+        if (object instanceof JSONArray) {
+            return (JSONArray) object;
+        }
+        throw wrongValueFormatException(key, "JSONArray", null);
+    }
+
+    /**
+     * Get the JSONObject value associated with a key.
+     *
+     * @param key
+     *            A key string.
+     * @return A JSONObject which is the value.
+     * @throws JSONException
+     *             if the key is not found or if the value is not a JSONObject.
+     */
+    public JSONObject getJSONObject(String key) throws JSONException {
+        Object object = this.get(key);
+        if (object instanceof JSONObject) {
+            return (JSONObject) object;
+        }
+        throw wrongValueFormatException(key, "JSONObject", null);
+    }
+
+    /**
+     * Get the long value associated with a key.
+     *
+     * @param key
+     *            A key string.
+     * @return The long value.
+     * @throws JSONException
+     *             if the key is not found or if the value cannot be converted
+     *             to a long.
+     */
+    public long getLong(String key) throws JSONException {
+        final Object object = this.get(key);
+        if(object instanceof Number) {
+            return ((Number)object).longValue();
+        }
+        try {
+            return Long.parseLong(object.toString());
+        } catch (Exception e) {
+            throw wrongValueFormatException(key, "long", e);
+        }
+    }
+
+    /**
+     * Get an array of field names from a JSONObject.
+     *
+     * @param jo
+     *            JSON object
+     * @return An array of field names, or null if there are no names.
+     */
+    public static String[] getNames(JSONObject jo) {
+        if (jo.isEmpty()) {
+            return null;
+        }
+        return jo.keySet().toArray(new String[jo.length()]);
+    }
+
+    /**
+     * Get an array of public field names from an Object.
+     *
+     * @param object
+     *            object to read
+     * @return An array of field names, or null if there are no names.
+     */
+    public static String[] getNames(Object object) {
+        if (object == null) {
+            return null;
+        }
+        Class<?> klass = object.getClass();
+        Field[] fields = klass.getFields();
+        int length = fields.length;
+        if (length == 0) {
+            return null;
+        }
+        String[] names = new String[length];
+        for (int i = 0; i < length; i += 1) {
+            names[i] = fields[i].getName();
+        }
+        return names;
+    }
+
+    /**
+     * Get the string associated with a key.
+     *
+     * @param key
+     *            A key string.
+     * @return A string which is the value.
+     * @throws JSONException
+     *             if there is no string value for the key.
+     */
+    public String getString(String key) throws JSONException {
+        Object object = this.get(key);
+        if (object instanceof String) {
+            return (String) object;
+        }
+        throw wrongValueFormatException(key, "string", null);
+    }
+
+    /**
+     * Determine if the JSONObject contains a specific key.
+     *
+     * @param key
+     *            A key string.
+     * @return true if the key exists in the JSONObject.
+     */
+    public boolean has(String key) {
+        return this.map.containsKey(key);
+    }
+
+    /**
+     * Increment a property of a JSONObject. If there is no such property,
+     * create one with a value of 1 (Integer). If there is such a property, and if it is
+     * an Integer, Long, Double, Float, BigInteger, or BigDecimal then add one to it.
+     * No overflow bounds checking is performed, so callers should initialize the key
+     * prior to this call with an appropriate type that can handle the maximum expected
+     * value.
+     *
+     * @param key
+     *            A key string.
+     * @return this.
+     * @throws JSONException
+     *             If there is already a property with this name that is not an
+     *             Integer, Long, Double, or Float.
+     */
+    public JSONObject increment(String key) throws JSONException {
+        Object value = this.opt(key);
+        if (value == null) {
+            this.put(key, 1);
+        } else if (value instanceof Integer) {
+            this.put(key, ((Integer) value).intValue() + 1);
+        } else if (value instanceof Long) {
+            this.put(key, ((Long) value).longValue() + 1L);
+        } else if (value instanceof BigInteger) {
+            this.put(key, ((BigInteger)value).add(BigInteger.ONE));
+        } else if (value instanceof Float) {
+            this.put(key, ((Float) value).floatValue() + 1.0f);
+        } else if (value instanceof Double) {
+            this.put(key, ((Double) value).doubleValue() + 1.0d);
+        } else if (value instanceof BigDecimal) {
+            this.put(key, ((BigDecimal)value).add(BigDecimal.ONE));
+        } else {
+            throw new JSONException("Unable to increment [" + quote(key) + "].");
+        }
+        return this;
+    }
+
+    /**
+     * Determine if the value associated with the key is <code>null</code> or if there is no
+     * value.
+     *
+     * @param key
+     *            A key string.
+     * @return true if there is no value associated with the key or if the value
+     *        is the JSONObject.NULL object.
+     */
+    public boolean isNull(String key) {
+        return JSONObject.NULL.equals(this.opt(key));
+    }
+
+    /**
+     * Get an enumeration of the keys of the JSONObject. Modifying this key Set will also
+     * modify the JSONObject. Use with caution.
+     *
+     * @see Set#iterator()
+     *
+     * @return An iterator of the keys.
+     */
+    public Iterator<String> keys() {
+        return this.keySet().iterator();
+    }
+
+    /**
+     * Get a set of keys of the JSONObject. Modifying this key Set will also modify the
+     * JSONObject. Use with caution.
+     *
+     * @see Map#keySet()
+     *
+     * @return A keySet.
+     */
+    public Set<String> keySet() {
+        return this.map.keySet();
+    }
+
+    /**
+     * Get a set of entries of the JSONObject. These are raw values and may not
+     * match what is returned by the JSONObject get* and opt* functions. Modifying
+     * the returned EntrySet or the Entry objects contained therein will modify the
+     * backing JSONObject. This does not return a clone or a read-only view.
+     *
+     * Use with caution.
+     *
+     * @see Map#entrySet()
+     *
+     * @return An Entry Set
+     */
+    protected Set<Entry<String, Object>> entrySet() {
+        return this.map.entrySet();
+    }
+
+    /**
+     * Get the number of keys stored in the JSONObject.
+     *
+     * @return The number of keys in the JSONObject.
+     */
+    public int length() {
+        return this.map.size();
+    }
+
+    /**
+     * Removes all of the elements from this JSONObject.
+     * The JSONObject will be empty after this call returns.
+     */
+    public void clear() {
+        this.map.clear();
+    }
+
+    /**
+     * Check if JSONObject is empty.
+     *
+     * @return true if JSONObject is empty, otherwise false.
+     */
+    public boolean isEmpty() {
+        return this.map.isEmpty();
+    }
+
+    /**
+     * Produce a JSONArray containing the names of the elements of this
+     * JSONObject.
+     *
+     * @return A JSONArray containing the key strings, or null if the JSONObject
+     *        is empty.
+     */
+    public JSONArray names() {
+    	if(this.map.isEmpty()) {
+    		return null;
+    	}
+        return new JSONArray(this.map.keySet());
+    }
+
+    /**
+     * Produce a string from a Number.
+     *
+     * @param number
+     *            A Number
+     * @return A String.
+     * @throws JSONException
+     *             If n is a non-finite number.
+     */
+    public static String numberToString(Number number) throws JSONException {
+        if (number == null) {
+            throw new JSONException("Null pointer");
+        }
+        testValidity(number);
+
+        // Shave off trailing zeros and decimal point, if possible.
+
+        String string = number.toString();
+        if (string.indexOf('.') > 0 && string.indexOf('e') < 0
+                && string.indexOf('E') < 0) {
+            while (string.endsWith("0")) {
+                string = string.substring(0, string.length() - 1);
+            }
+            if (string.endsWith(".")) {
+                string = string.substring(0, string.length() - 1);
+            }
+        }
+        return string;
+    }
+
+    /**
+     * Get an optional value associated with a key.
+     *
+     * @param key
+     *            A key string.
+     * @return An object which is the value, or null if there is no value.
+     */
+    public Object opt(String key) {
+        return key == null ? null : this.map.get(key);
+    }
+
+    /**
+     * Get the enum value associated with a key.
+     *
+     * @param <E>
+     *            Enum Type
+     * @param clazz
+     *            The type of enum to retrieve.
+     * @param key
+     *            A key string.
+     * @return The enum value associated with the key or null if not found
+     */
+    public <E extends Enum<E>> E optEnum(Class<E> clazz, String key) {
+        return this.optEnum(clazz, key, null);
+    }
+
+    /**
+     * Get the enum value associated with a key.
+     *
+     * @param <E>
+     *            Enum Type
+     * @param clazz
+     *            The type of enum to retrieve.
+     * @param key
+     *            A key string.
+     * @param defaultValue
+     *            The default in case the value is not found
+     * @return The enum value associated with the key or defaultValue
+     *            if the value is not found or cannot be assigned to <code>clazz</code>
+     */
+    public <E extends Enum<E>> E optEnum(Class<E> clazz, String key, E defaultValue) {
+        try {
+            Object val = this.opt(key);
+            if (NULL.equals(val)) {
+                return defaultValue;
+            }
+            if (clazz.isAssignableFrom(val.getClass())) {
+                // we just checked it!
+                @SuppressWarnings("unchecked")
+                E myE = (E) val;
+                return myE;
+            }
+            return Enum.valueOf(clazz, val.toString());
+        } catch (IllegalArgumentException e) {
+            return defaultValue;
+        } catch (NullPointerException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Get an optional boolean associated with a key. It returns false if there
+     * is no such key, or if the value is not Boolean.TRUE or the String "true".
+     *
+     * @param key
+     *            A key string.
+     * @return The truth.
+     */
+    public boolean optBoolean(String key) {
+        return this.optBoolean(key, false);
+    }
+
+    /**
+     * Get an optional boolean associated with a key. It returns the
+     * defaultValue if there is no such key, or if it is not a Boolean or the
+     * String "true" or "false" (case insensitive).
+     *
+     * @param key
+     *            A key string.
+     * @param defaultValue
+     *            The default.
+     * @return The truth.
+     */
+    public boolean optBoolean(String key, boolean defaultValue) {
+        Object val = this.opt(key);
+        if (NULL.equals(val)) {
+            return defaultValue;
+        }
+        if (val instanceof Boolean){
+            return ((Boolean) val).booleanValue();
+        }
+        try {
+            // we'll use the get anyway because it does string conversion.
+            return this.getBoolean(key);
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Get an optional BigDecimal associated with a key, or the defaultValue if
+     * there is no such key or if its value is not a number. If the value is a
+     * string, an attempt will be made to evaluate it as a number. If the value
+     * is float or double, then the {@link BigDecimal#BigDecimal(double)}
+     * constructor will be used. See notes on the constructor for conversion
+     * issues that may arise.
+     *
+     * @param key
+     *            A key string.
+     * @param defaultValue
+     *            The default.
+     * @return An object which is the value.
+     */
+    public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) {
+        Object val = this.opt(key);
+        return objectToBigDecimal(val, defaultValue);
+    }
+
+    /**
+     * @param val value to convert
+     * @param defaultValue default value to return is the conversion doesn't work or is null.
+     * @return BigDecimal conversion of the original value, or the defaultValue if unable
+     *          to convert.
+     */
+    static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) {
+        if (NULL.equals(val)) {
+            return defaultValue;
+        }
+        if (val instanceof BigDecimal){
+            return (BigDecimal) val;
+        }
+        if (val instanceof BigInteger){
+            return new BigDecimal((BigInteger) val);
+        }
+        if (val instanceof Double || val instanceof Float){
+            if (!numberIsFinite((Number)val)) {
+                return defaultValue;
+            }
+            return new BigDecimal(((Number) val).doubleValue());
+        }
+        if (val instanceof Long || val instanceof Integer
+                || val instanceof Short || val instanceof Byte){
+            return new BigDecimal(((Number) val).longValue());
+        }
+        // don't check if it's a string in case of unchecked Number subclasses
+        try {
+            return new BigDecimal(val.toString());
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Get an optional BigInteger associated with a key, or the defaultValue if
+     * there is no such key or if its value is not a number. If the value is a
+     * string, an attempt will be made to evaluate it as a number.
+     *
+     * @param key
+     *            A key string.
+     * @param defaultValue
+     *            The default.
+     * @return An object which is the value.
+     */
+    public BigInteger optBigInteger(String key, BigInteger defaultValue) {
+        Object val = this.opt(key);
+        return objectToBigInteger(val, defaultValue);
+    }
+
+    /**
+     * @param val value to convert
+     * @param defaultValue default value to return is the conversion doesn't work or is null.
+     * @return BigInteger conversion of the original value, or the defaultValue if unable
+     *          to convert.
+     */
+    static BigInteger objectToBigInteger(Object val, BigInteger defaultValue) {
+        if (NULL.equals(val)) {
+            return defaultValue;
+        }
+        if (val instanceof BigInteger){
+            return (BigInteger) val;
+        }
+        if (val instanceof BigDecimal){
+            return ((BigDecimal) val).toBigInteger();
+        }
+        if (val instanceof Double || val instanceof Float){
+            if (!numberIsFinite((Number)val)) {
+                return defaultValue;
+            }
+            return new BigDecimal(((Number) val).doubleValue()).toBigInteger();
+        }
+        if (val instanceof Long || val instanceof Integer
+                || val instanceof Short || val instanceof Byte){
+            return BigInteger.valueOf(((Number) val).longValue());
+        }
+        // don't check if it's a string in case of unchecked Number subclasses
+        try {
+            // the other opt functions handle implicit conversions, i.e.
+            // jo.put("double",1.1d);
+            // jo.optInt("double"); -- will return 1, not an error
+            // this conversion to BigDecimal then to BigInteger is to maintain
+            // that type cast support that may truncate the decimal.
+            final String valStr = val.toString();
+            if(isDecimalNotation(valStr)) {
+                return new BigDecimal(valStr).toBigInteger();
+            }
+            return new BigInteger(valStr);
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Get an optional double associated with a key, or NaN if there is no such
+     * key or if its value is not a number. If the value is a string, an attempt
+     * will be made to evaluate it as a number.
+     *
+     * @param key
+     *            A string which is the key.
+     * @return An object which is the value.
+     */
+    public double optDouble(String key) {
+        return this.optDouble(key, Double.NaN);
+    }
+
+    /**
+     * Get an optional double associated with a key, or the defaultValue if
+     * there is no such key or if its value is not a number. If the value is a
+     * string, an attempt will be made to evaluate it as a number.
+     *
+     * @param key
+     *            A key string.
+     * @param defaultValue
+     *            The default.
+     * @return An object which is the value.
+     */
+    public double optDouble(String key, double defaultValue) {
+        Number val = this.optNumber(key);
+        if (val == null) {
+            return defaultValue;
+        }
+        final double doubleValue = val.doubleValue();
+        // if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) {
+        // return defaultValue;
+        // }
+        return doubleValue;
+    }
+
+    /**
+     * Get the optional double value associated with an index. NaN is returned
+     * if there is no value for the index, or if the value is not a number and
+     * cannot be converted to a number.
+     *
+     * @param key
+     *            A key string.
+     * @return The value.
+     */
+    public float optFloat(String key) {
+        return this.optFloat(key, Float.NaN);
+    }
+
+    /**
+     * Get the optional double value associated with an index. The defaultValue
+     * is returned if there is no value for the index, or if the value is not a
+     * number and cannot be converted to a number.
+     *
+     * @param key
+     *            A key string.
+     * @param defaultValue
+     *            The default value.
+     * @return The value.
+     */
+    public float optFloat(String key, float defaultValue) {
+        Number val = this.optNumber(key);
+        if (val == null) {
+            return defaultValue;
+        }
+        final float floatValue = val.floatValue();
+        // if (Float.isNaN(floatValue) || Float.isInfinite(floatValue)) {
+        // return defaultValue;
+        // }
+        return floatValue;
+    }
+
+    /**
+     * Get an optional int value associated with a key, or zero if there is no
+     * such key or if the value is not a number. If the value is a string, an
+     * attempt will be made to evaluate it as a number.
+     *
+     * @param key
+     *            A key string.
+     * @return An object which is the value.
+     */
+    public int optInt(String key) {
+        return this.optInt(key, 0);
+    }
+
+    /**
+     * Get an optional int value associated with a key, or the default if there
+     * is no such key or if the value is not a number. If the value is a string,
+     * an attempt will be made to evaluate it as a number.
+     *
+     * @param key
+     *            A key string.
+     * @param defaultValue
+     *            The default.
+     * @return An object which is the value.
+     */
+    public int optInt(String key, int defaultValue) {
+        final Number val = this.optNumber(key, null);
+        if (val == null) {
+            return defaultValue;
+        }
+        return val.intValue();
+    }
+
+    /**
+     * Get an optional JSONArray associated with a key. It returns null if there
+     * is no such key, or if its value is not a JSONArray.
+     *
+     * @param key
+     *            A key string.
+     * @return A JSONArray which is the value.
+     */
+    public JSONArray optJSONArray(String key) {
+        Object o = this.opt(key);
+        return o instanceof JSONArray ? (JSONArray) o : null;
+    }
+
+    /**
+     * Get an optional JSONObject associated with a key. It returns null if
+     * there is no such key, or if its value is not a JSONObject.
+     *
+     * @param key
+     *            A key string.
+     * @return A JSONObject which is the value.
+     */
+    public JSONObject optJSONObject(String key) {
+        Object object = this.opt(key);
+        return object instanceof JSONObject ? (JSONObject) object : null;
+    }
+
+    /**
+     * Get an optional long value associated with a key, or zero if there is no
+     * such key or if the value is not a number. If the value is a string, an
+     * attempt will be made to evaluate it as a number.
+     *
+     * @param key
+     *            A key string.
+     * @return An object which is the value.
+     */
+    public long optLong(String key) {
+        return this.optLong(key, 0);
+    }
+
+    /**
+     * Get an optional long value associated with a key, or the default if there
+     * is no such key or if the value is not a number. If the value is a string,
+     * an attempt will be made to evaluate it as a number.
+     *
+     * @param key
+     *            A key string.
+     * @param defaultValue
+     *            The default.
+     * @return An object which is the value.
+     */
+    public long optLong(String key, long defaultValue) {
+        final Number val = this.optNumber(key, null);
+        if (val == null) {
+            return defaultValue;
+        }
+
+        return val.longValue();
+    }
+
+    /**
+     * Get an optional {@link Number} value associated with a key, or <code>null</code>
+     * if there is no such key or if the value is not a number. If the value is a string,
+     * an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method
+     * would be used in cases where type coercion of the number value is unwanted.
+     *
+     * @param key
+     *            A key string.
+     * @return An object which is the value.
+     */
+    public Number optNumber(String key) {
+        return this.optNumber(key, null);
+    }
+
+    /**
+     * Get an optional {@link Number} value associated with a key, or the default if there
+     * is no such key or if the value is not a number. If the value is a string,
+     * an attempt will be made to evaluate it as a number. This method
+     * would be used in cases where type coercion of the number value is unwanted.
+     *
+     * @param key
+     *            A key string.
+     * @param defaultValue
+     *            The default.
+     * @return An object which is the value.
+     */
+    public Number optNumber(String key, Number defaultValue) {
+        Object val = this.opt(key);
+        if (NULL.equals(val)) {
+            return defaultValue;
+        }
+        if (val instanceof Number){
+            return (Number) val;
+        }
+
+        try {
+            return stringToNumber(val.toString());
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Get an optional string associated with a key. It returns an empty string
+     * if there is no such key. If the value is not a string and is not null,
+     * then it is converted to a string.
+     *
+     * @param key
+     *            A key string.
+     * @return A string which is the value.
+     */
+    public String optString(String key) {
+        return this.optString(key, "");
+    }
+
+    /**
+     * Get an optional string associated with a key. It returns the defaultValue
+     * if there is no such key.
+     *
+     * @param key
+     *            A key string.
+     * @param defaultValue
+     *            The default.
+     * @return A string which is the value.
+     */
+    public String optString(String key, String defaultValue) {
+        Object object = this.opt(key);
+        return NULL.equals(object) ? defaultValue : object.toString();
+    }
+
+    /**
+     * Populates the internal map of the JSONObject with the bean properties. The
+     * bean can not be recursive.
+     *
+     * @see JSONObject#JSONObject(Object)
+     *
+     * @param bean
+     *            the bean
+     */
+    private void populateMap(Object bean) {
+        Class<?> klass = bean.getClass();
+
+        // If klass is a System class then set includeSuperClass to false.
+
+        boolean includeSuperClass = klass.getClassLoader() != null;
+
+        Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods();
+        for (final Method method : methods) {
+            final int modifiers = method.getModifiers();
+            if (Modifier.isPublic(modifiers)
+                    && !Modifier.isStatic(modifiers)
+                    && method.getParameterTypes().length == 0
+                    && !method.isBridge()
+                    && method.getReturnType() != Void.TYPE
+                    && isValidMethodName(method.getName())) {
+                final String key = getKeyNameFromMethod(method);
+                if (key != null && !key.isEmpty()) {
+                    try {
+                        final Object result = method.invoke(bean);
+                        if (result != null) {
+                            this.map.put(key, wrap(result));
+                            // we don't use the result anywhere outside of wrap
+                            // if it's a resource we should be sure to close it
+                            // after calling toString
+                            if (result instanceof Closeable) {
+                                try {
+                                    ((Closeable) result).close();
+                                } catch (IOException ignore) {
+                                }
+                            }
+                        }
+                    } catch (IllegalAccessException ignore) {
+                    } catch (IllegalArgumentException ignore) {
+                    } catch (InvocationTargetException ignore) {
+                    }
+                }
+            }
+        }
+    }
+
+    private static boolean isValidMethodName(String name) {
+        return !"getClass".equals(name) && !"getDeclaringClass".equals(name);
+    }
+
+    private static String getKeyNameFromMethod(Method method) {
+        final int ignoreDepth = getAnnotationDepth(method, JSONPropertyIgnore.class);
+        if (ignoreDepth > 0) {
+            final int forcedNameDepth = getAnnotationDepth(method, JSONPropertyName.class);
+            if (forcedNameDepth < 0 || ignoreDepth <= forcedNameDepth) {
+                // the hierarchy asked to ignore, and the nearest name override
+                // was higher or non-existent
+                return null;
+            }
+        }
+        JSONPropertyName annotation = getAnnotation(method, JSONPropertyName.class);
+        if (annotation != null && annotation.value() != null && !annotation.value().isEmpty()) {
+            return annotation.value();
+        }
+        String key;
+        final String name = method.getName();
+        if (name.startsWith("get") && name.length() > 3) {
+            key = name.substring(3);
+        } else if (name.startsWith("is") && name.length() > 2) {
+            key = name.substring(2);
+        } else {
+            return null;
+        }
+        // if the first letter in the key is not uppercase, then skip.
+        // This is to maintain backwards compatibility before PR406
+        // (https://github.com/stleary/JSON-java/pull/406/)
+        if (key.length() == 0 || Character.isLowerCase(key.charAt(0))) {
+            return null;
+        }
+        if (key.length() == 1) {
+            key = key.toLowerCase(Locale.ROOT);
+        } else if (!Character.isUpperCase(key.charAt(1))) {
+            key = key.substring(0, 1).toLowerCase(Locale.ROOT) + key.substring(1);
+        }
+        return key;
+    }
+
+    /**
+     * Searches the class hierarchy to see if the method or it's super
+     * implementations and interfaces has the annotation.
+     *
+     * @param <A>
+     *            type of the annotation
+     *
+     * @param m
+     *            method to check
+     * @param annotationClass
+     *            annotation to look for
+     * @return the {@link Annotation} if the annotation exists on the current method
+     *         or one of it's super class definitions
+     */
+    private static <A extends Annotation> A getAnnotation(final Method m, final Class<A> annotationClass) {
+        // if we have invalid data the result is null
+        if (m == null || annotationClass == null) {
+            return null;
+        }
+
+        if (m.isAnnotationPresent(annotationClass)) {
+            return m.getAnnotation(annotationClass);
+        }
+
+        // if we've already reached the Object class, return null;
+        Class<?> c = m.getDeclaringClass();
+        if (c.getSuperclass() == null) {
+            return null;
+        }
+
+        // check directly implemented interfaces for the method being checked
+        for (Class<?> i : c.getInterfaces()) {
+            try {
+                Method im = i.getMethod(m.getName(), m.getParameterTypes());
+                return getAnnotation(im, annotationClass);
+            } catch (final SecurityException ex) {
+                continue;
+            } catch (final NoSuchMethodException ex) {
+                continue;
+            }
+        }
+
+        try {
+            return getAnnotation(
+                    c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()),
+                    annotationClass);
+        } catch (final SecurityException ex) {
+            return null;
+        } catch (final NoSuchMethodException ex) {
+            return null;
+        }
+    }
+
+    /**
+     * Searches the class hierarchy to see if the method or it's super
+     * implementations and interfaces has the annotation. Returns the depth of the
+     * annotation in the hierarchy.
+     *
+     * @param <A>
+     *            type of the annotation
+     *
+     * @param m
+     *            method to check
+     * @param annotationClass
+     *            annotation to look for
+     * @return Depth of the annotation or -1 if the annotation is not on the method.
+     */
+    private static int getAnnotationDepth(final Method m, final Class<? extends Annotation> annotationClass) {
+        // if we have invalid data the result is -1
+        if (m == null || annotationClass == null) {
+            return -1;
+        }
+
+        if (m.isAnnotationPresent(annotationClass)) {
+            return 1;
+        }
+
+        // if we've already reached the Object class, return -1;
+        Class<?> c = m.getDeclaringClass();
+        if (c.getSuperclass() == null) {
+            return -1;
+        }
+
+        // check directly implemented interfaces for the method being checked
+        for (Class<?> i : c.getInterfaces()) {
+            try {
+                Method im = i.getMethod(m.getName(), m.getParameterTypes());
+                int d = getAnnotationDepth(im, annotationClass);
+                if (d > 0) {
+                    // since the annotation was on the interface, add 1
+                    return d + 1;
+                }
+            } catch (final SecurityException ex) {
+                continue;
+            } catch (final NoSuchMethodException ex) {
+                continue;
+            }
+        }
+
+        try {
+            int d = getAnnotationDepth(
+                    c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()),
+                    annotationClass);
+            if (d > 0) {
+                // since the annotation was on the superclass, add 1
+                return d + 1;
+            }
+            return -1;
+        } catch (final SecurityException ex) {
+            return -1;
+        } catch (final NoSuchMethodException ex) {
+            return -1;
+        }
+    }
+
+    /**
+     * Put a key/boolean pair in the JSONObject.
+     *
+     * @param key
+     *            A key string.
+     * @param value
+     *            A boolean which is the value.
+     * @return this.
+     * @throws JSONException
+     *            If the value is non-finite number.
+     * @throws NullPointerException
+     *            If the key is <code>null</code>.
+     */
+    public JSONObject put(String key, boolean value) throws JSONException {
+        return this.put(key, value ? Boolean.TRUE : Boolean.FALSE);
+    }
+
+    /**
+     * Put a key/value pair in the JSONObject, where the value will be a
+     * JSONArray which is produced from a Collection.
+     *
+     * @param key
+     *            A key string.
+     * @param value
+     *            A Collection value.
+     * @return this.
+     * @throws JSONException
+     *            If the value is non-finite number.
+     * @throws NullPointerException
+     *            If the key is <code>null</code>.
+     */
+    public JSONObject put(String key, Collection<?> value) throws JSONException {
+        return this.put(key, new JSONArray(value));
+    }
+
+    /**
+     * Put a key/double pair in the JSONObject.
+     *
+     * @param key
+     *            A key string.
+     * @param value
+     *            A double which is the value.
+     * @return this.
+     * @throws JSONException
+     *            If the value is non-finite number.
+     * @throws NullPointerException
+     *            If the key is <code>null</code>.
+     */
+    public JSONObject put(String key, double value) throws JSONException {
+        return this.put(key, Double.valueOf(value));
+    }
+
+    /**
+     * Put a key/float pair in the JSONObject.
+     *
+     * @param key
+     *            A key string.
+     * @param value
+     *            A float which is the value.
+     * @return this.
+     * @throws JSONException
+     *            If the value is non-finite number.
+     * @throws NullPointerException
+     *            If the key is <code>null</code>.
+     */
+    public JSONObject put(String key, float value) throws JSONException {
+        return this.put(key, Float.valueOf(value));
+    }
+
+    /**
+     * Put a key/int pair in the JSONObject.
+     *
+     * @param key
+     *            A key string.
+     * @param value
+     *            An int which is the value.
+     * @return this.
+     * @throws JSONException
+     *            If the value is non-finite number.
+     * @throws NullPointerException
+     *            If the key is <code>null</code>.
+     */
+    public JSONObject put(String key, int value) throws JSONException {
+        return this.put(key, Integer.valueOf(value));
+    }
+
+    /**
+     * Put a key/long pair in the JSONObject.
+     *
+     * @param key
+     *            A key string.
+     * @param value
+     *            A long which is the value.
+     * @return this.
+     * @throws JSONException
+     *            If the value is non-finite number.
+     * @throws NullPointerException
+     *            If the key is <code>null</code>.
+     */
+    public JSONObject put(String key, long value) throws JSONException {
+        return this.put(key, Long.valueOf(value));
+    }
+
+    /**
+     * Put a key/value pair in the JSONObject, where the value will be a
+     * JSONObject which is produced from a Map.
+     *
+     * @param key
+     *            A key string.
+     * @param value
+     *            A Map value.
+     * @return this.
+     * @throws JSONException
+     *            If the value is non-finite number.
+     * @throws NullPointerException
+     *            If the key is <code>null</code>.
+     */
+    public JSONObject put(String key, Map<?, ?> value) throws JSONException {
+        return this.put(key, new JSONObject(value));
+    }
+
+    /**
+     * Put a key/value pair in the JSONObject. If the value is <code>null</code>, then the
+     * key will be removed from the JSONObject if it is present.
+     *
+     * @param key
+     *            A key string.
+     * @param value
+     *            An object which is the value. It should be of one of these
+     *            types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
+     *            String, or the JSONObject.NULL object.
+     * @return this.
+     * @throws JSONException
+     *            If the value is non-finite number.
+     * @throws NullPointerException
+     *            If the key is <code>null</code>.
+     */
+    public JSONObject put(String key, Object value) throws JSONException {
+        if (key == null) {
+            throw new NullPointerException("Null key.");
+        }
+        if (value != null) {
+            testValidity(value);
+            this.map.put(key, value);
+        } else {
+            this.remove(key);
+        }
+        return this;
+    }
+
+    /**
+     * Put a key/value pair in the JSONObject, but only if the key and the value
+     * are both non-null, and only if there is not already a member with that
+     * name.
+     *
+     * @param key
+     *            key to insert into
+     * @param value
+     *            value to insert
+     * @return this.
+     * @throws JSONException
+     *             if the key is a duplicate
+     */
+    public JSONObject putOnce(String key, Object value) throws JSONException {
+        if (key != null && value != null) {
+            if (this.opt(key) != null) {
+                throw new JSONException("Duplicate key \"" + key + "\"");
+            }
+            return this.put(key, value);
+        }
+        return this;
+    }
+
+    /**
+     * Put a key/value pair in the JSONObject, but only if the key and the value
+     * are both non-null.
+     *
+     * @param key
+     *            A key string.
+     * @param value
+     *            An object which is the value. It should be of one of these
+     *            types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
+     *            String, or the JSONObject.NULL object.
+     * @return this.
+     * @throws JSONException
+     *             If the value is a non-finite number.
+     */
+    public JSONObject putOpt(String key, Object value) throws JSONException {
+        if (key != null && value != null) {
+            return this.put(key, value);
+        }
+        return this;
+    }
+
+    /**
+     * Creates a JSONPointer using an initialization string and tries to
+     * match it to an item within this JSONObject. For example, given a
+     * JSONObject initialized with this document:
+     * <pre>
+     * {
+     *     "a":{"b":"c"}
+     * }
+     * </pre>
+     * and this JSONPointer string:
+     * <pre>
+     * "/a/b"
+     * </pre>
+     * Then this method will return the String "c".
+     * A JSONPointerException may be thrown from code called by this method.
+     *
+     * @param jsonPointer string that can be used to create a JSONPointer
+     * @return the item matched by the JSONPointer, otherwise null
+     */
+    public Object query(String jsonPointer) {
+        return query(new JSONPointer(jsonPointer));
+    }
+    /**
+     * Uses a user initialized JSONPointer  and tries to
+     * match it to an item within this JSONObject. For example, given a
+     * JSONObject initialized with this document:
+     * <pre>
+     * {
+     *     "a":{"b":"c"}
+     * }
+     * </pre>
+     * and this JSONPointer:
+     * <pre>
+     * "/a/b"
+     * </pre>
+     * Then this method will return the String "c".
+     * A JSONPointerException may be thrown from code called by this method.
+     *
+     * @param jsonPointer string that can be used to create a JSONPointer
+     * @return the item matched by the JSONPointer, otherwise null
+     */
+    public Object query(JSONPointer jsonPointer) {
+        return jsonPointer.queryFrom(this);
+    }
+
+    /**
+     * Queries and returns a value from this object using {@code jsonPointer}, or
+     * returns null if the query fails due to a missing key.
+     *
+     * @param jsonPointer the string representation of the JSON pointer
+     * @return the queried value or {@code null}
+     * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
+     */
+    public Object optQuery(String jsonPointer) {
+    	return optQuery(new JSONPointer(jsonPointer));
+    }
+
+    /**
+     * Queries and returns a value from this object using {@code jsonPointer}, or
+     * returns null if the query fails due to a missing key.
+     *
+     * @param jsonPointer The JSON pointer
+     * @return the queried value or {@code null}
+     * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
+     */
+    public Object optQuery(JSONPointer jsonPointer) {
+        try {
+            return jsonPointer.queryFrom(this);
+        } catch (JSONPointerException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Produce a string in double quotes with backslash sequences in all the
+     * right places. A backslash will be inserted within &lt;/, producing
+     * &lt;\/, allowing JSON text to be delivered in HTML. In JSON text, a
+     * string cannot contain a control character or an unescaped quote or
+     * backslash.
+     *
+     * @param string
+     *            A String
+     * @return A String correctly formatted for insertion in a JSON text.
+     */
+    public static String quote(String string) {
+        StringWriter sw = new StringWriter();
+        synchronized (sw.getBuffer()) {
+            try {
+                return quote(string, sw).toString();
+            } catch (IOException ignored) {
+                // will never happen - we are writing to a string writer
+                return "";
+            }
+        }
+    }
+
+    public static Writer quote(String string, Writer w) throws IOException {
+        if (string == null || string.isEmpty()) {
+            w.write("\"\"");
+            return w;
+        }
+
+        char b;
+        char c = 0;
+        String hhhh;
+        int i;
+        int len = string.length();
+
+        w.write('"');
+        for (i = 0; i < len; i += 1) {
+            b = c;
+            c = string.charAt(i);
+            switch (c) {
+            case '\\':
+            case '"':
+                w.write('\\');
+                w.write(c);
+                break;
+            case '/':
+                if (b == '<') {
+                    w.write('\\');
+                }
+                w.write(c);
+                break;
+            case '\b':
+                w.write("\\b");
+                break;
+            case '\t':
+                w.write("\\t");
+                break;
+            case '\n':
+                w.write("\\n");
+                break;
+            case '\f':
+                w.write("\\f");
+                break;
+            case '\r':
+                w.write("\\r");
+                break;
+            default:
+                if (c < ' ' || (c >= '\u0080' && c < '\u00a0')
+                        || (c >= '\u2000' && c < '\u2100')) {
+                    w.write("\\u");
+                    hhhh = Integer.toHexString(c);
+                    w.write("0000", 0, 4 - hhhh.length());
+                    w.write(hhhh);
+                } else {
+                    w.write(c);
+                }
+            }
+        }
+        w.write('"');
+        return w;
+    }
+
+    /**
+     * Remove a name and its value, if present.
+     *
+     * @param key
+     *            The name to be removed.
+     * @return The value that was associated with the name, or null if there was
+     *         no value.
+     */
+    public Object remove(String key) {
+        return this.map.remove(key);
+    }
+
+    /**
+     * Determine if two JSONObjects are similar.
+     * They must contain the same set of names which must be associated with
+     * similar values.
+     *
+     * @param other The other JSONObject
+     * @return true if they are equal
+     */
+    public boolean similar(Object other) {
+        try {
+            if (!(other instanceof JSONObject)) {
+                return false;
+            }
+            if (!this.keySet().equals(((JSONObject)other).keySet())) {
+                return false;
+            }
+            for (final Entry<String,?> entry : this.entrySet()) {
+                String name = entry.getKey();
+                Object valueThis = entry.getValue();
+                Object valueOther = ((JSONObject)other).get(name);
+                if(valueThis == valueOther) {
+                	continue;
+                }
+                if(valueThis == null) {
+                	return false;
+                }
+                if (valueThis instanceof JSONObject) {
+                    if (!((JSONObject)valueThis).similar(valueOther)) {
+                        return false;
+                    }
+                } else if (valueThis instanceof JSONArray) {
+                    if (!((JSONArray)valueThis).similar(valueOther)) {
+                        return false;
+                    }
+                } else if (valueThis instanceof Number && valueOther instanceof Number) {
+                    return isNumberSimilar((Number)valueThis, (Number)valueOther);
+                } else if (!valueThis.equals(valueOther)) {
+                    return false;
+                }
+            }
+            return true;
+        } catch (Throwable exception) {
+            return false;
+        }
+    }
+
+    /**
+     * Compares two numbers to see if they are similar.
+     *
+     * If either of the numbers are Double or Float instances, then they are checked to have
+     * a finite value. If either value is not finite (NaN or &#177;infinity), then this
+     * function will always return false. If both numbers are finite, they are first checked
+     * to be the same type and implement {@link Comparable}. If they do, then the actual
+     * {@link Comparable#compareTo(Object)} is called. If they are not the same type, or don't
+     * implement Comparable, then they are converted to {@link BigDecimal}s. Finally the
+     * BigDecimal values are compared using {@link BigDecimal#compareTo(BigDecimal)}.
+     *
+     * @param l the Left value to compare. Can not be <code>null</code>.
+     * @param r the right value to compare. Can not be <code>null</code>.
+     * @return true if the numbers are similar, false otherwise.
+     */
+    static boolean isNumberSimilar(Number l, Number r) {
+        if (!numberIsFinite(l) || !numberIsFinite(r)) {
+            // non-finite numbers are never similar
+            return false;
+        }
+
+        // if the classes are the same and implement Comparable
+        // then use the built in compare first.
+        if(l.getClass().equals(r.getClass()) && l instanceof Comparable) {
+            @SuppressWarnings({ "rawtypes", "unchecked" })
+            int compareTo = ((Comparable)l).compareTo(r);
+            return compareTo==0;
+        }
+
+        // BigDecimal should be able to handle all of our number types that we support through
+        // documentation. Convert to BigDecimal first, then use the Compare method to
+        // decide equality.
+        final BigDecimal lBigDecimal = objectToBigDecimal(l, null);
+        final BigDecimal rBigDecimal = objectToBigDecimal(r, null);
+        if (lBigDecimal == null || rBigDecimal == null) {
+            return false;
+        }
+        return lBigDecimal.compareTo(rBigDecimal) == 0;
+    }
+
+    private static boolean numberIsFinite(Number n) {
+        if (n instanceof Double && (((Double) n).isInfinite() || ((Double) n).isNaN())) {
+            return false;
+        } else if (n instanceof Float && (((Float) n).isInfinite() || ((Float) n).isNaN())) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Tests if the value should be tried as a decimal. It makes no test if there are actual digits.
+     *
+     * @param val value to test
+     * @return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise.
+     */
+    protected static boolean isDecimalNotation(final String val) {
+        return val.indexOf('.') > -1 || val.indexOf('e') > -1
+                || val.indexOf('E') > -1 || "-0".equals(val);
+    }
+
+    /**
+     * Converts a string to a number using the narrowest possible type. Possible
+     * returns for this function are BigDecimal, Double, BigInteger, Long, and Integer.
+     * When a Double is returned, it should always be a valid Double and not NaN or +-infinity.
+     *
+     * @param val value to convert
+     * @return Number representation of the value.
+     * @throws NumberFormatException thrown if the value is not a valid number. A public
+     *      caller should catch this and wrap it in a {@link JSONException} if applicable.
+     */
+    protected static Number stringToNumber(final String val) throws NumberFormatException {
+        char initial = val.charAt(0);
+        if ((initial >= '0' && initial <= '9') || initial == '-') {
+            // decimal representation
+            if (isDecimalNotation(val)) {
+                // Use a BigDecimal all the time so we keep the original
+                // representation. BigDecimal doesn't support -0.0, ensure we
+                // keep that by forcing a decimal.
+                try {
+                    BigDecimal bd = new BigDecimal(val);
+                    if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
+                        return Double.valueOf(-0.0);
+                    }
+                    return bd;
+                } catch (NumberFormatException retryAsDouble) {
+                    // this is to support "Hex Floats" like this: 0x1.0P-1074
+                    try {
+                        Double d = Double.valueOf(val);
+                        if(d.isNaN() || d.isInfinite()) {
+                            throw new NumberFormatException("val ["+val+"] is not a valid number.");
+                        }
+                        return d;
+                    } catch (NumberFormatException ignore) {
+                        throw new NumberFormatException("val ["+val+"] is not a valid number.");
+                    }
+                }
+            }
+            // block items like 00 01 etc. Java number parsers treat these as Octal.
+            if(initial == '0' && val.length() > 1) {
+                char at1 = val.charAt(1);
+                if(at1 >= '0' && at1 <= '9') {
+                    throw new NumberFormatException("val ["+val+"] is not a valid number.");
+                }
+            } else if (initial == '-' && val.length() > 2) {
+                char at1 = val.charAt(1);
+                char at2 = val.charAt(2);
+                if(at1 == '0' && at2 >= '0' && at2 <= '9') {
+                    throw new NumberFormatException("val ["+val+"] is not a valid number.");
+                }
+            }
+            // integer representation.
+            // This will narrow any values to the smallest reasonable Object representation
+            // (Integer, Long, or BigInteger)
+
+            // BigInteger down conversion: We use a similar bitLenth compare as
+            // BigInteger#intValueExact uses. Increases GC, but objects hold
+            // only what they need. i.e. Less runtime overhead if the value is
+            // long lived.
+            BigInteger bi = new BigInteger(val);
+            if(bi.bitLength() <= 31){
+                return Integer.valueOf(bi.intValue());
+            }
+            if(bi.bitLength() <= 63){
+                return Long.valueOf(bi.longValue());
+            }
+            return bi;
+        }
+        throw new NumberFormatException("val ["+val+"] is not a valid number.");
+    }
+
+    /**
+     * Try to convert a string into a number, boolean, or null. If the string
+     * can't be converted, return the string.
+     *
+     * @param string
+     *            A String. can not be null.
+     * @return A simple JSON value.
+     * @throws NullPointerException
+     *             Thrown if the string is null.
+     */
+    // Changes to this method must be copied to the corresponding method in
+    // the XML class to keep full support for Android
+    public static Object stringToValue(String string) {
+        if ("".equals(string)) {
+            return string;
+        }
+
+        // check JSON key words true/false/null
+        if ("true".equalsIgnoreCase(string)) {
+            return Boolean.TRUE;
+        }
+        if ("false".equalsIgnoreCase(string)) {
+            return Boolean.FALSE;
+        }
+        if ("null".equalsIgnoreCase(string)) {
+            return JSONObject.NULL;
+        }
+
+        /*
+         * If it might be a number, try converting it. If a number cannot be
+         * produced, then the value will just be a string.
+         */
+
+        char initial = string.charAt(0);
+        if ((initial >= '0' && initial <= '9') || initial == '-') {
+            try {
+                return stringToNumber(string);
+            } catch (Exception ignore) {
+            }
+        }
+        return string;
+    }
+
+    /**
+     * Throw an exception if the object is a NaN or infinite number.
+     *
+     * @param o
+     *            The object to test.
+     * @throws JSONException
+     *             If o is a non-finite number.
+     */
+    public static void testValidity(Object o) throws JSONException {
+        if (o instanceof Number && !numberIsFinite((Number) o)) {
+            throw new JSONException("JSON does not allow non-finite numbers.");
+        }
+    }
+
+    /**
+     * Produce a JSONArray containing the values of the members of this
+     * JSONObject.
+     *
+     * @param names
+     *            A JSONArray containing a list of key strings. This determines
+     *            the sequence of the values in the result.
+     * @return A JSONArray of values.
+     * @throws JSONException
+     *             If any of the values are non-finite numbers.
+     */
+    public JSONArray toJSONArray(JSONArray names) throws JSONException {
+        if (names == null || names.isEmpty()) {
+            return null;
+        }
+        JSONArray ja = new JSONArray();
+        for (int i = 0; i < names.length(); i += 1) {
+            ja.put(this.opt(names.getString(i)));
+        }
+        return ja;
+    }
+
+    /**
+     * Make a JSON text of this JSONObject. For compactness, no whitespace is
+     * added. If this would not result in a syntactically correct JSON text,
+     * then null will be returned instead.
+     * <p><b>
+     * Warning: This method assumes that the data structure is acyclical.
+     * </b>
+     *
+     * @return a printable, displayable, portable, transmittable representation
+     *         of the object, beginning with <code>{</code>&nbsp;<small>(left
+     *         brace)</small> and ending with <code>}</code>&nbsp;<small>(right
+     *         brace)</small>.
+     */
+    @Override
+    public String toString() {
+        try {
+            return this.toString(0);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * Make a pretty-printed JSON text of this JSONObject.
+     *
+     * <p>If <pre>{@code indentFactor > 0}</pre> and the {@link JSONObject}
+     * has only one key, then the object will be output on a single line:
+     * <pre>{@code {"key": 1}}</pre>
+     *
+     * <p>If an object has 2 or more keys, then it will be output across
+     * multiple lines: <pre>{@code {
+     *  "key1": 1,
+     *  "key2": "value 2",
+     *  "key3": 3
+     * }}</pre>
+     * <p><b>
+     * Warning: This method assumes that the data structure is acyclical.
+     * </b>
+     *
+     * @param indentFactor
+     *            The number of spaces to add to each level of indentation.
+     * @return a printable, displayable, portable, transmittable representation
+     *         of the object, beginning with <code>{</code>&nbsp;<small>(left
+     *         brace)</small> and ending with <code>}</code>&nbsp;<small>(right
+     *         brace)</small>.
+     * @throws JSONException
+     *             If the object contains an invalid number.
+     */
+    public String toString(int indentFactor) throws JSONException {
+        StringWriter w = new StringWriter();
+        synchronized (w.getBuffer()) {
+            return this.write(w, indentFactor, 0).toString();
+        }
+    }
+
+    /**
+     * Make a JSON text of an Object value. If the object has an
+     * value.toJSONString() method, then that method will be used to produce the
+     * JSON text. The method is required to produce a strictly conforming text.
+     * If the object does not contain a toJSONString method (which is the most
+     * common case), then a text will be produced by other means. If the value
+     * is an array or Collection, then a JSONArray will be made from it and its
+     * toJSONString method will be called. If the value is a MAP, then a
+     * JSONObject will be made from it and its toJSONString method will be
+     * called. Otherwise, the value's toString method will be called, and the
+     * result will be quoted.
+     *
+     * <p>
+     * Warning: This method assumes that the data structure is acyclical.
+     *
+     * @param value
+     *            The value to be serialized.
+     * @return a printable, displayable, transmittable representation of the
+     *         object, beginning with <code>{</code>&nbsp;<small>(left
+     *         brace)</small> and ending with <code>}</code>&nbsp;<small>(right
+     *         brace)</small>.
+     * @throws JSONException
+     *             If the value is or contains an invalid number.
+     */
+    public static String valueToString(Object value) throws JSONException {
+    	// moves the implementation to JSONWriter as:
+    	// 1. It makes more sense to be part of the writer class
+    	// 2. For Android support this method is not available. By implementing it in the Writer
+    	//    Android users can use the writer with the built in Android JSONObject implementation.
+        return JSONWriter.valueToString(value);
+    }
+
+    /**
+     * Wrap an object, if necessary. If the object is <code>null</code>, return the NULL
+     * object. If it is an array or collection, wrap it in a JSONArray. If it is
+     * a map, wrap it in a JSONObject. If it is a standard property (Double,
+     * String, et al) then it is already wrapped. Otherwise, if it comes from
+     * one of the java packages, turn it into a string. And if it doesn't, try
+     * to wrap it in a JSONObject. If the wrapping fails, then null is returned.
+     *
+     * @param object
+     *            The object to wrap
+     * @return The wrapped value
+     */
+    public static Object wrap(Object object) {
+        try {
+            if (NULL.equals(object)) {
+                return NULL;
+            }
+            if (object instanceof JSONObject || object instanceof JSONArray
+                    || NULL.equals(object) || object instanceof JSONString
+                    || object instanceof Byte || object instanceof Character
+                    || object instanceof Short || object instanceof Integer
+                    || object instanceof Long || object instanceof Boolean
+                    || object instanceof Float || object instanceof Double
+                    || object instanceof String || object instanceof BigInteger
+                    || object instanceof BigDecimal || object instanceof Enum) {
+                return object;
+            }
+
+            if (object instanceof Collection) {
+                Collection<?> coll = (Collection<?>) object;
+                return new JSONArray(coll);
+            }
+            if (object.getClass().isArray()) {
+                return new JSONArray(object);
+            }
+            if (object instanceof Map) {
+                Map<?, ?> map = (Map<?, ?>) object;
+                return new JSONObject(map);
+            }
+            Package objectPackage = object.getClass().getPackage();
+            String objectPackageName = objectPackage != null ? objectPackage
+                    .getName() : "";
+            if (objectPackageName.startsWith("java.")
+                    || objectPackageName.startsWith("javax.")
+                    || object.getClass().getClassLoader() == null) {
+                return object.toString();
+            }
+            return new JSONObject(object);
+        } catch (Exception exception) {
+            return null;
+        }
+    }
+
+    /**
+     * Write the contents of the JSONObject as JSON text to a writer. For
+     * compactness, no whitespace is added.
+     * <p><b>
+     * Warning: This method assumes that the data structure is acyclical.
+     * </b>
+     * @param writer the writer object
+     * @return The writer.
+     * @throws JSONException if a called function has an error
+     */
+    public Writer write(Writer writer) throws JSONException {
+        return this.write(writer, 0, 0);
+    }
+
+    static final Writer writeValue(Writer writer, Object value,
+            int indentFactor, int indent) throws JSONException, IOException {
+        if (value == null || value.equals(null)) {
+            writer.write("null");
+        } else if (value instanceof JSONString) {
+            Object o;
+            try {
+                o = ((JSONString) value).toJSONString();
+            } catch (Exception e) {
+                throw new JSONException(e);
+            }
+            writer.write(o != null ? o.toString() : quote(value.toString()));
+        } else if (value instanceof Number) {
+            // not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary
+            final String numberAsString = numberToString((Number) value);
+            if(NUMBER_PATTERN.matcher(numberAsString).matches()) {
+                writer.write(numberAsString);
+            } else {
+                // The Number value is not a valid JSON number.
+                // Instead we will quote it as a string
+                quote(numberAsString, writer);
+            }
+        } else if (value instanceof Boolean) {
+            writer.write(value.toString());
+        } else if (value instanceof Enum<?>) {
+            writer.write(quote(((Enum<?>)value).name()));
+        } else if (value instanceof JSONObject) {
+            ((JSONObject) value).write(writer, indentFactor, indent);
+        } else if (value instanceof JSONArray) {
+            ((JSONArray) value).write(writer, indentFactor, indent);
+        } else if (value instanceof Map) {
+            Map<?, ?> map = (Map<?, ?>) value;
+            new JSONObject(map).write(writer, indentFactor, indent);
+        } else if (value instanceof Collection) {
+            Collection<?> coll = (Collection<?>) value;
+            new JSONArray(coll).write(writer, indentFactor, indent);
+        } else if (value.getClass().isArray()) {
+            new JSONArray(value).write(writer, indentFactor, indent);
+        } else {
+            quote(value.toString(), writer);
+        }
+        return writer;
+    }
+
+    static final void indent(Writer writer, int indent) throws IOException {
+        for (int i = 0; i < indent; i += 1) {
+            writer.write(' ');
+        }
+    }
+
+    /**
+     * Write the contents of the JSONObject as JSON text to a writer.
+     *
+     * <p>If <pre>{@code indentFactor > 0}</pre> and the {@link JSONObject}
+     * has only one key, then the object will be output on a single line:
+     * <pre>{@code {"key": 1}}</pre>
+     *
+     * <p>If an object has 2 or more keys, then it will be output across
+     * multiple lines: <pre>{@code {
+     *  "key1": 1,
+     *  "key2": "value 2",
+     *  "key3": 3
+     * }}</pre>
+     * <p><b>
+     * Warning: This method assumes that the data structure is acyclical.
+     * </b>
+     *
+     * @param writer
+     *            Writes the serialized JSON
+     * @param indentFactor
+     *            The number of spaces to add to each level of indentation.
+     * @param indent
+     *            The indentation of the top level.
+     * @return The writer.
+     * @throws JSONException if a called function has an error or a write error
+     * occurs
+     */
+    public Writer write(Writer writer, int indentFactor, int indent)
+            throws JSONException {
+        try {
+            boolean needsComma = false;
+            final int length = this.length();
+            writer.write('{');
+
+            if (length == 1) {
+            	final Entry<String,?> entry = this.entrySet().iterator().next();
+                final String key = entry.getKey();
+                writer.write(quote(key));
+                writer.write(':');
+                if (indentFactor > 0) {
+                    writer.write(' ');
+                }
+                try{
+                    writeValue(writer, entry.getValue(), indentFactor, indent);
+                } catch (Exception e) {
+                    throw new JSONException("Unable to write JSONObject value for key: " + key, e);
+                }
+            } else if (length != 0) {
+                final int newIndent = indent + indentFactor;
+                for (final Entry<String,?> entry : this.entrySet()) {
+                    if (needsComma) {
+                        writer.write(',');
+                    }
+                    if (indentFactor > 0) {
+                        writer.write('\n');
+                    }
+                    indent(writer, newIndent);
+                    final String key = entry.getKey();
+                    writer.write(quote(key));
+                    writer.write(':');
+                    if (indentFactor > 0) {
+                        writer.write(' ');
+                    }
+                    try {
+                        writeValue(writer, entry.getValue(), indentFactor, newIndent);
+                    } catch (Exception e) {
+                        throw new JSONException("Unable to write JSONObject value for key: " + key, e);
+                    }
+                    needsComma = true;
+                }
+                if (indentFactor > 0) {
+                    writer.write('\n');
+                }
+                indent(writer, indent);
+            }
+            writer.write('}');
+            return writer;
+        } catch (IOException exception) {
+            throw new JSONException(exception);
+        }
+    }
+
+    /**
+     * Returns a java.util.Map containing all of the entries in this object.
+     * If an entry in the object is a JSONArray or JSONObject it will also
+     * be converted.
+     * <p>
+     * Warning: This method assumes that the data structure is acyclical.
+     *
+     * @return a java.util.Map containing the entries of this object
+     */
+    public Map<String, Object> toMap() {
+        Map<String, Object> results = new HashMap<String, Object>();
+        for (Entry<String, Object> entry : this.entrySet()) {
+            Object value;
+            if (entry.getValue() == null || NULL.equals(entry.getValue())) {
+                value = null;
+            } else if (entry.getValue() instanceof JSONObject) {
+                value = ((JSONObject) entry.getValue()).toMap();
+            } else if (entry.getValue() instanceof JSONArray) {
+                value = ((JSONArray) entry.getValue()).toList();
+            } else {
+                value = entry.getValue();
+            }
+            results.put(entry.getKey(), value);
+        }
+        return results;
+    }
+
+    /**
+     * Create a new JSONException in a common format for incorrect conversions.
+     * @param key name of the key
+     * @param valueType the type of value being coerced to
+     * @param cause optional cause of the coercion failure
+     * @return JSONException that can be thrown.
+     */
+    private static JSONException wrongValueFormatException(
+            String key,
+            String valueType,
+            Throwable cause) {
+        return new JSONException(
+                "JSONObject[" + quote(key) + "] is not a " + valueType + "."
+                , cause);
+    }
+
+    /**
+     * Create a new JSONException in a common format for incorrect conversions.
+     * @param key name of the key
+     * @param valueType the type of value being coerced to
+     * @param cause optional cause of the coercion failure
+     * @return JSONException that can be thrown.
+     */
+    private static JSONException wrongValueFormatException(
+            String key,
+            String valueType,
+            Object value,
+            Throwable cause) {
+        return new JSONException(
+                "JSONObject[" + quote(key) + "] is not a " + valueType + " (" + value + ")."
+                , cause);
+    }
+}

+ 295 - 0
src/main/java/com/usky/xml/JSONPointer.java

@@ -0,0 +1,295 @@
+package com.usky.xml;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static java.lang.String.format;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/**
+ * A JSON Pointer is a simple query language defined for JSON documents by
+ * <a href="https://tools.ietf.org/html/rfc6901">RFC 6901</a>.
+ * 
+ * In a nutshell, JSONPointer allows the user to navigate into a JSON document
+ * using strings, and retrieve targeted objects, like a simple form of XPATH.
+ * Path segments are separated by the '/' char, which signifies the root of
+ * the document when it appears as the first char of the string. Array 
+ * elements are navigated using ordinals, counting from 0. JSONPointer strings
+ * may be extended to any arbitrary number of segments. If the navigation
+ * is successful, the matched item is returned. A matched item may be a
+ * JSONObject, a JSONArray, or a JSON value. If the JSONPointer string building 
+ * fails, an appropriate exception is thrown. If the navigation fails to find
+ * a match, a JSONPointerException is thrown. 
+ * 
+ * @author JSON.org
+ * @version 2016-05-14
+ */
+public class JSONPointer {
+
+    // used for URL encoding and decoding
+    private static final String ENCODING = "utf-8";
+
+    /**
+     * This class allows the user to build a JSONPointer in steps, using
+     * exactly one segment in each step.
+     */
+    public static class Builder {
+
+        // Segments for the eventual JSONPointer string
+        private final List<String> refTokens = new ArrayList<String>();
+
+        /**
+         * Creates a {@code JSONPointer} instance using the tokens previously set using the
+         * {@link #append(String)} method calls.
+         * @return a JSONPointer object
+         */
+        public JSONPointer build() {
+            return new JSONPointer(this.refTokens);
+        }
+
+        /**
+         * Adds an arbitrary token to the list of reference tokens. It can be any non-null value.
+         * 
+         * Unlike in the case of JSON string or URI fragment representation of JSON pointers, the
+         * argument of this method MUST NOT be escaped. If you want to query the property called
+         * {@code "a~b"} then you should simply pass the {@code "a~b"} string as-is, there is no
+         * need to escape it as {@code "a~0b"}.
+         * 
+         * @param token the new token to be appended to the list
+         * @return {@code this}
+         * @throws NullPointerException if {@code token} is null
+         */
+        public Builder append(String token) {
+            if (token == null) {
+                throw new NullPointerException("token cannot be null");
+            }
+            this.refTokens.add(token);
+            return this;
+        }
+
+        /**
+         * Adds an integer to the reference token list. Although not necessarily, mostly this token will
+         * denote an array index. 
+         * 
+         * @param arrayIndex the array index to be added to the token list
+         * @return {@code this}
+         */
+        public Builder append(int arrayIndex) {
+            this.refTokens.add(String.valueOf(arrayIndex));
+            return this;
+        }
+    }
+
+    /**
+     * Static factory method for {@link Builder}. Example usage:
+     * 
+     * <pre><code>
+     * JSONPointer pointer = JSONPointer.builder()
+     *       .append("obj")
+     *       .append("other~key").append("another/key")
+     *       .append("\"")
+     *       .append(0)
+     *       .build();
+     * </code></pre>
+     * 
+     *  @return a builder instance which can be used to construct a {@code JSONPointer} instance by chained
+     *  {@link Builder#append(String)} calls.
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    // Segments for the JSONPointer string
+    private final List<String> refTokens;
+
+    /**
+     * Pre-parses and initializes a new {@code JSONPointer} instance. If you want to
+     * evaluate the same JSON Pointer on different JSON documents then it is recommended
+     * to keep the {@code JSONPointer} instances due to performance considerations.
+     * 
+     * @param pointer the JSON String or URI Fragment representation of the JSON pointer.
+     * @throws IllegalArgumentException if {@code pointer} is not a valid JSON pointer
+     */
+    public JSONPointer(final String pointer) {
+        if (pointer == null) {
+            throw new NullPointerException("pointer cannot be null");
+        }
+        if (pointer.isEmpty() || pointer.equals("#")) {
+            this.refTokens = Collections.emptyList();
+            return;
+        }
+        String refs;
+        if (pointer.startsWith("#/")) {
+            refs = pointer.substring(2);
+            try {
+                refs = URLDecoder.decode(refs, ENCODING);
+            } catch (UnsupportedEncodingException e) {
+                throw new RuntimeException(e);
+            }
+        } else if (pointer.startsWith("/")) {
+            refs = pointer.substring(1);
+        } else {
+            throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'");
+        }
+        this.refTokens = new ArrayList<String>();
+        int slashIdx = -1;
+        int prevSlashIdx = 0;
+        do {
+            prevSlashIdx = slashIdx + 1;
+            slashIdx = refs.indexOf('/', prevSlashIdx);
+            if(prevSlashIdx == slashIdx || prevSlashIdx == refs.length()) {
+                // found 2 slashes in a row ( obj//next )
+                // or single slash at the end of a string ( obj/test/ )
+                this.refTokens.add("");
+            } else if (slashIdx >= 0) {
+                final String token = refs.substring(prevSlashIdx, slashIdx);
+                this.refTokens.add(unescape(token));
+            } else {
+                // last item after separator, or no separator at all.
+                final String token = refs.substring(prevSlashIdx);
+                this.refTokens.add(unescape(token));
+            }
+        } while (slashIdx >= 0);
+        // using split does not take into account consecutive separators or "ending nulls"
+        //for (String token : refs.split("/")) {
+        //    this.refTokens.add(unescape(token));
+        //}
+    }
+
+    public JSONPointer(List<String> refTokens) {
+        this.refTokens = new ArrayList<String>(refTokens);
+    }
+
+    /**
+     * @see https://tools.ietf.org/html/rfc6901#section-3
+     */
+    private static String unescape(String token) {
+        return token.replace("~1", "/").replace("~0", "~");
+    }
+
+    /**
+     * Evaluates this JSON Pointer on the given {@code document}. The {@code document}
+     * is usually a {@link JSONObject} or a {@link JSONArray} instance, but the empty
+     * JSON Pointer ({@code ""}) can be evaluated on any JSON values and in such case the
+     * returned value will be {@code document} itself. 
+     * 
+     * @param document the JSON document which should be the subject of querying.
+     * @return the result of the evaluation
+     * @throws JSONPointerException if an error occurs during evaluation
+     */
+    public Object queryFrom(Object document) throws JSONPointerException {
+        if (this.refTokens.isEmpty()) {
+            return document;
+        }
+        Object current = document;
+        for (String token : this.refTokens) {
+            if (current instanceof JSONObject) {
+                current = ((JSONObject) current).opt(unescape(token));
+            } else if (current instanceof JSONArray) {
+                current = readByIndexToken(current, token);
+            } else {
+                throw new JSONPointerException(format(
+                        "value [%s] is not an array or object therefore its key %s cannot be resolved", current,
+                        token));
+            }
+        }
+        return current;
+    }
+
+    /**
+     * Matches a JSONArray element by ordinal position
+     * @param current the JSONArray to be evaluated
+     * @param indexToken the array index in string form
+     * @return the matched object. If no matching item is found a
+     * @throws JSONPointerException is thrown if the index is out of bounds
+     */
+    private static Object readByIndexToken(Object current, String indexToken) throws JSONPointerException {
+        try {
+            int index = Integer.parseInt(indexToken);
+            JSONArray currentArr = (JSONArray) current;
+            if (index >= currentArr.length()) {
+                throw new JSONPointerException(format("index %s is out of bounds - the array has %d elements", indexToken,
+                        Integer.valueOf(currentArr.length())));
+            }
+            try {
+				return currentArr.get(index);
+			} catch (JSONException e) {
+				throw new JSONPointerException("Error reading value at index position " + index, e);
+			}
+        } catch (NumberFormatException e) {
+            throw new JSONPointerException(format("%s is not an array index", indexToken), e);
+        }
+    }
+
+    /**
+     * Returns a string representing the JSONPointer path value using string
+     * representation
+     */
+    @Override
+    public String toString() {
+        StringBuilder rval = new StringBuilder("");
+        for (String token: this.refTokens) {
+            rval.append('/').append(escape(token));
+        }
+        return rval.toString();
+    }
+
+    /**
+     * Escapes path segment values to an unambiguous form.
+     * The escape char to be inserted is '~'. The chars to be escaped 
+     * are ~, which maps to ~0, and /, which maps to ~1.
+     * @param token the JSONPointer segment value to be escaped
+     * @return the escaped value for the token
+     * 
+     * @see https://tools.ietf.org/html/rfc6901#section-3
+     */
+    private static String escape(String token) {
+        return token.replace("~", "~0")
+                .replace("/", "~1");
+    }
+
+    /**
+     * Returns a string representing the JSONPointer path value using URI
+     * fragment identifier representation
+     * @return a uri fragment string
+     */
+    public String toURIFragment() {
+        try {
+            StringBuilder rval = new StringBuilder("#");
+            for (String token : this.refTokens) {
+                rval.append('/').append(URLEncoder.encode(token, ENCODING));
+            }
+            return rval.toString();
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+}

+ 45 - 0
src/main/java/com/usky/xml/JSONPointerException.java

@@ -0,0 +1,45 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/**
+ * The JSONPointerException is thrown by {@link JSONPointer} if an error occurs
+ * during evaluating a pointer.
+ * 
+ * @author JSON.org
+ * @version 2016-05-13
+ */
+public class JSONPointerException extends JSONException {
+    private static final long serialVersionUID = 8872944667561856751L;
+
+    public JSONPointerException(String message) {
+        super(message);
+    }
+
+    public JSONPointerException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}

+ 43 - 0
src/main/java/com/usky/xml/JSONPropertyIgnore.java

@@ -0,0 +1,43 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2018 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Documented
+@Retention(RUNTIME)
+@Target({METHOD})
+/**
+ * Use this annotation on a getter method to override the Bean name
+ * parser for Bean -&gt; JSONObject mapping. If this annotation is
+ * present at any level in the class hierarchy, then the method will
+ * not be serialized from the bean into the JSONObject.
+ */
+public @interface JSONPropertyIgnore { }

+ 47 - 0
src/main/java/com/usky/xml/JSONPropertyName.java

@@ -0,0 +1,47 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2018 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Documented
+@Retention(RUNTIME)
+@Target({METHOD})
+/**
+ * Use this annotation on a getter method to override the Bean name
+ * parser for Bean -&gt; JSONObject mapping. A value set to empty string <code>""</code>
+ * will have the Bean parser fall back to the default field name processing.
+ */
+public @interface JSONPropertyName {
+    /**
+     * @return The name of the property as to be used in the JSON Object.
+     */
+    String value();
+}

+ 43 - 0
src/main/java/com/usky/xml/JSONString.java

@@ -0,0 +1,43 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+/**
+ * The <code>JSONString</code> interface allows a <code>toJSONString()</code>
+ * method so that a class can change the behavior of
+ * <code>JSONObject.toString()</code>, <code>JSONArray.toString()</code>,
+ * and <code>JSONWriter.value(</code>Object<code>)</code>. The
+ * <code>toJSONString</code> method will be used instead of the default behavior
+ * of using the Object's <code>toString()</code> method and quoting the result.
+ */
+public interface JSONString {
+    /**
+     * The <code>toJSONString</code> method allows a class to produce its own JSON
+     * serialization.
+     *
+     * @return A strictly syntactically correct JSON text.
+     */
+    public String toJSONString();
+}

+ 79 - 0
src/main/java/com/usky/xml/JSONStringer.java

@@ -0,0 +1,79 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2006 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import java.io.StringWriter;
+
+/**
+ * JSONStringer provides a quick and convenient way of producing JSON text.
+ * The texts produced strictly conform to JSON syntax rules. No whitespace is
+ * added, so the results are ready for transmission or storage. Each instance of
+ * JSONStringer can produce one JSON text.
+ * <p>
+ * A JSONStringer instance provides a <code>value</code> method for appending
+ * values to the
+ * text, and a <code>key</code>
+ * method for adding keys before values in objects. There are <code>array</code>
+ * and <code>endArray</code> methods that make and bound array values, and
+ * <code>object</code> and <code>endObject</code> methods which make and bound
+ * object values. All of these methods return the JSONWriter instance,
+ * permitting cascade style. For example, <pre>
+ * myString = new JSONStringer()
+ *     .object()
+ *         .key("JSON")
+ *         .value("Hello, World!")
+ *     .endObject()
+ *     .toString();</pre> which produces the string <pre>
+ * {"JSON":"Hello, World!"}</pre>
+ * <p>
+ * The first method called must be <code>array</code> or <code>object</code>.
+ * There are no methods for adding commas or colons. JSONStringer adds them for
+ * you. Objects and arrays can be nested up to 200 levels deep.
+ * <p>
+ * This can sometimes be easier than using a JSONObject to build a string.
+ * @author JSON.org
+ * @version 2015-12-09
+ */
+public class JSONStringer extends JSONWriter {
+    /**
+     * Make a fresh JSONStringer. It can be used to build one JSON text.
+     */
+    public JSONStringer() {
+        super(new StringWriter());
+    }
+
+    /**
+     * Return the JSON text. This method is used to obtain the product of the
+     * JSONStringer instance. It will return <code>null</code> if there was a
+     * problem in the construction of the JSON text (such as the calls to
+     * <code>array</code> were not properly balanced with calls to
+     * <code>endArray</code>).
+     * @return The JSON text.
+     */
+    @Override
+    public String toString() {
+        return this.mode == 'd' ? this.writer.toString() : null;
+    }
+}

+ 526 - 0
src/main/java/com/usky/xml/JSONTokener.java

@@ -0,0 +1,526 @@
+package com.usky.xml;
+
+import java.io.*;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+/**
+ * A JSONTokener takes a source string and extracts characters and tokens from
+ * it. It is used by the JSONObject and JSONArray constructors to parse
+ * JSON source strings.
+ * @author JSON.org
+ * @version 2014-05-03
+ */
+public class JSONTokener {
+    /** current read character position on the current line. */
+    private long character;
+    /** flag to indicate if the end of the input has been found. */
+    private boolean eof;
+    /** current read index of the input. */
+    private long index;
+    /** current line of the input. */
+    private long line;
+    /** previous character read from the input. */
+    private char previous;
+    /** Reader for the input. */
+    private final Reader reader;
+    /** flag to indicate that a previous character was requested. */
+    private boolean usePrevious;
+    /** the number of characters read in the previous line. */
+    private long characterPreviousLine;
+
+
+    /**
+     * Construct a JSONTokener from a Reader. The caller must close the Reader.
+     *
+     * @param reader     A reader.
+     */
+    public JSONTokener(Reader reader) {
+        this.reader = reader.markSupported()
+                ? reader
+                        : new BufferedReader(reader);
+        this.eof = false;
+        this.usePrevious = false;
+        this.previous = 0;
+        this.index = 0;
+        this.character = 1;
+        this.characterPreviousLine = 0;
+        this.line = 1;
+    }
+
+
+    /**
+     * Construct a JSONTokener from an InputStream. The caller must close the input stream.
+     * @param inputStream The source.
+     */
+    public JSONTokener(InputStream inputStream) {
+        this(new InputStreamReader(inputStream));
+    }
+
+
+    /**
+     * Construct a JSONTokener from a string.
+     *
+     * @param s     A source string.
+     */
+    public JSONTokener(String s) {
+        this(new StringReader(s));
+    }
+
+
+    /**
+     * Back up one character. This provides a sort of lookahead capability,
+     * so that you can test for a digit or letter before attempting to parse
+     * the next number or identifier.
+     * @throws JSONException Thrown if trying to step back more than 1 step
+     *  or if already at the start of the string
+     */
+    public void back() throws JSONException {
+        if (this.usePrevious || this.index <= 0) {
+            throw new JSONException("Stepping back two steps is not supported");
+        }
+        this.decrementIndexes();
+        this.usePrevious = true;
+        this.eof = false;
+    }
+
+    /**
+     * Decrements the indexes for the {@link #back()} method based on the previous character read.
+     */
+    private void decrementIndexes() {
+        this.index--;
+        if(this.previous=='\r' || this.previous == '\n') {
+            this.line--;
+            this.character=this.characterPreviousLine ;
+        } else if(this.character > 0){
+            this.character--;
+        }
+    }
+
+    /**
+     * Get the hex value of a character (base16).
+     * @param c A character between '0' and '9' or between 'A' and 'F' or
+     * between 'a' and 'f'.
+     * @return  An int between 0 and 15, or -1 if c was not a hex digit.
+     */
+    public static int dehexchar(char c) {
+        if (c >= '0' && c <= '9') {
+            return c - '0';
+        }
+        if (c >= 'A' && c <= 'F') {
+            return c - ('A' - 10);
+        }
+        if (c >= 'a' && c <= 'f') {
+            return c - ('a' - 10);
+        }
+        return -1;
+    }
+
+    /**
+     * Checks if the end of the input has been reached.
+     *  
+     * @return true if at the end of the file and we didn't step back
+     */
+    public boolean end() {
+        return this.eof && !this.usePrevious;
+    }
+
+
+    /**
+     * Determine if the source string still contains characters that next()
+     * can consume.
+     * @return true if not yet at the end of the source.
+     * @throws JSONException thrown if there is an error stepping forward
+     *  or backward while checking for more data.
+     */
+    public boolean more() throws JSONException {
+        if(this.usePrevious) {
+            return true;
+        }
+        try {
+            this.reader.mark(1);
+        } catch (IOException e) {
+            throw new JSONException("Unable to preserve stream position", e);
+        }
+        try {
+            // -1 is EOF, but next() can not consume the null character '\0'
+            if(this.reader.read() <= 0) {
+                this.eof = true;
+                return false;
+            }
+            this.reader.reset();
+        } catch (IOException e) {
+            throw new JSONException("Unable to read the next character from the stream", e);
+        }
+        return true;
+    }
+
+
+    /**
+     * Get the next character in the source string.
+     *
+     * @return The next character, or 0 if past the end of the source string.
+     * @throws JSONException Thrown if there is an error reading the source string.
+     */
+    public char next() throws JSONException {
+        int c;
+        if (this.usePrevious) {
+            this.usePrevious = false;
+            c = this.previous;
+        } else {
+            try {
+                c = this.reader.read();
+            } catch (IOException exception) {
+                throw new JSONException(exception);
+            }
+        }
+        if (c <= 0) { // End of stream
+            this.eof = true;
+            return 0;
+        }
+        this.incrementIndexes(c);
+        this.previous = (char) c;
+        return this.previous;
+    }
+
+    /**
+     * Increments the internal indexes according to the previous character
+     * read and the character passed as the current character.
+     * @param c the current character read.
+     */
+    private void incrementIndexes(int c) {
+        if(c > 0) {
+            this.index++;
+            if(c=='\r') {
+                this.line++;
+                this.characterPreviousLine = this.character;
+                this.character=0;
+            }else if (c=='\n') {
+                if(this.previous != '\r') {
+                    this.line++;
+                    this.characterPreviousLine = this.character;
+                }
+                this.character=0;
+            } else {
+                this.character++;
+            }
+        }
+    }
+
+    /**
+     * Consume the next character, and check that it matches a specified
+     * character.
+     * @param c The character to match.
+     * @return The character.
+     * @throws JSONException if the character does not match.
+     */
+    public char next(char c) throws JSONException {
+        char n = this.next();
+        if (n != c) {
+            if(n > 0) {
+                throw this.syntaxError("Expected '" + c + "' and instead saw '" +
+                        n + "'");
+            }
+            throw this.syntaxError("Expected '" + c + "' and instead saw ''");
+        }
+        return n;
+    }
+
+
+    /**
+     * Get the next n characters.
+     *
+     * @param n     The number of characters to take.
+     * @return      A string of n characters.
+     * @throws JSONException
+     *   Substring bounds error if there are not
+     *   n characters remaining in the source string.
+     */
+    public String next(int n) throws JSONException {
+        if (n == 0) {
+            return "";
+        }
+
+        char[] chars = new char[n];
+        int pos = 0;
+
+        while (pos < n) {
+            chars[pos] = this.next();
+            if (this.end()) {
+                throw this.syntaxError("Substring bounds error");
+            }
+            pos += 1;
+        }
+        return new String(chars);
+    }
+
+
+    /**
+     * Get the next char in the string, skipping whitespace.
+     * @throws JSONException Thrown if there is an error reading the source string.
+     * @return  A character, or 0 if there are no more characters.
+     */
+    public char nextClean() throws JSONException {
+        for (;;) {
+            char c = this.next();
+            if (c == 0 || c > ' ') {
+                return c;
+            }
+        }
+    }
+
+
+    /**
+     * Return the characters up to the next close quote character.
+     * Backslash processing is done. The formal JSON format does not
+     * allow strings in single quotes, but an implementation is allowed to
+     * accept them.
+     * @param quote The quoting character, either
+     *      <code>"</code>&nbsp;<small>(double quote)</small> or
+     *      <code>'</code>&nbsp;<small>(single quote)</small>.
+     * @return      A String.
+     * @throws JSONException Unterminated string.
+     */
+    public String nextString(char quote) throws JSONException {
+        char c;
+        StringBuilder sb = new StringBuilder();
+        for (;;) {
+            c = this.next();
+            switch (c) {
+            case 0:
+            case '\n':
+            case '\r':
+                throw this.syntaxError("Unterminated string");
+            case '\\':
+                c = this.next();
+                switch (c) {
+                case 'b':
+                    sb.append('\b');
+                    break;
+                case 't':
+                    sb.append('\t');
+                    break;
+                case 'n':
+                    sb.append('\n');
+                    break;
+                case 'f':
+                    sb.append('\f');
+                    break;
+                case 'r':
+                    sb.append('\r');
+                    break;
+                case 'u':
+                    try {
+                        sb.append((char)Integer.parseInt(this.next(4), 16));
+                    } catch (NumberFormatException e) {
+                        throw this.syntaxError("Illegal escape.", e);
+                    }
+                    break;
+                case '"':
+                case '\'':
+                case '\\':
+                case '/':
+                    sb.append(c);
+                    break;
+                default:
+                    throw this.syntaxError("Illegal escape.");
+                }
+                break;
+            default:
+                if (c == quote) {
+                    return sb.toString();
+                }
+                sb.append(c);
+            }
+        }
+    }
+
+
+    /**
+     * Get the text up but not including the specified character or the
+     * end of line, whichever comes first.
+     * @param  delimiter A delimiter character.
+     * @return   A string.
+     * @throws JSONException Thrown if there is an error while searching
+     *  for the delimiter
+     */
+    public String nextTo(char delimiter) throws JSONException {
+        StringBuilder sb = new StringBuilder();
+        for (;;) {
+            char c = this.next();
+            if (c == delimiter || c == 0 || c == '\n' || c == '\r') {
+                if (c != 0) {
+                    this.back();
+                }
+                return sb.toString().trim();
+            }
+            sb.append(c);
+        }
+    }
+
+
+    /**
+     * Get the text up but not including one of the specified delimiter
+     * characters or the end of line, whichever comes first.
+     * @param delimiters A set of delimiter characters.
+     * @return A string, trimmed.
+     * @throws JSONException Thrown if there is an error while searching
+     *  for the delimiter
+     */
+    public String nextTo(String delimiters) throws JSONException {
+        char c;
+        StringBuilder sb = new StringBuilder();
+        for (;;) {
+            c = this.next();
+            if (delimiters.indexOf(c) >= 0 || c == 0 ||
+                    c == '\n' || c == '\r') {
+                if (c != 0) {
+                    this.back();
+                }
+                return sb.toString().trim();
+            }
+            sb.append(c);
+        }
+    }
+
+
+    /**
+     * Get the next value. The value can be a Boolean, Double, Integer,
+     * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
+     * @throws JSONException If syntax error.
+     *
+     * @return An object.
+     */
+    public Object nextValue() throws JSONException {
+        char c = this.nextClean();
+        String string;
+
+        switch (c) {
+        case '"':
+        case '\'':
+            return this.nextString(c);
+        case '{':
+            this.back();
+            return new JSONObject(this);
+        case '[':
+            this.back();
+            return new JSONArray(this);
+        }
+
+        /*
+         * Handle unquoted text. This could be the values true, false, or
+         * null, or it can be a number. An implementation (such as this one)
+         * is allowed to also accept non-standard forms.
+         *
+         * Accumulate characters until we reach the end of the text or a
+         * formatting character.
+         */
+
+        StringBuilder sb = new StringBuilder();
+        while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
+            sb.append(c);
+            c = this.next();
+        }
+        if (!this.eof) {
+            this.back();
+        }
+
+        string = sb.toString().trim();
+        if ("".equals(string)) {
+            throw this.syntaxError("Missing value");
+        }
+        return JSONObject.stringToValue(string);
+    }
+
+
+    /**
+     * Skip characters until the next character is the requested character.
+     * If the requested character is not found, no characters are skipped.
+     * @param to A character to skip to.
+     * @return The requested character, or zero if the requested character
+     * is not found.
+     * @throws JSONException Thrown if there is an error while searching
+     *  for the to character
+     */
+    public char skipTo(char to) throws JSONException {
+        char c;
+        try {
+            long startIndex = this.index;
+            long startCharacter = this.character;
+            long startLine = this.line;
+            this.reader.mark(1000000);
+            do {
+                c = this.next();
+                if (c == 0) {
+                    // in some readers, reset() may throw an exception if
+                    // the remaining portion of the input is greater than
+                    // the mark size (1,000,000 above).
+                    this.reader.reset();
+                    this.index = startIndex;
+                    this.character = startCharacter;
+                    this.line = startLine;
+                    return 0;
+                }
+            } while (c != to);
+            this.reader.mark(1);
+        } catch (IOException exception) {
+            throw new JSONException(exception);
+        }
+        this.back();
+        return c;
+    }
+
+    /**
+     * Make a JSONException to signal a syntax error.
+     *
+     * @param message The error message.
+     * @return  A JSONException object, suitable for throwing
+     */
+    public JSONException syntaxError(String message) {
+        return new JSONException(message + this.toString());
+    }
+
+    /**
+     * Make a JSONException to signal a syntax error.
+     *
+     * @param message The error message.
+     * @param causedBy The throwable that caused the error.
+     * @return  A JSONException object, suitable for throwing
+     */
+    public JSONException syntaxError(String message, Throwable causedBy) {
+        return new JSONException(message + this.toString(), causedBy);
+    }
+
+    /**
+     * Make a printable string of this JSONTokener.
+     *
+     * @return " at {index} [character {character} line {line}]"
+     */
+    @Override
+    public String toString() {
+        return " at " + this.index + " [character " + this.character + " line " +
+                this.line + "]";
+    }
+}

+ 414 - 0
src/main/java/com/usky/xml/JSONWriter.java

@@ -0,0 +1,414 @@
+package com.usky.xml;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/*
+Copyright (c) 2006 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/**
+ * JSONWriter provides a quick and convenient way of producing JSON text.
+ * The texts produced strictly conform to JSON syntax rules. No whitespace is
+ * added, so the results are ready for transmission or storage. Each instance of
+ * JSONWriter can produce one JSON text.
+ * <p>
+ * A JSONWriter instance provides a <code>value</code> method for appending
+ * values to the
+ * text, and a <code>key</code>
+ * method for adding keys before values in objects. There are <code>array</code>
+ * and <code>endArray</code> methods that make and bound array values, and
+ * <code>object</code> and <code>endObject</code> methods which make and bound
+ * object values. All of these methods return the JSONWriter instance,
+ * permitting a cascade style. For example, <pre>
+ * new JSONWriter(myWriter)
+ *     .object()
+ *         .key("JSON")
+ *         .value("Hello, World!")
+ *     .endObject();</pre> which writes <pre>
+ * {"JSON":"Hello, World!"}</pre>
+ * <p>
+ * The first method called must be <code>array</code> or <code>object</code>.
+ * There are no methods for adding commas or colons. JSONWriter adds them for
+ * you. Objects and arrays can be nested up to 200 levels deep.
+ * <p>
+ * This can sometimes be easier than using a JSONObject to build a string.
+ * @author JSON.org
+ * @version 2016-08-08
+ */
+public class JSONWriter {
+    private static final int maxdepth = 200;
+
+    /**
+     * The comma flag determines if a comma should be output before the next
+     * value.
+     */
+    private boolean comma;
+
+    /**
+     * The current mode. Values:
+     * 'a' (array),
+     * 'd' (done),
+     * 'i' (initial),
+     * 'k' (key),
+     * 'o' (object).
+     */
+    protected char mode;
+
+    /**
+     * The object/array stack.
+     */
+    private final JSONObject stack[];
+
+    /**
+     * The stack top index. A value of 0 indicates that the stack is empty.
+     */
+    private int top;
+
+    /**
+     * The writer that will receive the output.
+     */
+    protected Appendable writer;
+
+    /**
+     * Make a fresh JSONWriter. It can be used to build one JSON text.
+     * @param w an appendable object
+     */
+    public JSONWriter(Appendable w) {
+        this.comma = false;
+        this.mode = 'i';
+        this.stack = new JSONObject[maxdepth];
+        this.top = 0;
+        this.writer = w;
+    }
+
+    /**
+     * Append a value.
+     * @param string A string value.
+     * @return this
+     * @throws JSONException If the value is out of sequence.
+     */
+    private JSONWriter append(String string) throws JSONException {
+        if (string == null) {
+            throw new JSONException("Null pointer");
+        }
+        if (this.mode == 'o' || this.mode == 'a') {
+            try {
+                if (this.comma && this.mode == 'a') {
+                    this.writer.append(',');
+                }
+                this.writer.append(string);
+            } catch (IOException e) {
+            	// Android as of API 25 does not support this exception constructor
+            	// however we won't worry about it. If an exception is happening here
+            	// it will just throw a "Method not found" exception instead.
+                throw new JSONException(e);
+            }
+            if (this.mode == 'o') {
+                this.mode = 'k';
+            }
+            this.comma = true;
+            return this;
+        }
+        throw new JSONException("Value out of sequence.");
+    }
+
+    /**
+     * Begin appending a new array. All values until the balancing
+     * <code>endArray</code> will be appended to this array. The
+     * <code>endArray</code> method must be called to mark the array's end.
+     * @return this
+     * @throws JSONException If the nesting is too deep, or if the object is
+     * started in the wrong place (for example as a key or after the end of the
+     * outermost array or object).
+     */
+    public JSONWriter array() throws JSONException {
+        if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') {
+            this.push(null);
+            this.append("[");
+            this.comma = false;
+            return this;
+        }
+        throw new JSONException("Misplaced array.");
+    }
+
+    /**
+     * End something.
+     * @param m Mode
+     * @param c Closing character
+     * @return this
+     * @throws JSONException If unbalanced.
+     */
+    private JSONWriter end(char m, char c) throws JSONException {
+        if (this.mode != m) {
+            throw new JSONException(m == 'a'
+                ? "Misplaced endArray."
+                : "Misplaced endObject.");
+        }
+        this.pop(m);
+        try {
+            this.writer.append(c);
+        } catch (IOException e) {
+        	// Android as of API 25 does not support this exception constructor
+        	// however we won't worry about it. If an exception is happening here
+        	// it will just throw a "Method not found" exception instead.
+            throw new JSONException(e);
+        }
+        this.comma = true;
+        return this;
+    }
+
+    /**
+     * End an array. This method most be called to balance calls to
+     * <code>array</code>.
+     * @return this
+     * @throws JSONException If incorrectly nested.
+     */
+    public JSONWriter endArray() throws JSONException {
+        return this.end('a', ']');
+    }
+
+    /**
+     * End an object. This method most be called to balance calls to
+     * <code>object</code>.
+     * @return this
+     * @throws JSONException If incorrectly nested.
+     */
+    public JSONWriter endObject() throws JSONException {
+        return this.end('k', '}');
+    }
+
+    /**
+     * Append a key. The key will be associated with the next value. In an
+     * object, every value must be preceded by a key.
+     * @param string A key string.
+     * @return this
+     * @throws JSONException If the key is out of place. For example, keys
+     *  do not belong in arrays or if the key is null.
+     */
+    public JSONWriter key(String string) throws JSONException {
+        if (string == null) {
+            throw new JSONException("Null key.");
+        }
+        if (this.mode == 'k') {
+            try {
+                JSONObject topObject = this.stack[this.top - 1];
+                // don't use the built in putOnce method to maintain Android support
+				if(topObject.has(string)) {
+					throw new JSONException("Duplicate key \"" + string + "\"");
+				}
+                topObject.put(string, true);
+                if (this.comma) {
+                    this.writer.append(',');
+                }
+                this.writer.append(JSONObject.quote(string));
+                this.writer.append(':');
+                this.comma = false;
+                this.mode = 'o';
+                return this;
+            } catch (IOException e) {
+            	// Android as of API 25 does not support this exception constructor
+            	// however we won't worry about it. If an exception is happening here
+            	// it will just throw a "Method not found" exception instead.
+                throw new JSONException(e);
+            }
+        }
+        throw new JSONException("Misplaced key.");
+    }
+
+
+    /**
+     * Begin appending a new object. All keys and values until the balancing
+     * <code>endObject</code> will be appended to this object. The
+     * <code>endObject</code> method must be called to mark the object's end.
+     * @return this
+     * @throws JSONException If the nesting is too deep, or if the object is
+     * started in the wrong place (for example as a key or after the end of the
+     * outermost array or object).
+     */
+    public JSONWriter object() throws JSONException {
+        if (this.mode == 'i') {
+            this.mode = 'o';
+        }
+        if (this.mode == 'o' || this.mode == 'a') {
+            this.append("{");
+            this.push(new JSONObject());
+            this.comma = false;
+            return this;
+        }
+        throw new JSONException("Misplaced object.");
+
+    }
+
+
+    /**
+     * Pop an array or object scope.
+     * @param c The scope to close.
+     * @throws JSONException If nesting is wrong.
+     */
+    private void pop(char c) throws JSONException {
+        if (this.top <= 0) {
+            throw new JSONException("Nesting error.");
+        }
+        char m = this.stack[this.top - 1] == null ? 'a' : 'k';
+        if (m != c) {
+            throw new JSONException("Nesting error.");
+        }
+        this.top -= 1;
+        this.mode = this.top == 0
+            ? 'd'
+            : this.stack[this.top - 1] == null
+            ? 'a'
+            : 'k';
+    }
+
+    /**
+     * Push an array or object scope.
+     * @param jo The scope to open.
+     * @throws JSONException If nesting is too deep.
+     */
+    private void push(JSONObject jo) throws JSONException {
+        if (this.top >= maxdepth) {
+            throw new JSONException("Nesting too deep.");
+        }
+        this.stack[this.top] = jo;
+        this.mode = jo == null ? 'a' : 'k';
+        this.top += 1;
+    }
+
+    /**
+     * Make a JSON text of an Object value. If the object has an
+     * value.toJSONString() method, then that method will be used to produce the
+     * JSON text. The method is required to produce a strictly conforming text.
+     * If the object does not contain a toJSONString method (which is the most
+     * common case), then a text will be produced by other means. If the value
+     * is an array or Collection, then a JSONArray will be made from it and its
+     * toJSONString method will be called. If the value is a MAP, then a
+     * JSONObject will be made from it and its toJSONString method will be
+     * called. Otherwise, the value's toString method will be called, and the
+     * result will be quoted.
+     *
+     * <p>
+     * Warning: This method assumes that the data structure is acyclical.
+     *
+     * @param value
+     *            The value to be serialized.
+     * @return a printable, displayable, transmittable representation of the
+     *         object, beginning with <code>{</code>&nbsp;<small>(left
+     *         brace)</small> and ending with <code>}</code>&nbsp;<small>(right
+     *         brace)</small>.
+     * @throws JSONException
+     *             If the value is or contains an invalid number.
+     */
+    public static String valueToString(Object value) throws JSONException {
+        if (value == null || value.equals(null)) {
+            return "null";
+        }
+        if (value instanceof JSONString) {
+            String object;
+            try {
+                object = ((JSONString) value).toJSONString();
+            } catch (Exception e) {
+                throw new JSONException(e);
+            }
+            if (object != null) {
+                return object;
+            }
+            throw new JSONException("Bad value from toJSONString: " + object);
+        }
+        if (value instanceof Number) {
+            // not all Numbers may match actual JSON Numbers. i.e. Fractions or Complex
+            final String numberAsString = JSONObject.numberToString((Number) value);
+            if(JSONObject.NUMBER_PATTERN.matcher(numberAsString).matches()) {
+                // Close enough to a JSON number that we will return it unquoted
+                return numberAsString;
+            }
+            // The Number value is not a valid JSON number.
+            // Instead we will quote it as a string
+            return JSONObject.quote(numberAsString);
+        }
+        if (value instanceof Boolean || value instanceof JSONObject
+                || value instanceof JSONArray) {
+            return value.toString();
+        }
+        if (value instanceof Map) {
+            Map<?, ?> map = (Map<?, ?>) value;
+            return new JSONObject(map).toString();
+        }
+        if (value instanceof Collection) {
+            Collection<?> coll = (Collection<?>) value;
+            return new JSONArray(coll).toString();
+        }
+        if (value.getClass().isArray()) {
+            return new JSONArray(value).toString();
+        }
+        if(value instanceof Enum<?>){
+            return JSONObject.quote(((Enum<?>)value).name());
+        }
+        return JSONObject.quote(value.toString());
+    }
+
+    /**
+     * Append either the value <code>true</code> or the value
+     * <code>false</code>.
+     * @param b A boolean.
+     * @return this
+     * @throws JSONException if a called function has an error
+     */
+    public JSONWriter value(boolean b) throws JSONException {
+        return this.append(b ? "true" : "false");
+    }
+
+    /**
+     * Append a double value.
+     * @param d A double.
+     * @return this
+     * @throws JSONException If the number is not finite.
+     */
+    public JSONWriter value(double d) throws JSONException {
+        return this.value(Double.valueOf(d));
+    }
+
+    /**
+     * Append a long value.
+     * @param l A long.
+     * @return this
+     * @throws JSONException if a called function has an error
+     */
+    public JSONWriter value(long l) throws JSONException {
+        return this.append(Long.toString(l));
+    }
+
+
+    /**
+     * Append an object value.
+     * @param object The object to append. It can be null, or a Boolean, Number,
+     *   String, JSONObject, or JSONArray, or an object that implements JSONString.
+     * @return this
+     * @throws JSONException If the value is out of sequence.
+     */
+    public JSONWriter value(Object object) throws JSONException {
+        return this.append(valueToString(object));
+    }
+}

+ 75 - 0
src/main/java/com/usky/xml/Property.java

@@ -0,0 +1,75 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import java.util.Enumeration;
+import java.util.Properties;
+
+/**
+ * Converts a Property file data into JSONObject and back.
+ * @author JSON.org
+ * @version 2015-05-05
+ */
+public class Property {
+    /**
+     * Converts a property file object into a JSONObject. The property file object is a table of name value pairs.
+     * @param properties java.util.Properties
+     * @return JSONObject
+     * @throws JSONException if a called function has an error
+     */
+    public static JSONObject toJSONObject(Properties properties) throws JSONException {
+        // can't use the new constructor for Android support
+        // JSONObject jo = new JSONObject(properties == null ? 0 : properties.size());
+        JSONObject jo = new JSONObject();
+        if (properties != null && !properties.isEmpty()) {
+            Enumeration<?> enumProperties = properties.propertyNames();
+            while(enumProperties.hasMoreElements()) {
+                String name = (String)enumProperties.nextElement();
+                jo.put(name, properties.getProperty(name));
+            }
+        }
+        return jo;
+    }
+
+    /**
+     * Converts the JSONObject into a property file object.
+     * @param jo JSONObject
+     * @return java.util.Properties
+     * @throws JSONException if a called function has an error
+     */
+    public static Properties toProperties(JSONObject jo)  throws JSONException {
+        Properties  properties = new Properties();
+        if (jo != null) {
+        	// Don't use the new entrySet API to maintain Android support
+            for (final String key : jo.keySet()) {
+                Object value = jo.opt(key);
+                if (!JSONObject.NULL.equals(value)) {
+                    properties.put(key, value.toString());
+                }
+            }
+        }
+        return properties;
+    }
+}

+ 859 - 0
src/main/java/com/usky/xml/XML.java

@@ -0,0 +1,859 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2015 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Iterator;
+
+
+/**
+ * This provides static methods to convert an XML text into a JSONObject, and to
+ * covert a JSONObject into an XML text.
+ *
+ * @author JSON.org
+ * @version 2016-08-10
+ */
+@SuppressWarnings("boxing")
+public class XML {
+
+    /** The Character '&amp;'. */
+    public static final Character AMP = '&';
+
+    /** The Character '''. */
+    public static final Character APOS = '\'';
+
+    /** The Character '!'. */
+    public static final Character BANG = '!';
+
+    /** The Character '='. */
+    public static final Character EQ = '=';
+
+    /** The Character <pre>{@code '>'. }</pre>*/
+    public static final Character GT = '>';
+
+    /** The Character '&lt;'. */
+    public static final Character LT = '<';
+
+    /** The Character '?'. */
+    public static final Character QUEST = '?';
+
+    /** The Character '"'. */
+    public static final Character QUOT = '"';
+
+    /** The Character '/'. */
+    public static final Character SLASH = '/';
+
+    /**
+     * Null attribute name
+     */
+    public static final String NULL_ATTR = "xsi:nil";
+
+    public static final String TYPE_ATTR = "xsi:type";
+
+    /**
+     * Creates an iterator for navigating Code Points in a string instead of
+     * characters. Once Java7 support is dropped, this can be replaced with
+     * <code>
+     * string.codePoints()
+     * </code>
+     * which is available in Java8 and above.
+     *
+     * @see <a href=
+     *      "http://stackoverflow.com/a/21791059/6030888">http://stackoverflow.com/a/21791059/6030888</a>
+     */
+    private static Iterable<Integer> codePointIterator(final String string) {
+        return new Iterable<Integer>() {
+            @Override
+            public Iterator<Integer> iterator() {
+                return new Iterator<Integer>() {
+                    private int nextIndex = 0;
+                    private int length = string.length();
+
+                    @Override
+                    public boolean hasNext() {
+                        return this.nextIndex < this.length;
+                    }
+
+                    @Override
+                    public Integer next() {
+                        int result = string.codePointAt(this.nextIndex);
+                        this.nextIndex += Character.charCount(result);
+                        return result;
+                    }
+
+                    @Override
+                    public void remove() {
+                        throw new UnsupportedOperationException();
+                    }
+                };
+            }
+        };
+    }
+
+    /**
+     * Replace special characters with XML escapes:
+     *
+     * <pre>{@code 
+     * &amp; (ampersand) is replaced by &amp;amp;
+     * &lt; (less than) is replaced by &amp;lt;
+     * &gt; (greater than) is replaced by &amp;gt;
+     * &quot; (double quote) is replaced by &amp;quot;
+     * &apos; (single quote / apostrophe) is replaced by &amp;apos;
+     * }</pre>
+     *
+     * @param string
+     *            The string to be escaped.
+     * @return The escaped string.
+     */
+    public static String escape(String string) {
+        StringBuilder sb = new StringBuilder(string.length());
+        for (final int cp : codePointIterator(string)) {
+            switch (cp) {
+            case '&':
+                sb.append("&amp;");
+                break;
+            case '<':
+                sb.append("&lt;");
+                break;
+            case '>':
+                sb.append("&gt;");
+                break;
+            case '"':
+                sb.append("&quot;");
+                break;
+            case '\'':
+                sb.append("&apos;");
+                break;
+            default:
+                if (mustEscape(cp)) {
+                    sb.append("&#x");
+                    sb.append(Integer.toHexString(cp));
+                    sb.append(';');
+                } else {
+                    sb.appendCodePoint(cp);
+                }
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * @param cp code point to test
+     * @return true if the code point is not valid for an XML
+     */
+    private static boolean mustEscape(int cp) {
+        /* Valid range from https://www.w3.org/TR/REC-xml/#charsets
+         *
+         * #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
+         *
+         * any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
+         */
+        // isISOControl is true when (cp >= 0 && cp <= 0x1F) || (cp >= 0x7F && cp <= 0x9F)
+        // all ISO control characters are out of range except tabs and new lines
+        return (Character.isISOControl(cp)
+                && cp != 0x9
+                && cp != 0xA
+                && cp != 0xD
+            ) || !(
+                // valid the range of acceptable characters that aren't control
+                (cp >= 0x20 && cp <= 0xD7FF)
+                || (cp >= 0xE000 && cp <= 0xFFFD)
+                || (cp >= 0x10000 && cp <= 0x10FFFF)
+            )
+        ;
+    }
+
+    /**
+     * Removes XML escapes from the string.
+     *
+     * @param string
+     *            string to remove escapes from
+     * @return string with converted entities
+     */
+    public static String unescape(String string) {
+        StringBuilder sb = new StringBuilder(string.length());
+        for (int i = 0, length = string.length(); i < length; i++) {
+            char c = string.charAt(i);
+            if (c == '&') {
+                final int semic = string.indexOf(';', i);
+                if (semic > i) {
+                    final String entity = string.substring(i + 1, semic);
+                    sb.append(XMLTokener.unescapeEntity(entity));
+                    // skip past the entity we just parsed.
+                    i += entity.length() + 1;
+                } else {
+                    // this shouldn't happen in most cases since the parser
+                    // errors on unclosed entries.
+                    sb.append(c);
+                }
+            } else {
+                // not part of an entity
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Throw an exception if the string contains whitespace. Whitespace is not
+     * allowed in tagNames and attributes.
+     *
+     * @param string
+     *            A string.
+     * @throws JSONException Thrown if the string contains whitespace or is empty.
+     */
+    public static void noSpace(String string) throws JSONException {
+        int i, length = string.length();
+        if (length == 0) {
+            throw new JSONException("Empty string.");
+        }
+        for (i = 0; i < length; i += 1) {
+            if (Character.isWhitespace(string.charAt(i))) {
+                throw new JSONException("'" + string
+                        + "' contains a space character.");
+            }
+        }
+    }
+
+    /**
+     * Scan the content following the named tag, attaching it to the context.
+     *
+     * @param x
+     *            The XMLTokener containing the source string.
+     * @param context
+     *            The JSONObject that will include the new material.
+     * @param name
+     *            The tag name.
+     * @return true if the close tag is processed.
+     * @throws JSONException
+     */
+    private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config)
+            throws JSONException {
+        char c;
+        int i;
+        JSONObject jsonObject = null;
+        String string;
+        String tagName;
+        Object token;
+        XMLXsiTypeConverter<?> xmlXsiTypeConverter;
+
+        // Test for and skip past these forms:
+        // <!-- ... -->
+        // <! ... >
+        // <![ ... ]]>
+        // <? ... ?>
+        // Report errors for these forms:
+        // <>
+        // <=
+        // <<
+
+        token = x.nextToken();
+
+        // <!
+
+        if (token == BANG) {
+            c = x.next();
+            if (c == '-') {
+                if (x.next() == '-') {
+                    x.skipPast("-->");
+                    return false;
+                }
+                x.back();
+            } else if (c == '[') {
+                token = x.nextToken();
+                if ("CDATA".equals(token)) {
+                    if (x.next() == '[') {
+                        string = x.nextCDATA();
+                        if (string.length() > 0) {
+                            context.accumulate(config.getcDataTagName(), string);
+                        }
+                        return false;
+                    }
+                }
+                throw x.syntaxError("Expected 'CDATA['");
+            }
+            i = 1;
+            do {
+                token = x.nextMeta();
+                if (token == null) {
+                    throw x.syntaxError("Missing '>' after '<!'.");
+                } else if (token == LT) {
+                    i += 1;
+                } else if (token == GT) {
+                    i -= 1;
+                }
+            } while (i > 0);
+            return false;
+        } else if (token == QUEST) {
+
+            // <?
+            x.skipPast("?>");
+            return false;
+        } else if (token == SLASH) {
+
+            // Close tag </
+
+            token = x.nextToken();
+            if (name == null) {
+                throw x.syntaxError("Mismatched close tag " + token);
+            }
+            if (!token.equals(name)) {
+                throw x.syntaxError("Mismatched " + name + " and " + token);
+            }
+            if (x.nextToken() != GT) {
+                throw x.syntaxError("Misshaped close tag");
+            }
+            return true;
+
+        } else if (token instanceof Character) {
+            throw x.syntaxError("Misshaped tag");
+
+            // Open tag <
+
+        } else {
+            tagName = (String) token;
+            token = null;
+            jsonObject = new JSONObject();
+            boolean nilAttributeFound = false;
+            xmlXsiTypeConverter = null;
+            for (;;) {
+                if (token == null) {
+                    token = x.nextToken();
+                }
+                // attribute = value
+                if (token instanceof String) {
+                    string = (String) token;
+                    token = x.nextToken();
+                    if (token == EQ) {
+                        token = x.nextToken();
+                        if (!(token instanceof String)) {
+                            throw x.syntaxError("Missing value");
+                        }
+
+                        if (config.isConvertNilAttributeToNull()
+                                && NULL_ATTR.equals(string)
+                                && Boolean.parseBoolean((String) token)) {
+                            nilAttributeFound = true;
+                        } else if(config.getXsiTypeMap() != null && !config.getXsiTypeMap().isEmpty()
+                                && TYPE_ATTR.equals(string)) {
+                            xmlXsiTypeConverter = config.getXsiTypeMap().get(token);
+                        } else if (!nilAttributeFound) {
+                            jsonObject.accumulate(string,
+                                    config.isKeepStrings()
+                                            ? ((String) token)
+                                            : stringToValue((String) token));
+                        }
+                        token = null;
+                    } else {
+                        jsonObject.accumulate(string, "");
+                    }
+
+
+                } else if (token == SLASH) {
+                    // Empty tag <.../>
+                    if (x.nextToken() != GT) {
+                        throw x.syntaxError("Misshaped tag");
+                    }
+                    if (nilAttributeFound) {
+                        context.accumulate(tagName, JSONObject.NULL);
+                    } else if (jsonObject.length() > 0) {
+                        context.accumulate(tagName, jsonObject);
+                    } else {
+                        context.accumulate(tagName, "");
+                    }
+                    return false;
+
+                } else if (token == GT) {
+                    // Content, between <...> and </...>
+                    for (;;) {
+                        token = x.nextContent();
+                        if (token == null) {
+                            if (tagName != null) {
+                                throw x.syntaxError("Unclosed tag " + tagName);
+                            }
+                            return false;
+                        } else if (token instanceof String) {
+                            string = (String) token;
+                            if (string.length() > 0) {
+                                if(xmlXsiTypeConverter != null) {
+                                    jsonObject.accumulate(config.getcDataTagName(),
+                                            stringToValue(string, xmlXsiTypeConverter));
+                                } else {
+                                    jsonObject.accumulate(config.getcDataTagName(),
+                                            config.isKeepStrings() ? string : stringToValue(string));
+                                }
+                            }
+
+                        } else if (token == LT) {
+                            // Nested element
+                            if (parse(x, jsonObject, tagName, config)) {
+                                if (jsonObject.length() == 0) {
+                                    context.accumulate(tagName, "");
+                                } else if (jsonObject.length() == 1
+                                        && jsonObject.opt(config.getcDataTagName()) != null) {
+                                    context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()));
+                                } else {
+                                    context.accumulate(tagName, jsonObject);
+                                }
+                                return false;
+                            }
+                        }
+                    }
+                } else {
+                    throw x.syntaxError("Misshaped tag");
+                }
+            }
+        }
+    }
+
+    /**
+     * This method tries to convert the given string value to the target object
+     * @param string String to convert
+     * @param typeConverter value converter to convert string to integer, boolean e.t.c
+     * @return JSON value of this string or the string
+     */
+    public static Object stringToValue(String string, XMLXsiTypeConverter<?> typeConverter) {
+        if(typeConverter != null) {
+            return typeConverter.convert(string);
+        }
+        return stringToValue(string);
+    }
+
+    /**
+     * This method is the same as {@link JSONObject#stringToValue(String)}.
+     *
+     * @param string String to convert
+     * @return JSON value of this string or the string
+     */
+    // To maintain compatibility with the Android API, this method is a direct copy of
+    // the one in JSONObject. Changes made here should be reflected there.
+    // This method should not make calls out of the XML object.
+    public static Object stringToValue(String string) {
+        if ("".equals(string)) {
+            return string;
+        }
+
+        // check JSON key words true/false/null
+        if ("true".equalsIgnoreCase(string)) {
+            return Boolean.TRUE;
+        }
+        if ("false".equalsIgnoreCase(string)) {
+            return Boolean.FALSE;
+        }
+        if ("null".equalsIgnoreCase(string)) {
+            return JSONObject.NULL;
+        }
+
+        /*
+         * If it might be a number, try converting it. If a number cannot be
+         * produced, then the value will just be a string.
+         */
+
+        char initial = string.charAt(0);
+        if ((initial >= '0' && initial <= '9') || initial == '-') {
+            try {
+                return stringToNumber(string);
+            } catch (Exception ignore) {
+            }
+        }
+        return string;
+    }
+    
+    /**
+     * direct copy of {@link JSONObject#stringToNumber(String)} to maintain Android support.
+     */
+    private static Number stringToNumber(final String val) throws NumberFormatException {
+        char initial = val.charAt(0);
+        if ((initial >= '0' && initial <= '9') || initial == '-') {
+            // decimal representation
+            if (isDecimalNotation(val)) {
+                // Use a BigDecimal all the time so we keep the original
+                // representation. BigDecimal doesn't support -0.0, ensure we
+                // keep that by forcing a decimal.
+                try {
+                    BigDecimal bd = new BigDecimal(val);
+                    if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
+                        return Double.valueOf(-0.0);
+                    }
+                    return bd;
+                } catch (NumberFormatException retryAsDouble) {
+                    // this is to support "Hex Floats" like this: 0x1.0P-1074
+                    try {
+                        Double d = Double.valueOf(val);
+                        if(d.isNaN() || d.isInfinite()) {
+                            throw new NumberFormatException("val ["+val+"] is not a valid number.");
+                        }
+                        return d;
+                    } catch (NumberFormatException ignore) {
+                        throw new NumberFormatException("val ["+val+"] is not a valid number.");
+                    }
+                }
+            }
+            // block items like 00 01 etc. Java number parsers treat these as Octal.
+            if(initial == '0' && val.length() > 1) {
+                char at1 = val.charAt(1);
+                if(at1 >= '0' && at1 <= '9') {
+                    throw new NumberFormatException("val ["+val+"] is not a valid number.");
+                }
+            } else if (initial == '-' && val.length() > 2) {
+                char at1 = val.charAt(1);
+                char at2 = val.charAt(2);
+                if(at1 == '0' && at2 >= '0' && at2 <= '9') {
+                    throw new NumberFormatException("val ["+val+"] is not a valid number.");
+                }
+            }
+            // integer representation.
+            // This will narrow any values to the smallest reasonable Object representation
+            // (Integer, Long, or BigInteger)
+            
+            // BigInteger down conversion: We use a similar bitLenth compare as
+            // BigInteger#intValueExact uses. Increases GC, but objects hold
+            // only what they need. i.e. Less runtime overhead if the value is
+            // long lived.
+            BigInteger bi = new BigInteger(val);
+            if(bi.bitLength() <= 31){
+                return Integer.valueOf(bi.intValue());
+            }
+            if(bi.bitLength() <= 63){
+                return Long.valueOf(bi.longValue());
+            }
+            return bi;
+        }
+        throw new NumberFormatException("val ["+val+"] is not a valid number.");
+    }
+    
+    /**
+     * direct copy of {@link JSONObject#isDecimalNotation(String)} to maintain Android support.
+     */
+    private static boolean isDecimalNotation(final String val) {
+        return val.indexOf('.') > -1 || val.indexOf('e') > -1
+                || val.indexOf('E') > -1 || "-0".equals(val);
+    }
+
+
+    /**
+     * Convert a well-formed (but not necessarily valid) XML string into a
+     * JSONObject. Some information may be lost in this transformation because
+     * JSON is a data format and XML is a document format. XML uses elements,
+     * attributes, and content text, while JSON uses unordered collections of
+     * name/value pairs and arrays of values. JSON does not does not like to
+     * distinguish between elements and attributes. Sequences of similar
+     * elements are represented as JSONArrays. Content text may be placed in a
+     * "content" member. Comments, prologs, DTDs, and <pre>{@code 
+     * &lt;[ [ ]]>}</pre>
+     * are ignored.
+     *
+     * @param string
+     *            The source string.
+     * @return A JSONObject containing the structured data from the XML string.
+     * @throws JSONException Thrown if there is an errors while parsing the string
+     */
+    public static JSONObject toJSONObject(String string) throws JSONException {
+        return toJSONObject(string, XMLParserConfiguration.ORIGINAL);
+    }
+
+    /**
+     * Convert a well-formed (but not necessarily valid) XML into a
+     * JSONObject. Some information may be lost in this transformation because
+     * JSON is a data format and XML is a document format. XML uses elements,
+     * attributes, and content text, while JSON uses unordered collections of
+     * name/value pairs and arrays of values. JSON does not does not like to
+     * distinguish between elements and attributes. Sequences of similar
+     * elements are represented as JSONArrays. Content text may be placed in a
+     * "content" member. Comments, prologs, DTDs, and <pre>{@code 
+     * &lt;[ [ ]]>}</pre>
+     * are ignored.
+     *
+     * @param reader The XML source reader.
+     * @return A JSONObject containing the structured data from the XML string.
+     * @throws JSONException Thrown if there is an errors while parsing the string
+     */
+    public static JSONObject toJSONObject(Reader reader) throws JSONException {
+        return toJSONObject(reader, XMLParserConfiguration.ORIGINAL);
+    }
+
+    /**
+     * Convert a well-formed (but not necessarily valid) XML into a
+     * JSONObject. Some information may be lost in this transformation because
+     * JSON is a data format and XML is a document format. XML uses elements,
+     * attributes, and content text, while JSON uses unordered collections of
+     * name/value pairs and arrays of values. JSON does not does not like to
+     * distinguish between elements and attributes. Sequences of similar
+     * elements are represented as JSONArrays. Content text may be placed in a
+     * "content" member. Comments, prologs, DTDs, and <pre>{@code
+     * &lt;[ [ ]]>}</pre>
+     * are ignored.
+     *
+     * All values are converted as strings, for 1, 01, 29.0 will not be coerced to
+     * numbers but will instead be the exact value as seen in the XML document.
+     *
+     * @param reader The XML source reader.
+     * @param keepStrings If true, then values will not be coerced into boolean
+     *  or numeric values and will instead be left as strings
+     * @return A JSONObject containing the structured data from the XML string.
+     * @throws JSONException Thrown if there is an errors while parsing the string
+     */
+    public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws JSONException {
+        if(keepStrings) {
+            return toJSONObject(reader, XMLParserConfiguration.KEEP_STRINGS);
+        }
+        return toJSONObject(reader, XMLParserConfiguration.ORIGINAL);
+    }
+
+    /**
+     * Convert a well-formed (but not necessarily valid) XML into a
+     * JSONObject. Some information may be lost in this transformation because
+     * JSON is a data format and XML is a document format. XML uses elements,
+     * attributes, and content text, while JSON uses unordered collections of
+     * name/value pairs and arrays of values. JSON does not does not like to
+     * distinguish between elements and attributes. Sequences of similar
+     * elements are represented as JSONArrays. Content text may be placed in a
+     * "content" member. Comments, prologs, DTDs, and <pre>{@code
+     * &lt;[ [ ]]>}</pre>
+     * are ignored.
+     *
+     * All values are converted as strings, for 1, 01, 29.0 will not be coerced to
+     * numbers but will instead be the exact value as seen in the XML document.
+     *
+     * @param reader The XML source reader.
+     * @param config Configuration options for the parser
+     * @return A JSONObject containing the structured data from the XML string.
+     * @throws JSONException Thrown if there is an errors while parsing the string
+     */
+    public static JSONObject toJSONObject(Reader reader, XMLParserConfiguration config) throws JSONException {
+        JSONObject jo = new JSONObject();
+        XMLTokener x = new XMLTokener(reader);
+        while (x.more()) {
+            x.skipPast("<");
+            if(x.more()) {
+                parse(x, jo, null, config);
+            }
+        }
+        return jo;
+    }
+
+    /**
+     * Convert a well-formed (but not necessarily valid) XML string into a
+     * JSONObject. Some information may be lost in this transformation because
+     * JSON is a data format and XML is a document format. XML uses elements,
+     * attributes, and content text, while JSON uses unordered collections of
+     * name/value pairs and arrays of values. JSON does not does not like to
+     * distinguish between elements and attributes. Sequences of similar
+     * elements are represented as JSONArrays. Content text may be placed in a
+     * "content" member. Comments, prologs, DTDs, and <pre>{@code 
+     * &lt;[ [ ]]>}</pre>
+     * are ignored.
+     *
+     * All values are converted as strings, for 1, 01, 29.0 will not be coerced to
+     * numbers but will instead be the exact value as seen in the XML document.
+     *
+     * @param string
+     *            The source string.
+     * @param keepStrings If true, then values will not be coerced into boolean
+     *  or numeric values and will instead be left as strings
+     * @return A JSONObject containing the structured data from the XML string.
+     * @throws JSONException Thrown if there is an errors while parsing the string
+     */
+    public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException {
+        return toJSONObject(new StringReader(string), keepStrings);
+    }
+
+    /**
+     * Convert a well-formed (but not necessarily valid) XML string into a
+     * JSONObject. Some information may be lost in this transformation because
+     * JSON is a data format and XML is a document format. XML uses elements,
+     * attributes, and content text, while JSON uses unordered collections of
+     * name/value pairs and arrays of values. JSON does not does not like to
+     * distinguish between elements and attributes. Sequences of similar
+     * elements are represented as JSONArrays. Content text may be placed in a
+     * "content" member. Comments, prologs, DTDs, and <pre>{@code 
+     * &lt;[ [ ]]>}</pre>
+     * are ignored.
+     *
+     * All values are converted as strings, for 1, 01, 29.0 will not be coerced to
+     * numbers but will instead be the exact value as seen in the XML document.
+     *
+     * @param string
+     *            The source string.
+     * @param config Configuration options for the parser.
+     * @return A JSONObject containing the structured data from the XML string.
+     * @throws JSONException Thrown if there is an errors while parsing the string
+     */
+    public static JSONObject toJSONObject(String string, XMLParserConfiguration config) throws JSONException {
+        return toJSONObject(new StringReader(string), config);
+    }
+
+    /**
+     * Convert a JSONObject into a well-formed, element-normal XML string.
+     *
+     * @param object
+     *            A JSONObject.
+     * @return A string.
+     * @throws JSONException Thrown if there is an error parsing the string
+     */
+    public static String toString(Object object) throws JSONException {
+        return toString(object, null, XMLParserConfiguration.ORIGINAL);
+    }
+
+    /**
+     * Convert a JSONObject into a well-formed, element-normal XML string.
+     *
+     * @param object
+     *            A JSONObject.
+     * @param tagName
+     *            The optional name of the enclosing tag.
+     * @return A string.
+     * @throws JSONException Thrown if there is an error parsing the string
+     */
+    public static String toString(final Object object, final String tagName) {
+        return toString(object, tagName, XMLParserConfiguration.ORIGINAL);
+    }
+
+    /**
+     * Convert a JSONObject into a well-formed, element-normal XML string.
+     *
+     * @param object
+     *            A JSONObject.
+     * @param tagName
+     *            The optional name of the enclosing tag.
+     * @param config
+     *            Configuration that can control output to XML.
+     * @return A string.
+     * @throws JSONException Thrown if there is an error parsing the string
+     */
+    public static String toString(final Object object, final String tagName, final XMLParserConfiguration config)
+            throws JSONException {
+        StringBuilder sb = new StringBuilder();
+        JSONArray ja;
+        JSONObject jo;
+        String string;
+
+        if (object instanceof JSONObject) {
+
+            // Emit <tagName>
+            if (tagName != null) {
+                sb.append('<');
+                sb.append(tagName);
+                sb.append('>');
+            }
+
+            // Loop thru the keys.
+            // don't use the new entrySet accessor to maintain Android Support
+            jo = (JSONObject) object;
+            for (final String key : jo.keySet()) {
+                Object value = jo.opt(key);
+                if (value == null) {
+                    value = "";
+                } else if (value.getClass().isArray()) {
+                    value = new JSONArray(value);
+                }
+
+                // Emit content in body
+                if (key.equals(config.getcDataTagName())) {
+                    if (value instanceof JSONArray) {
+                        ja = (JSONArray) value;
+                        int jaLength = ja.length();
+                        // don't use the new iterator API to maintain support for Android
+						for (int i = 0; i < jaLength; i++) {
+                            if (i > 0) {
+                                sb.append('\n');
+                            }
+                            Object val = ja.opt(i);
+                            sb.append(escape(val.toString()));
+                        }
+                    } else {
+                        sb.append(escape(value.toString()));
+                    }
+
+                    // Emit an array of similar keys
+
+                } else if (value instanceof JSONArray) {
+                    ja = (JSONArray) value;
+                    int jaLength = ja.length();
+                    // don't use the new iterator API to maintain support for Android
+					for (int i = 0; i < jaLength; i++) {
+                        Object val = ja.opt(i);
+                        if (val instanceof JSONArray) {
+                            sb.append('<');
+                            sb.append(key);
+                            sb.append('>');
+                            sb.append(toString(val, null, config));
+                            sb.append("</");
+                            sb.append(key);
+                            sb.append('>');
+                        } else {
+                            sb.append(toString(val, key, config));
+                        }
+                    }
+                } else if ("".equals(value)) {
+                    sb.append('<');
+                    sb.append(key);
+                    sb.append("/>");
+
+                    // Emit a new tag <k>
+
+                } else {
+                    sb.append(toString(value, key, config));
+                }
+            }
+            if (tagName != null) {
+
+                // Emit the </tagName> close tag
+                sb.append("</");
+                sb.append(tagName);
+                sb.append('>');
+            }
+            return sb.toString();
+
+        }
+
+        if (object != null && (object instanceof JSONArray ||  object.getClass().isArray())) {
+            if(object.getClass().isArray()) {
+                ja = new JSONArray(object);
+            } else {
+                ja = (JSONArray) object;
+            }
+            int jaLength = ja.length();
+            // don't use the new iterator API to maintain support for Android
+			for (int i = 0; i < jaLength; i++) {
+                Object val = ja.opt(i);
+                // XML does not have good support for arrays. If an array
+                // appears in a place where XML is lacking, synthesize an
+                // <array> element.
+                sb.append(toString(val, tagName == null ? "array" : tagName, config));
+            }
+            return sb.toString();
+        }
+
+        string = (object == null) ? "null" : escape(object.toString());
+        return (tagName == null) ? "\"" + string + "\""
+                : (string.length() == 0) ? "<" + tagName + "/>" : "<" + tagName
+                        + ">" + string + "</" + tagName + ">";
+
+    }
+}

+ 286 - 0
src/main/java/com/usky/xml/XMLParserConfiguration.java

@@ -0,0 +1,286 @@
+package com.usky.xml;
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Configuration object for the XML parser. The configuration is immutable.
+ * @author AylwardJ
+ */
+@SuppressWarnings({""})
+public class XMLParserConfiguration {
+    /** Original Configuration of the XML Parser. */
+    public static final XMLParserConfiguration ORIGINAL
+        = new XMLParserConfiguration();
+    /** Original configuration of the XML Parser except that values are kept as strings. */
+    public static final XMLParserConfiguration KEEP_STRINGS
+        = new XMLParserConfiguration().withKeepStrings(true);
+
+    /**
+     * When parsing the XML into JSON, specifies if values should be kept as strings (<code>true</code>), or if
+     * they should try to be guessed into JSON values (numeric, boolean, string)
+     */
+    private boolean keepStrings;
+    
+    /**
+     * The name of the key in a JSON Object that indicates a CDATA section. Historically this has
+     * been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA
+     * processing.
+     */
+    private String cDataTagName;
+    
+    /**
+     * When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
+     * should be kept as attribute(<code>false</code>), or they should be converted to
+     * <code>null</code>(<code>true</code>)
+     */
+    private boolean convertNilAttributeToNull;
+
+    /**
+     * This will allow type conversion for values in XML if xsi:type attribute is defined
+     */
+    private Map<String, XMLXsiTypeConverter<?>> xsiTypeMap;
+
+    /**
+     * Default parser configuration. Does not keep strings (tries to implicitly convert
+     * values), and the CDATA Tag Name is "content".
+     */
+    public XMLParserConfiguration () {
+        this.keepStrings = false;
+        this.cDataTagName = "content";
+        this.convertNilAttributeToNull = false;
+        this.xsiTypeMap = Collections.emptyMap();
+    }
+
+    /**
+     * Configure the parser string processing and use the default CDATA Tag Name as "content".
+     * @param keepStrings <code>true</code> to parse all values as string.
+     *      <code>false</code> to try and convert XML string values into a JSON value.
+     * @deprecated This constructor has been deprecated in favor of using the new builder
+     *      pattern for the configuration.
+     *      This constructor may be removed in a future release.
+     */
+    @Deprecated
+    public XMLParserConfiguration (final boolean keepStrings) {
+        this(keepStrings, "content", false);
+    }
+
+    /**
+     * Configure the parser string processing to try and convert XML values to JSON values and
+     * use the passed CDATA Tag Name the processing value. Pass <code>null</code> to
+     * disable CDATA processing
+     * @param cDataTagName<code>null</code> to disable CDATA processing. Any other value
+     *      to use that value as the JSONObject key name to process as CDATA.
+     * @deprecated This constructor has been deprecated in favor of using the new builder
+     *      pattern for the configuration.
+     *      This constructor may be removed in a future release.
+     */
+    @Deprecated
+    public XMLParserConfiguration (final String cDataTagName) {
+        this(false, cDataTagName, false);
+    }
+
+    /**
+     * Configure the parser to use custom settings.
+     * @param keepStrings <code>true</code> to parse all values as string.
+     *      <code>false</code> to try and convert XML string values into a JSON value.
+     * @param cDataTagName<code>null</code> to disable CDATA processing. Any other value
+     *      to use that value as the JSONObject key name to process as CDATA.
+     * @deprecated This constructor has been deprecated in favor of using the new builder
+     *      pattern for the configuration.
+     *      This constructor may be removed in a future release.
+     */
+    @Deprecated
+    public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName) {
+        this.keepStrings = keepStrings;
+        this.cDataTagName = cDataTagName;
+        this.convertNilAttributeToNull = false;
+    }
+
+    /**
+     * Configure the parser to use custom settings.
+     * @param keepStrings <code>true</code> to parse all values as string.
+     *      <code>false</code> to try and convert XML string values into a JSON value.
+     * @param cDataTagName <code>null</code> to disable CDATA processing. Any other value
+     *      to use that value as the JSONObject key name to process as CDATA.
+     * @param convertNilAttributeToNull <code>true</code> to parse values with attribute xsi:nil="true" as null.
+     *                                  <code>false</code> to parse values with attribute xsi:nil="true" as {"xsi:nil":true}.
+     * @deprecated This constructor has been deprecated in favor of using the new builder
+     *      pattern for the configuration.
+     *      This constructor may be removed or marked private in a future release.
+     */
+    @Deprecated
+    public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) {
+        this.keepStrings = keepStrings;
+        this.cDataTagName = cDataTagName;
+        this.convertNilAttributeToNull = convertNilAttributeToNull;
+    }
+
+    /**
+     * Configure the parser to use custom settings.
+     * @param keepStrings <code>true</code> to parse all values as string.
+     *      <code>false</code> to try and convert XML string values into a JSON value.
+     * @param cDataTagName <code>null</code> to disable CDATA processing. Any other value
+     *      to use that value as the JSONObject key name to process as CDATA.
+     * @param convertNilAttributeToNull <code>true</code> to parse values with attribute xsi:nil="true" as null.
+     *                                  <code>false</code> to parse values with attribute xsi:nil="true" as {"xsi:nil":true}.
+     * @param xsiTypeMap  <code>new HashMap<String, XMLXsiTypeConverter<?>>()</code> to parse values with attribute
+     *                   xsi:type="integer" as integer,  xsi:type="string" as string
+     */
+    private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
+            final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap ) {
+        this.keepStrings = keepStrings;
+        this.cDataTagName = cDataTagName;
+        this.convertNilAttributeToNull = convertNilAttributeToNull;
+        this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
+    }
+
+    /**
+     * Provides a new instance of the same configuration.
+     */
+    @Override
+    protected XMLParserConfiguration clone() {
+        // future modifications to this method should always ensure a "deep"
+        // clone in the case of collections. i.e. if a Map is added as a configuration
+        // item, a new map instance should be created and if possible each value in the
+        // map should be cloned as well. If the values of the map are known to also
+        // be immutable, then a shallow clone of the map is acceptable.
+        return new XMLParserConfiguration(
+                this.keepStrings,
+                this.cDataTagName,
+                this.convertNilAttributeToNull,
+                this.xsiTypeMap
+        );
+    }
+    
+    /**
+     * When parsing the XML into JSON, specifies if values should be kept as strings (<code>true</code>), or if
+     * they should try to be guessed into JSON values (numeric, boolean, string)
+     * 
+     * @return The {@link #keepStrings} configuration value.
+     */
+    public boolean isKeepStrings() {
+        return this.keepStrings;
+    }
+
+    /**
+     * When parsing the XML into JSON, specifies if values should be kept as strings (<code>true</code>), or if
+     * they should try to be guessed into JSON values (numeric, boolean, string)
+     * 
+     * @param newVal
+     *      new value to use for the {@link #keepStrings} configuration option.
+     * 
+     * @return The existing configuration will not be modified. A new configuration is returned.
+     */
+    public XMLParserConfiguration withKeepStrings(final boolean newVal) {
+        XMLParserConfiguration newConfig = this.clone();
+        newConfig.keepStrings = newVal;
+        return newConfig;
+    }
+
+    /**
+     * The name of the key in a JSON Object that indicates a CDATA section. Historically this has
+     * been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA
+     * processing.
+     * 
+     * @return The {@link #cDataTagName} configuration value.
+     */
+    public String getcDataTagName() {
+        return this.cDataTagName;
+    }
+
+    /**
+     * The name of the key in a JSON Object that indicates a CDATA section. Historically this has
+     * been the value "content" but can be changed. Use <code>null</code> to indicate no CDATA
+     * processing.
+     * 
+     * @param newVal
+     *      new value to use for the {@link #cDataTagName} configuration option.
+     * 
+     * @return The existing configuration will not be modified. A new configuration is returned.
+     */
+    public XMLParserConfiguration withcDataTagName(final String newVal) {
+        XMLParserConfiguration newConfig = this.clone();
+        newConfig.cDataTagName = newVal;
+        return newConfig;
+    }
+
+    /**
+     * When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
+     * should be kept as attribute(<code>false</code>), or they should be converted to
+     * <code>null</code>(<code>true</code>)
+     * 
+     * @return The {@link #convertNilAttributeToNull} configuration value.
+     */
+    public boolean isConvertNilAttributeToNull() {
+        return this.convertNilAttributeToNull;
+    }
+
+    /**
+     * When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
+     * should be kept as attribute(<code>false</code>), or they should be converted to
+     * <code>null</code>(<code>true</code>)
+     * 
+     * @param newVal
+     *      new value to use for the {@link #convertNilAttributeToNull} configuration option.
+     * 
+     * @return The existing configuration will not be modified. A new configuration is returned.
+     */
+    public XMLParserConfiguration withConvertNilAttributeToNull(final boolean newVal) {
+        XMLParserConfiguration newConfig = this.clone();
+        newConfig.convertNilAttributeToNull = newVal;
+        return newConfig;
+    }
+
+    /**
+     * When parsing the XML into JSON, specifies that the values with attribute xsi:type
+     * will be converted to target type defined to client in this configuration
+     * {@code Map<String, XMLXsiTypeConverter<?>>} to parse values with attribute
+     * xsi:type="integer" as integer,  xsi:type="string" as string
+     * @return {@link #xsiTypeMap} unmodifiable configuration map.
+     */
+    public Map<String, XMLXsiTypeConverter<?>> getXsiTypeMap() {
+        return this.xsiTypeMap;
+    }
+
+    /**
+     * When parsing the XML into JSON, specifies that the values with attribute xsi:type
+     * will be converted to target type defined to client in this configuration
+     * {@code Map<String, XMLXsiTypeConverter<?>>} to parse values with attribute
+     * xsi:type="integer" as integer,  xsi:type="string" as string
+     * @param xsiTypeMap  {@code new HashMap<String, XMLXsiTypeConverter<?>>()} to parse values with attribute
+     *                   xsi:type="integer" as integer,  xsi:type="string" as string
+     * @return The existing configuration will not be modified. A new configuration is returned.
+     */
+    public XMLParserConfiguration withXsiTypeMap(final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap) {
+        XMLParserConfiguration newConfig = this.clone();
+        Map<String, XMLXsiTypeConverter<?>> cloneXsiTypeMap = new HashMap<String, XMLXsiTypeConverter<?>>(xsiTypeMap);
+        newConfig.xsiTypeMap = Collections.unmodifiableMap(cloneXsiTypeMap);
+        return newConfig;
+    }
+}

+ 416 - 0
src/main/java/com/usky/xml/XMLTokener.java

@@ -0,0 +1,416 @@
+package com.usky.xml;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import java.io.Reader;
+
+/**
+ * The XMLTokener extends the JSONTokener to provide additional methods
+ * for the parsing of XML texts.
+ * @author JSON.org
+ * @version 2015-12-09
+ */
+public class XMLTokener extends JSONTokener {
+
+
+   /** The table of entity values. It initially contains Character values for
+    * amp, apos, gt, lt, quot.
+    */
+   public static final java.util.HashMap<String, Character> entity;
+
+   static {
+       entity = new java.util.HashMap<String, Character>(8);
+       entity.put("amp",  XML.AMP);
+       entity.put("apos", XML.APOS);
+       entity.put("gt",   XML.GT);
+       entity.put("lt",   XML.LT);
+       entity.put("quot", XML.QUOT);
+   }
+
+    /**
+     * Construct an XMLTokener from a Reader.
+     * @param r A source reader.
+     */
+    public XMLTokener(Reader r) {
+        super(r);
+    }
+
+    /**
+     * Construct an XMLTokener from a string.
+     * @param s A source string.
+     */
+    public XMLTokener(String s) {
+        super(s);
+    }
+
+    /**
+     * Get the text in the CDATA block.
+     * @return The string up to the <code>]]&gt;</code>.
+     * @throws JSONException If the <code>]]&gt;</code> is not found.
+     */
+    public String nextCDATA() throws JSONException {
+        char         c;
+        int          i;
+        StringBuilder sb = new StringBuilder();
+        while (more()) {
+            c = next();
+            sb.append(c);
+            i = sb.length() - 3;
+            if (i >= 0 && sb.charAt(i) == ']' &&
+                          sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') {
+                sb.setLength(i);
+                return sb.toString();
+            }
+        }
+        throw syntaxError("Unclosed CDATA");
+    }
+
+
+    /**
+     * Get the next XML outer token, trimming whitespace. There are two kinds
+     * of tokens: the <pre>{@code '<' }</pre> character which begins a markup
+     * tag, and the content
+     * text between markup tags.
+     *
+     * @return  A string, or a <pre>{@code '<' }</pre> Character, or null if
+     * there is no more source text.
+     * @throws JSONException if a called function has an error
+     */
+    public Object nextContent() throws JSONException {
+        char         c;
+        StringBuilder sb;
+        do {
+            c = next();
+        } while (Character.isWhitespace(c));
+        if (c == 0) {
+            return null;
+        }
+        if (c == '<') {
+            return XML.LT;
+        }
+        sb = new StringBuilder();
+        for (;;) {
+            if (c == 0) {
+                return sb.toString().trim();
+            }
+            if (c == '<') {
+                back();
+                return sb.toString().trim();
+            }
+            if (c == '&') {
+                sb.append(nextEntity(c));
+            } else {
+                sb.append(c);
+            }
+            c = next();
+        }
+    }
+
+
+    /**
+     * <pre>{@code
+     * Return the next entity. These entities are translated to Characters:
+     *     &amp;  &apos;  &gt;  &lt;  &quot;.
+     * }</pre>
+     * @param ampersand An ampersand character.
+     * @return  A Character or an entity String if the entity is not recognized.
+     * @throws JSONException If missing ';' in XML entity.
+     */
+    public Object nextEntity(@SuppressWarnings("unused") char ampersand) throws JSONException {
+        StringBuilder sb = new StringBuilder();
+        for (;;) {
+            char c = next();
+            if (Character.isLetterOrDigit(c) || c == '#') {
+                sb.append(Character.toLowerCase(c));
+            } else if (c == ';') {
+                break;
+            } else {
+                throw syntaxError("Missing ';' in XML entity: &" + sb);
+            }
+        }
+        String string = sb.toString();
+        return unescapeEntity(string);
+    }
+    
+    /**
+     * Unescape an XML entity encoding;
+     * @param e entity (only the actual entity value, not the preceding & or ending ;
+     * @return
+     */
+    static String unescapeEntity(String e) {
+        // validate
+        if (e == null || e.isEmpty()) {
+            return "";
+        }
+        // if our entity is an encoded unicode point, parse it.
+        if (e.charAt(0) == '#') {
+            int cp;
+            if (e.charAt(1) == 'x' || e.charAt(1) == 'X') {
+                // hex encoded unicode
+                cp = Integer.parseInt(e.substring(2), 16);
+            } else {
+                // decimal encoded unicode
+                cp = Integer.parseInt(e.substring(1));
+            }
+            return new String(new int[] {cp},0,1);
+        } 
+        Character knownEntity = entity.get(e);
+        if(knownEntity==null) {
+            // we don't know the entity so keep it encoded
+            return '&' + e + ';';
+        }
+        return knownEntity.toString();
+    }
+
+
+    /**
+     * <pre>{@code 
+     * Returns the next XML meta token. This is used for skipping over <!...>
+     * and <?...?> structures.
+     *  }</pre>
+     * @return <pre>{@code Syntax characters (< > / = ! ?) are returned as
+     *  Character, and strings and names are returned as Boolean. We don't care
+     *  what the values actually are.
+     *  }</pre>
+     * @throws JSONException If a string is not properly closed or if the XML
+     *  is badly structured.
+     */
+    public Object nextMeta() throws JSONException {
+        char c;
+        char q;
+        do {
+            c = next();
+        } while (Character.isWhitespace(c));
+        switch (c) {
+        case 0:
+            throw syntaxError("Misshaped meta tag");
+        case '<':
+            return XML.LT;
+        case '>':
+            return XML.GT;
+        case '/':
+            return XML.SLASH;
+        case '=':
+            return XML.EQ;
+        case '!':
+            return XML.BANG;
+        case '?':
+            return XML.QUEST;
+        case '"':
+        case '\'':
+            q = c;
+            for (;;) {
+                c = next();
+                if (c == 0) {
+                    throw syntaxError("Unterminated string");
+                }
+                if (c == q) {
+                    return Boolean.TRUE;
+                }
+            }
+        default:
+            for (;;) {
+                c = next();
+                if (Character.isWhitespace(c)) {
+                    return Boolean.TRUE;
+                }
+                switch (c) {
+                case 0:
+                    throw syntaxError("Unterminated string");
+                case '<':
+                case '>':
+                case '/':
+                case '=':
+                case '!':
+                case '?':
+                case '"':
+                case '\'':
+                    back();
+                    return Boolean.TRUE;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * <pre>{@code
+     * Get the next XML Token. These tokens are found inside of angle
+     * brackets. It may be one of these characters: / > = ! ? or it
+     * may be a string wrapped in single quotes or double quotes, or it may be a
+     * name.
+     * }</pre>
+     * @return a String or a Character.
+     * @throws JSONException If the XML is not well formed.
+     */
+    public Object nextToken() throws JSONException {
+        char c;
+        char q;
+        StringBuilder sb;
+        do {
+            c = next();
+        } while (Character.isWhitespace(c));
+        switch (c) {
+        case 0:
+            throw syntaxError("Misshaped element");
+        case '<':
+            throw syntaxError("Misplaced '<'");
+        case '>':
+            return XML.GT;
+        case '/':
+            return XML.SLASH;
+        case '=':
+            return XML.EQ;
+        case '!':
+            return XML.BANG;
+        case '?':
+            return XML.QUEST;
+
+// Quoted string
+
+        case '"':
+        case '\'':
+            q = c;
+            sb = new StringBuilder();
+            for (;;) {
+                c = next();
+                if (c == 0) {
+                    throw syntaxError("Unterminated string");
+                }
+                if (c == q) {
+                    return sb.toString();
+                }
+                if (c == '&') {
+                    sb.append(nextEntity(c));
+                } else {
+                    sb.append(c);
+                }
+            }
+        default:
+
+// Name
+
+            sb = new StringBuilder();
+            for (;;) {
+                sb.append(c);
+                c = next();
+                if (Character.isWhitespace(c)) {
+                    return sb.toString();
+                }
+                switch (c) {
+                case 0:
+                    return sb.toString();
+                case '>':
+                case '/':
+                case '=':
+                case '!':
+                case '?':
+                case '[':
+                case ']':
+                    back();
+                    return sb.toString();
+                case '<':
+                case '"':
+                case '\'':
+                    throw syntaxError("Bad character in a name");
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Skip characters until past the requested string.
+     * If it is not found, we are left at the end of the source with a result of false.
+     * @param to A string to skip past.
+     */
+    // The Android implementation of JSONTokener has a public method of public void skipPast(String to)
+    // even though ours does not have that method, to have API compatibility, our method in the subclass
+    // should match.
+    public void skipPast(String to) {
+        boolean b;
+        char c;
+        int i;
+        int j;
+        int offset = 0;
+        int length = to.length();
+        char[] circle = new char[length];
+
+        /*
+         * First fill the circle buffer with as many characters as are in the
+         * to string. If we reach an early end, bail.
+         */
+
+        for (i = 0; i < length; i += 1) {
+            c = next();
+            if (c == 0) {
+                return;
+            }
+            circle[i] = c;
+        }
+
+        /* We will loop, possibly for all of the remaining characters. */
+
+        for (;;) {
+            j = offset;
+            b = true;
+
+            /* Compare the circle buffer with the to string. */
+
+            for (i = 0; i < length; i += 1) {
+                if (circle[j] != to.charAt(i)) {
+                    b = false;
+                    break;
+                }
+                j += 1;
+                if (j >= length) {
+                    j -= length;
+                }
+            }
+
+            /* If we exit the loop with b intact, then victory is ours. */
+
+            if (b) {
+                return;
+            }
+
+            /* Get the next character. If there isn't one, then defeat is ours. */
+
+            c = next();
+            if (c == 0) {
+                return;
+            }
+            /*
+             * Shove the character in the circle buffer and advance the
+             * circle offset. The offset is mod n.
+             */
+            circle[offset] = c;
+            offset += 1;
+            if (offset >= length) {
+                offset -= length;
+            }
+        }
+    }
+}

+ 66 - 0
src/main/java/com/usky/xml/XMLXsiTypeConverter.java

@@ -0,0 +1,66 @@
+package com.usky.xml;
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/**
+ * Type conversion configuration interface to be used with xsi:type attributes.
+ * <pre>
+ * <b>XML Sample</b>
+ * {@code
+ *      <root>
+ *          <asString xsi:type="string">12345</asString>
+ *          <asInt xsi:type="integer">54321</asInt>
+ *      </root>
+ * }
+ * <b>JSON Output</b>
+ * {@code
+ *     {
+ *         "root" : {
+ *             "asString" : "12345",
+ *             "asInt": 54321
+ *         }
+ *     }
+ * }
+ *
+ * <b>Usage</b>
+ * {@code
+ *      Map<String, XMLXsiTypeConverter<?>> xsiTypeMap = new HashMap<String, XMLXsiTypeConverter<?>>();
+ *      xsiTypeMap.put("string", new XMLXsiTypeConverter<String>() {
+ *          &#64;Override public String convert(final String value) {
+ *              return value;
+ *          }
+ *      });
+ *      xsiTypeMap.put("integer", new XMLXsiTypeConverter<Integer>() {
+ *          &#64;Override public Integer convert(final String value) {
+ *              return Integer.valueOf(value);
+ *          }
+ *      });
+ * }
+ * </pre>
+ * @author kumar529
+ * @param <T> return type of convert method
+ */
+public interface XMLXsiTypeConverter<T> {
+    T convert(String value);
+}

+ 72 - 0
src/main/resources/application.yml

@@ -0,0 +1,72 @@
+#开发环境
+server:
+  port: 8082
+spring:
+  redis:
+    lettuce:
+      pool:
+        max-active: 8
+    password: uskyredis
+    port: 6379
+    host: 47.111.81.118
+  jackson:
+    time-zone: GMT+8
+  application:
+    name: JX_Cover
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    druid:
+      initialSize: 5
+      minIdle: 5
+      maxActive: 20
+      maxWait: 60000
+      timeBetweenEvictionRunsMillis: 60000
+      minEvictableIdleTimeMillis: 300000
+      validationQuery: SELECT 1 FROM DUAL
+      testWhileIdle: true
+      testOnBorrow: false
+      testOnReturn: false
+      poolPreparedStatements: true
+      aopPatterns: com.usky.*
+      filters: stat,slf4j
+      maxPoolPreparedStatementPerConnectionSize: 20
+      useGlobalDataSourceStat: true
+      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
+      db-type: com.alibaba.druid.pool.DruidDataSource
+      url: jdbc:mysql://47.111.81.118:3306/sd_party_school?useunicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
+      username: root
+      password: Yt2021
+  jpa:
+    database-platform: org.hibernate.dialect.MySQLDialect
+    hibernate:
+      ddl-auto: update
+    database: mysql
+    show-sql: true
+    open-in-view: false #懒加载时使用,建议false
+
+swagger:
+  enable: true
+
+cron:
+  #测试每隔十秒执行一次定时任务
+  test: 0/10 * * * * ?
+  #每一小时入库一次视频数据
+  rk: 0 0 0/12 * * ?
+  #请求每十秒执行一次
+  req: 0/10 * * * * ?
+sms:
+  accessKeyID: LTAI5tCTtstCUtCPBrmFAUCk # 你自己的accessKeyId
+  accessKeySecret: V6X2hMOEbepkTPdjeqjESs3Pc0jgPk # 你自己的AccessKeySecret
+  signName: 上海永天科技股份有限公司 # 签名名称
+  #verifyCodeTemplate: SMS_164095840 # 模板名称
+  verifyCodeTemplate: SMS_220385321 # 模板名称
+  domain: dysmsapi.aliyuncs.com # 域名
+  action: SendSMS # API类型,发送短信
+  version: 2017-05-25 # API版本,固定值
+  regionID: cn-hangzhou # 区域id
+
+
+
+
+
+

BIN
src/main/resources/favicon.ico


+ 121 - 0
src/main/resources/logback-spring.xml

@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="60 seconds" debug="false">
+    <springProperty scop="context" name="spring.application.name" source="spring.application.name" defaultValue=""/>
+    <!-- 定义日志文件 输出位置 -->
+    <property name="log.path" value="./log/${spring.application.name}" />
+    <!-- 彩色日志格式 -->
+    <property name="CONSOLE_LOG_PATTERN"
+              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
+    <!-- 彩色日志依赖的渲染类 -->
+    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
+    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
+    <conversionRule conversionWord="wEx"
+                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
+    <!-- 动态日志级别 -->
+    <jmxConfigurator />
+    <!-- 日志最大的历史 30天 -->
+    <property name="maxHistory" value="30" />
+    <!-- ConsoleAppender 控制台输出日志 -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>
+                <!-- 设置日志输出格式 -->
+                <!--%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n-->
+                ${CONSOLE_LOG_PATTERN}
+            </pattern>
+        </encoder>
+    </appender>
+    <!-- ERROR级别日志 -->
+    <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 RollingFileAppender -->
+    <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 过滤器,只记录WARN级别的日志 -->
+        <!-- 果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 设置过滤级别 -->
+            <level>ERROR</level>
+            <!-- 用于配置符合过滤条件的操作 -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 用于配置不符合过滤条件的操作 -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+        <!-- 最常用的滚动策略,它根据时间来制定滚动策略.既负责滚动也负责出发滚动 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!--日志输出位置 可相对、和绝对路径 -->
+            <fileNamePattern>
+                ${log.path}/error/%d{yyyy-MM}/%d{yyyy-MM-dd}-error.log
+            </fileNamePattern>
+            <!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件假设设置每个月滚动,且<maxHistory>是6, 则只保存最近6个月的文件,删除之前的旧文件。注意,删除旧文件是,那些为了归档而创建的目录也会被删除 -->
+            <maxHistory>${maxHistory}</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>
+                <!-- 设置日志输出格式 -->
+                %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n
+            </pattern>
+        </encoder>
+    </appender>
+    <!-- WARN级别日志 appender -->
+    <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 过滤器,只记录WARN级别的日志 -->
+        <!-- 果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 设置过滤级别 -->
+            <level>WARN</level>
+            <!-- 用于配置符合过滤条件的操作 -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 用于配置不符合过滤条件的操作 -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!--日志输出位置 可相对、和绝对路径 -->
+            <fileNamePattern>${log.path}/warn/%d{yyyy-MM}/%d{yyyy-MM-dd}-warn.log</fileNamePattern>
+            <maxHistory>${maxHistory}</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
+        </encoder>
+    </appender>
+    <!-- INFO级别日志 appender -->
+    <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>INFO</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${log.path}/info/%d{yyyy-MM}/%d{yyyy-MM-dd}-info.log</fileNamePattern>
+            <maxHistory>${maxHistory}</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
+        </encoder>
+    </appender>
+    <!-- DEBUG级别日志 appender -->
+    <appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>DEBUG</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${log.path}/debug/%d{yyyy-MM}/%d{yyyy-MM-dd}-debug.log</fileNamePattern>
+            <maxHistory>${maxHistory}</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
+        </encoder>
+    </appender>
+    <!-- 级别从高到低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->
+    <!-- root级别 DEBUG -->
+    <root>
+        <!-- 打印debug级别日志及以上级别日志 -->
+        <level value="info" />
+        <!-- 控制台输出 -->
+        <appender-ref ref="console" />
+        <!-- 文件输出 -->
+        <appender-ref ref="ERROR" />
+        <appender-ref ref="INFO" />
+        <appender-ref ref="WARN" />
+        <appender-ref ref="DEBUG" />
+    </root>
+</configuration>

+ 10 - 0
src/main/resources/mqtt-spring.properties

@@ -0,0 +1,10 @@
+mqtt.client-id-prefix=dev
+#mqtt.url=tcp://www.iiotsaas.com:1883
+#mqtt.url=tcp://47.98.201.73:1883
+mqtt.url=tcp://124.71.175.91:1883
+mqtt.username=wjzn2021
+mqtt.password=wjzn2021
+mqtt.keep-alive-interval=60
+mqtt.completionTimeout=5000
+#mqtt.sub-topics=/edge/usky/sn/#,usky
+mqtt.sub-topics=usky

+ 13 - 0
src/main/test/java/com/usky/controller/Basetest.java

@@ -0,0 +1,13 @@
+package com.usky.controller;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.junit.Assert.*;
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public interface Basetest {
+
+}

+ 111 - 0
src/main/test/java/com/usky/controller/TestTest.java

@@ -0,0 +1,111 @@
+package com.usky.controller;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.*;
+
+public class TestTest implements Basetest {
+
+    @Test
+    public void resultTest() {
+        Runnable r1 = new Runnable() {
+            @Override
+            public void run() {
+                System.out.println("Hello World!");
+            }
+        };
+        r1.run();
+        System.out.println("----------Lambda格式-----------");
+        //Lambda表达式
+        Runnable r2 = () -> System.out.println("Hello World!");
+        r2.run();
+
+    }
+
+    @Test
+    public  void test2(){
+        Consumer<String> con = new Consumer<String>() {
+            @Override
+            public void accept(String s) {
+                System.out.println(s);
+            }
+        };
+        con.accept("Hello World!");
+        System.out.println("-------Lambda格式----------");
+
+        Consumer<String> con1 = (String s) -> System.out.println(s);
+        con1.accept("Hello World!...");
+
+    }
+
+    @Test
+    public  void test3(){
+        Consumer<String> con = new Consumer<String>() {
+            @Override
+            public void accept(String s) {
+                System.out.println(s);
+            }
+        };
+        con.accept("Hello World!");
+        System.out.println("-------Lambda格式----------");
+        //类型省略
+        Consumer<String> con1 = (s) -> System.out.println(s);
+        con1.accept("Hello World!...");
+    }
+
+
+  // 需求 : 现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:
+  //         1.	第一个队伍只要名字为3个字的成员姓名;
+  //         2.	第一个队伍筛选之后只要前3个人;
+  //         3.	第二个队伍只要姓张的成员姓名;
+  //         4.	第二个队伍筛选之后不要前2个人;
+  //         5.	将两个队伍合并为一个队伍;
+  //         6.	根据姓名创建Person对象;
+  //         7.	打印整个队伍的Person对象信息。
+
+
+
+    @Test
+    public  void test5(){
+
+        List<String> one = new ArrayList<>();
+        one.add("迪丽热巴");
+        one.add("宋远桥");
+        one.add("苏星河");
+        one.add("老子");
+        one.add("庄子");
+        one.add("孙子");
+        one.add("洪七公");
+
+        List<String> two = new ArrayList<>();
+        two.add("古力娜扎");
+        two.add("张无忌");
+        two.add("张三丰");
+        two.add("赵丽颖");
+        two.add("张二狗");
+        two.add("张天爱");
+        two.add("张三");
+
+
+        Stream<String> stringStream = one.stream().filter(s -> s.length() == 3);
+        List<String> collect = stringStream.collect(Collectors.toList());
+        System.out.println("collect = " + collect);
+
+    }
+
+
+
+
+    
+
+
+
+
+
+}

+ 17 - 0
src/main/test/java/com/usky/controller/mqtt/BaseTest.java

@@ -0,0 +1,17 @@
+package com.usky.controller.mqtt;
+
+import com.usky.Application;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * @author laowo
+ * @version v1.0
+ * @date 2021/6/24 10:17
+ * @description TODO
+ **/
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Application.class)
+public class BaseTest {
+}

+ 18 - 0
src/main/test/java/com/usky/controller/mqtt/MqttTestControllerTest.java

@@ -0,0 +1,18 @@
+package com.usky.controller.mqtt;
+
+import com.usky.utils.RedisUtil;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Map;
+
+public class MqttTestControllerTest extends BaseTest {
+    @Autowired
+    private RedisUtil redisUtil;
+
+    @Test
+    public void redisTest() {
+        Map<String,Object> sn = (Map<String, Object>) redisUtil.get("sn");
+        System.out.println("sn = " + sn);
+    }
+}

+ 24 - 0
src/main/test/java/com/usky/controller/top/TopConfigControllerTest.java

@@ -0,0 +1,24 @@
+package com.usky.controller.top;
+
+import com.usky.controller.Basetest;
+import com.usky.entity.top.vo.TopConfigVO;
+import com.usky.service.top.TopService.TopService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.List;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class TopConfigControllerTest implements Basetest {
+    @Autowired
+    private TopService topService;
+    @Test
+    public void listTopConfig() {
+        List<TopConfigVO> topConfigVOS = topService.queryTopConfig("SD");
+        System.out.println("topConfigVOS = " + topConfigVOS);
+    }
+}