diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..d80cd146d --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Maven # +target/ + +# IDEA # +.idea/ +*.iml + +# Eclipse # +.settings/ +.classpath +.project +bin/ diff --git a/.travis.yml b/.travis.yml index 45f3cd645..a1407b9f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ -language: java -jdk: - - oraclejdk8 - - oraclejdk7 - - openjdk7 -script: - mvn test -Dmaven.test.skip=false -fae \ No newline at end of file +language: java +jdk: + - oraclejdk8 +script: + - mvn install -Dmaven.test.skip=true + diff --git a/LAST_VERSION.md b/LAST_VERSION.md new file mode 100644 index 000000000..f5eb7fc09 --- /dev/null +++ b/LAST_VERSION.md @@ -0,0 +1,76 @@ +# blade last version + +如果在maven仓库中下载不到最新版本的依赖,请添加maven snapshots仓库 + +```xml + + + oss-snapshots + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + +``` + +和`dependencies`相同级别 + + +## [blade-kit](http://search.maven.org/#search%7Cga%7C1%7Cblade-kit) +```xml + +    com.bladejava +    blade-kit +    1.4.0-alpha + +``` + +## [blade-core](http://search.maven.org/#search%7Cga%7C1%7Cblade-core) +```xml + +    com.bladejava +    blade-core +    1.7.0-alpha + +``` + +## [blade-embed-jetty](http://search.maven.org/#search%7Cga%7C1%7Cblade-embed-jetty) +```xml + +    com.bladejava +    blade-embed-jetty +    0.0.7 + +``` + +## [blade-template-jetbrick](http://search.maven.org/#search%7Cga%7C1%7Cblade-template-jetbrick) +```xml + +    com.bladejava +    blade-template-jetbrick +    0.0.7 + +``` + +## [blade-jdbc](http://search.maven.org/#search%7Cga%7C1%7Cblade-jdbc) +```xml + +    com.bladejava +    blade-jdbc +    0.1.4-alpha + +``` + +## [blade-patchca](http://search.maven.org/#search%7Cga%7C1%7Cblade-patchca) +```xml + +    com.bladejava +    blade-patchca +    1.0.5 + +``` + diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..753842b67 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index e008e5718..d6b455bb3 100644 --- a/README.md +++ b/README.md @@ -1,159 +1,176 @@ -#Blade -[![简洁强大的JavaWeb框架](http://i1.tietuku.com/0c4b9726253b6268.png "简洁强大的JavaWeb框架")](http://bladejava.com) +[![](https://dn-biezhi.qbox.me/LOGO_BIG.png)](http://bladejava.com) -[English](https://github.com/biezhi/blade/blob/master/README_EN.md) +[Quick Start](https://bladejava.com/docs)  |  [Demo Project](https://github.com/blade-samples)  |  [Contribute](https://bladejava.com/docs/appendix/contribute)  |  [Donate]()  |  [FAQ](https://bladejava.com/docs/faqs) |  [中文说明](https://github.com/biezhi/blade/blob/master/README_CN.md) -[![@biezhi on weibo](https://img.shields.io/badge/weibo-%40biezhi-red.svg)](http://weibo.com/u/5238733773) -[![Hex.pm](https://img.shields.io/hexpm/l/plug.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) -[![Build Status](https://api.travis-ci.org/biezhi/blade.svg?branch=master)](https://travis-ci.org/biezhi/blade) -[![Circle CI](https://circleci.com/gh/biezhi/blade/tree/master.svg?style=svg)](https://circleci.com/gh/biezhi/blade/tree/master) -[![release](https://img.shields.io/maven-central/v/com.bladejava/blade.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.bladejava%22) +[![Build Status](https://img.shields.io/travis/biezhi/blade.svg?style=flat-square)](https://travis-ci.org/biezhi/blade) +[![maven-central](https://img.shields.io/maven-central/v/com.bladejava/blade-core.svg?style=flat-square)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.bladejava%22) +[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg?style=flat-square)](https://www.apache.org/licenses/LICENSE-2.0.html) +[![Gitter](https://badges.gitter.im/biezhi/blade.svg)](https://gitter.im/biezhi/blade?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) -**blade** 是一个简洁强大的web框架,它内置了`IOC`管理,拦截器配置,`REST API`开发等众多主流web特性,集成了模板引擎,缓存插件,数据库操作,邮件发送等常用功能,简洁的源码值得你阅读和学习。如果你喜欢,欢迎[Star and Fork](https://github.com/biezhi/blade) ! -## Blade特性 +## What Is Blade? -* 简洁的MVC & 拦截器 -* REST风格API -* 注解方式开发 -* 微内核IOC容器 -* 实用工具类 -* 模板引擎支持 -* 支持JDK1.6+ -* 内置Jetty启动 -* 插件扩展机制 -* ... +Blade is a lightweight MVC framework. It is based on the principles of simplicity and elegance. +If you like it, please [star](https://github.com/biezhi/blade/stargazers) / [fork](https://github.com/biezhi/blade). Thx :blush: -## 快速入门 -第一步、用maven构建一个webapp,加入blade的依赖,推荐获取[最新版本](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.bladejava%22) +## Features + +* [x] Lightweight: the code is simple and the structure is clear +* [x] Modular (you can choose which components to use) +* [x] RESTful style routing interface +* [x] Template engine support +* [x] Run with jar file +* [x] JDK8+ + +## Overview + +* Simplicity: The design is simple, easy to understand and doesn't introduce many layers between you and the standard library. The goal of this project is that the users should be able to understand the whole framework in a single day. +* Elegance: `blade` supports the RESTful style routing interface, has no invasive interceptors and provides the writing of DSL grammar. + +## Get Start + +To get started, first [include the Blade library](http://bladejava.com/docs/intro/getting_start) : + +Grab via `Maven`: ```xml com.bladejava - blade - x.x.x + blade-core + 1.7.0-alpha + + + com.bladejava + blade-embed-jetty + 0.0.7 ``` - -第二步、在`web.xml`中配置Blade核心过滤器并设置你的初始化类,你也可以不配置(使用jetty启动) - -```xml - - Archetype Created Web Application - - BladeFilter - blade.BladeFilter - - applicationClass - blade.sample.App - - - - - BladeFilter - /* - - - +or `Gradle`: +```sh +compile 'com.bladejava:blade-core:1.7.0-alpha' +compile 'com.bladejava:blade-embed-jetty:0.0.7' ``` -第三步、编写App.java和路由文件,下面是一个示例 +Create `Main` method like this: ```java -public class App extends BladeApplication{ +public static void main(String[] args) { + $().get("/", (request, response) -> { + response.html("

Hello blade!

"); + }).start(Application.class); +} +``` - Logger logger = Logger.getLogger(App.class); - @Override - public void init() { - // 设置路由、拦截器包所在包 - Blade.defaultRoute("blade.sample"); - } - +Run it and point your browser to [http://localhost:9000](http://localhost:9000). There you go, you've just created your first Blade app! + +## API Example + +```java +public static void main(String[] args) { + $().get("/user/21", getxxx); + $().post("/save", postxxx); + $().delete("/del/21", deletexxx); + $().put("/put", putxxx); } ``` - +## REST URL Parameters + ```java -@Path("/") -public class Hello { +public static void main(String[] args) { + $().get("/user/:uid", (request, response) -> { + Integer uid = request.paramAsInt("uid"); + response.text("uid : " + uid); + }); - @Route("hello") - public String hello() { - System.out.println("hello"); - return "hello.jsp"; - } - - @Route(value = "post", method = HttpMethod.POST) - public void post(Request request) { - String name = request.query("name"); - System.out.println("name = " + name); - } + $().get("/users/:uid/post/:pid", (request, response) -> { + Integer uid = request.paramAsInt("uid"); + Integer pid = request.paramAsInt("pid"); + String msg = "uid = " + uid + ", pid = " + pid; + response.text(msg); + }); - @Route("users/:name") - public ModelAndView users(Request request, Response response) { - System.out.println("users"); - String name = request.pathParam(":name"); - - ModelAndView modelAndView = new ModelAndView("users"); - modelAndView.add("name", name); - return modelAndView; - } + $().start(Application.class); +} +``` - @Route("index") - public String index(Request request) { - request.attribute("name", "jack"); - return "index.jsp"; +## Form URL Parameters + +```java +public static void main(String[] args) { + $().get("/user", (request, response) -> { + Integer uid = request.queryAsInt("uid"); + response.text("uid : " + uid); + }).start(Application.class); +} +``` + +## Upload File + +```java +public void upload_img(@MultipartParam FileItem fileItem){ + if(null != fileItem){ + File file = fileItem.getFile(); + String fileRealPath = "your upload file path!"; + nioTransferCopy(file, fileRealPath); } - } ``` + +## Route Config File + +`route.conf` + +```sh +GET / IndexRoute.home +GET /signin IndexRoute.show_signin +POST /signin IndexRoute.signin +GET /signout IndexRoute.signout +POST /upload_img UploadRoute.upload_img +``` + +## Route Intercept + +```java +public static void main(String[] args) { + $().before("/.*", (request, response) -> { + System.out.println("before..."); + }).start(Application.class); +} +``` + +You may refer to these examples for additional guidance: + ++ [Hello Blade](https://github.com/blade-samples/hello) ++ [BBS WebSite](https://github.com/junicorn/java-china) ++ [Doc Service](https://github.com/biezhi/grice) ++ [More Examples](https://github.com/blade-samples) + -OK,这一切看起来多么的简单,查阅使用指南更多现成的例子供你参考: - -+ [API docs](http://bladejava.com/apidocs/) -+ [使用指南](http://bladejava.com/doc/cn/index.html) (完善中...) -+ [一些例子](https://github.com/bladejava) - -### 计划 - 1. 完善文档 - 2. 完成用`blade`开发的单用户博客系统 - 3. 编写英文文档 - 4. 优化代码性能 - -## 更新日志 - -### v1.1.3 - 1. 修复sql2o更新Bug - 2. 去除blade-kit无用类 - 3. 添加邮件支持 - 4. 添加程序计时支持 - 5. 添加http网络请求支持 - 6. 优化内置日志输出 - 7. 添加定时任务支持 - -### v1.1.0 - 1. 去除对外公开的多余方法展示 - 2. 添加`Blade.run()`方式运行jetty - 3. 添加`Blade.register()`方法注册bean对象 - 4. 优化IOC对象管理 - 5. 优化底层IO - 6. 简化插件扩展 - 7. 拦截器路由匹配分离 - 8. 修复jetty在多maven环境下运行bug - 9. 添加初始化监听context - 10. 优化文件上传 - 11. 优化路由匹配 - 12. 添加方法执行监测 - 13. 添加缓存支持 - -### v1.0.0 - 第一个稳定版本发布 - -## 开源协议 -Blade框架基于 [Apache2 License](http://www.apache.org/licenses/LICENSE-2.0.html) - -## 联系我 -Mail: biezhi.me#gmail.com - -Java交流群: [1013565](http://shang.qq.com/wpa/qunwpa?idkey=932642920a5c0ef5f1ae902723c4f168c58ea63f3cef1139e30d68145d3b5b2f) +## Update + +[update log](https://github.com/biezhi/blade/blob/master/UPDATE_LOG.md) + +## Contact + +- Blog:[https://biezhi.me](https://biezhi.me) +- Mail: biezhi.me@gmail.com + +## Contributor + +Thank you very much for the developers to help in the project, if you are willing to contribute, welcome! + +- [mfarid](https://github.com/mfarid) +- [daimajia](https://github.com/daimajia) +- [shenjie1993](https://github.com/shenjie1993) +- [sumory](https://github.com/sumory) +- [udaykadaboina](https://github.com/udaykadaboina) +- [SyedWasiHaider](https://github.com/SyedWasiHaider) +- [Awakens](https://github.com/Awakens) +- [shellac](https://github.com/shellac) +- [SudarAbisheck](https://github.com/SudarAbisheck) + +## Licenses + +Please see [Apache License](LICENSE) diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 000000000..64d87f449 --- /dev/null +++ b/README_CN.md @@ -0,0 +1,181 @@ + +[![](https://dn-biezhi.qbox.me/LOGO_BIG.png)](http://bladejava.com) + +[开始使用](https://bladejava.com/docs)  |  [示例项目](https://github.com/blade-samples)  |  [贡献代码](https://bladejava.com/docs/appendix/contribute)  |  [捐赠]()  |  [FAQ](https://bladejava.com/docs/faqs) |  [English](https://github.com/biezhi/blade/blob/master/README.md) + +[![Build Status](https://img.shields.io/travis/biezhi/blade.svg?style=flat-square)](https://travis-ci.org/biezhi/blade) +[![maven-central](https://img.shields.io/maven-central/v/com.bladejava/blade-core.svg?style=flat-square)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.bladejava%22) +[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg?style=flat-square)](https://www.apache.org/licenses/LICENSE-2.0.html) +[![@biezhi on weibo](https://img.shields.io/badge/weibo-%40biezhi-red.svg?style=flat-square)](http://weibo.com/u/5238733773) + +## Blade是什么? + +Blade 是一款轻量级的MVC框架, 重新定义JavaWeb开发,它拥有简洁的代码,优雅的设计。 + +如果你觉得不错, 欢迎 [Star](https://github.com/biezhi/blade/stargazers) / [Fork](https://github.com/biezhi/blade), 谢谢 :blush: + +## 特性 + +* [x] 轻量级, 代码简洁,结构清晰,更容易开发 +* [x] 模块化(你可以选择使用哪些组件) +* [x] Restful风格的路由接口 +* [x] 多种模板引擎支持 +* [x] 支持以jar文件发布运行 +* [x] JDK8以上 + +## 概述 + +* 简洁的:框架设计简单,容易理解,不依赖于更多第三方库。Blade框架目标让用户在一天内理解并使用。 +* 优雅的:`Blade` 支持 REST 风格路由接口, 提供 DSL 语法编写,无侵入式的拦截器。 +* 易部署:支持 `maven` 打成 `jar` 包直接运行。 + +## 快速入门 + +开始之前,首先 [引入Blade的库文件](http://bladejava.com/docs/intro/getting_start) : + +`Maven` 配置: + +```xml + + com.bladejava + blade-core + 1.7.0-alpha + + + com.bladejava + blade-embed-jetty + 0.0.7 + +``` + +或者 `Gradle`: + +```sh +compile 'com.bladejava:blade-core:1.7.0-alpha' +compile 'com.bladejava:blade-embed-jetty:0.0.7' +``` + +编写 `Main`函数: + +```java +public static void main(String[] args) { + $().get("/", (request, response) -> { + response.html("

Hello blade!

"); + }).start(Application.class); +} +``` + +用浏览器打开 http://localhost:9000 这样就可以看到第一个Blade应用了! + +## API示例 + +```java +public static void main(String[] args) { + $().get("/user/21", getxxx); + $().post("/save", postxxx); + $().delete("/del/21", deletexxx); + $().put("/put", putxxx); +} +``` + +## REST URL参数获取 + +```java +public static void main(String[] args) { + $().get("/user/:uid", (request, response) -> { + Integer uid = request.paramAsInt("uid"); + response.text("uid : " + uid); + }); + + $().get("/users/:uid/post/:pid", (request, response) -> { + Integer uid = request.paramAsInt("uid"); + Integer pid = request.paramAsInt("pid"); + String msg = "uid = " + uid + ", pid = " + pid; + response.text(msg); + }); + + $().start(Application.class); +} +``` + +## 表单参数获取 + +```java +public static void main(String[] args) { + $().get("/user", (request, response) -> { + Integer uid = request.queryAsInt("uid"); + response.text("uid : " + uid); + }).start(Application.class); +} +``` + +## 上传文件 + +```java +public void upload_img(@MultipartParam FileItem fileItem){ + if(null != fileItem){ + File file = fileItem.getFile(); + String fileRealPath = "your upload file path!"; + nioTransferCopy(file, fileRealPath); + } +} +``` + +## 配置文件路由 + +`route.conf` + +```sh +GET / IndexRoute.home +GET /signin IndexRoute.show_signin +POST /signin IndexRoute.signin +GET /signout IndexRoute.signout +POST /upload_img UploadRoute.upload_img +``` + +## 路由拦截 + +```java +public static void main(String[] args) { + $().before("/.*", (request, response) -> { + System.out.println("before..."); + }).start(Application.class); +} +``` + + +这一切看起来多么的简单,不过上面的功能可是冰山一角,查看文档和示例项目有更多惊喜: + ++ [hello工程](https://github.com/blade-samples/hello) ++ [论坛程序](https://github.com/junicorn/java-china) ++ [文档服务](https://github.com/biezhi/grice) ++ [更多例子](https://github.com/blade-samples) + + +## 更新日志 + +[更新日志](https://github.com/biezhi/blade/blob/master/UPDATE_LOG.md) + +## 联系我 + +- Blog:[https://biezhi.me](https://biezhi.me) +- Mail: biezhi.me#gmail.com +- Java交流群: [1013565](http://shang.qq.com/wpa/qunwpa?idkey=932642920a5c0ef5f1ae902723c4f168c58ea63f3cef1139e30d68145d3b5b2f) + +## 贡献 + +非常感谢下面的开发者朋友对本项目的帮助,如果你也愿意提交PR,欢迎你! + +- [mfarid](https://github.com/mfarid) +- [daimajia](https://github.com/daimajia) +- [shenjie1993](https://github.com/shenjie1993) +- [sumory](https://github.com/sumory) +- [udaykadaboina](https://github.com/udaykadaboina) +- [SyedWasiHaider](https://github.com/SyedWasiHaider) +- [Awakens](https://github.com/Awakens) +- [shellac](https://github.com/shellac) +- [SudarAbisheck](https://github.com/SudarAbisheck) + +## 开源协议 + +请查看 [Apache License](LICENSE) diff --git a/README_EN.md b/README_EN.md deleted file mode 100644 index d134c612f..000000000 --- a/README_EN.md +++ /dev/null @@ -1,161 +0,0 @@ -#Blade - -[![a concise and powerful web development framework](http://i1.tietuku.com/0c4b9726253b6268.png "a concise and powerful web development framework")](http://bladejava.com) - -[中文](https://github.com/biezhi/blade/blob/master/README.md) - -[![@biezhi on weibo](https://img.shields.io/badge/weibo-%40biezhi-red.svg)](http://weibo.com/u/5238733773) -[![Hex.pm](https://img.shields.io/hexpm/l/plug.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) -[![Build Status](https://api.travis-ci.org/biezhi/blade.svg?branch=master)](https://travis-ci.org/biezhi/blade) -[![Circle CI](https://circleci.com/gh/biezhi/blade/tree/master.svg?style=svg)](https://circleci.com/gh/biezhi/blade/tree/master) -[![release](https://img.shields.io/maven-central/v/com.bladejava/blade.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.bladejava%22) - -## Introduction - -**blade** Is a concise and powerful web development framework, it is built into the `IOC` administration, the interceptor configuration, `REST API` development and so on many mainstream web features, integrate the template engine, a cache plug-in, database operations, commonly used functions such as email, concise source deserves your reading. If you like it, can be `Star or Fork`, thanks! - -## Features - -* Simple MVC & Interceptor -* REST API -* Annotation way development -* The microkernel IOC container -* Utility class -* A template engine support -* Support JDK1.6 + -* Jetty is started -* Plug-in extension mechanism -* ... - -## Quick start -First. Use maven to build a webapp, join dependency on the blade,Recommended for the [latest version](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.bladejava%22) - -```xml - - com.bladejava - blade - x.x.x - -``` - -Second. Configuration in the `web.xml` Blade core filter initialization and set your class, and you can also not configuration(using jetty start) - -```xml - - Archetype Created Web Application - - BladeFilter - blade.BladeFilter - - applicationClass - blade.sample.App - - - - - BladeFilter - /* - - - -``` - -Third. Write App.java and routing file, here is an example - -```java -public class App extends BladeApplication{ - - Logger logger = Logger.getLogger(App.class); - @Override - public void init() { - // Set up routing and interceptor bag in bag - Blade.defaultRoute("blade.sample"); - } - -} -``` - - -```java -@Path("/") -public class Hello { - - @Route("hello") - public String hello() { - System.out.println("hello"); - return "hello.jsp"; - } - - @Route(value = "post", method = HttpMethod.POST) - public void post(Request request) { - String name = request.query("name"); - System.out.println("name = " + name); - } - - @Route("users/:name") - public ModelAndView users(Request request, Response response) { - System.out.println("users"); - String name = request.pathParam(":name"); - - ModelAndView modelAndView = new ModelAndView("users"); - modelAndView.add("name", name); - return modelAndView; - } - - @Route("index") - public String index(Request request) { - request.attribute("name", "jack"); - return "index.jsp"; - } - -} -``` - -OK, all this may seem simple, refer to the guidelines for use more ready-made examples for your reference: - -+ [API Docs](http://bladejava.com/apidocs/) -+ [Use Guide](http://bladejava.com/doc/cn/index.html) (The ongoing...) -+ [Some Examples](https://github.com/bladejava) - -### Plan - 1. Perfect the documents - 2. Complete with `blade` single-user blog system development - 3. Write English document - 4. Optimize the code performance - -### Update - -### v1.1.3 - 1. Update Bug fix sql2o - 2. Remove the blade-kit useless class - 3. Add email support - 4. Add a program timing - 5. Add the HTTP request support network - 6. Optimization of the built-in logging output - 7. Add tasks to support regularly - -### v1.1.0 - 1. Remove excess public methods - 2. Add the `Blade.run()` run jetty - 3. Add the `Blade.register()` method register bean object - 4. Optimize the ioc object management - 5. Add initialization to monitor the context - 6. Optimize the underlying IO - 7. Simplify the plug-in extension - 8. Matching vehicle routing separation - 9. Repair jetty in running maven environment more bugs - 10. Optimize the file upload - 11. Optimized matching routing - 12. Add methods perform monitoring - 13. Add cache support - -### v1.0.0 - The first stable release - -## licenses -Blade Framework based on the [Apache2 License](http://www.apache.org/licenses/LICENSE-2.0.html) - -## Contact -Mail: biezhi.me#gmail.com - -QQ Group: [1013565](http://shang.qq.com/wpa/qunwpa?idkey=932642920a5c0ef5f1ae902723c4f168c58ea63f3cef1139e30d68145d3b5b2f) \ No newline at end of file diff --git a/UPDATE_LOG.md b/UPDATE_LOG.md new file mode 100644 index 000000000..b75ea5156 --- /dev/null +++ b/UPDATE_LOG.md @@ -0,0 +1,130 @@ +# Blade 更新日志 + +### v1.6.7 + 1. 支持自定义filter, servlet + 2. 支持websocket + 3. 添加启动监听器 + 4. 配置类自动注入 + 5. 增强路由注解功能 + + +### v1.6.6 + 1. 支持内置jetty服务器启动 + 2. 支持@JSON和@RestController + 3. 支持自定义Banner启动 + 4. 支持AOP + 5. 自动查找基础包路径 + 6. 增强方法参数自动匹配 + 7. 支持可扩展配置, 将启动类和配置分离 + 8. 修复异常提示不全 + 9. 重构代码结构 + +### v1.6.3 + 1. 简化`blade-kit`配置类 + 2. 修改 `@Path` 为 `@Controller` + 3. 去除无用类和注解 + 4. 支持 `java -jar xxx.jar` 方式执行 + 5. 分离内置Server实现 + 6. 修复类加载不到bug + +### v1.6.1 + 1. 优化csrf防御代码 + 2. 可自定义xss防御实现 + 3. 修复自定义404,500页面配置 + 4. 修复queryAsInt报错 + +### v1.6.0 + 1. 更简化的配置(提供默认配置) + 2. 支持非web应用开发 + 3. 优化代码 + 4. 重构数据库操作 + 5. 内置数据库连接池 + 6. 更灵活的路由操作 + 7. 重新修订官网文档 + 8. 暂时去除servlet3x异步 + 9. 去除多余繁杂的配置,追求精简实用 + 10. 提供易扩展的IOC,路由接口 + +### v1.5.1 + 1. 修复webRoot是null + 2. 修复模板引擎路径错误 + 3. 修复控制器注入对象失败 + 4. 添加request.model表单数据转JavaBean + 5. 修复CSRF验证bug + 6. 更友好的错误输出 + 7. 静态资源返回 + +### v1.5.0 + 1. 优化路由寻址算法 + 2. 分离同步和异步请求 + 3. 优化IOC + +### v1.4.1 + 1. 去除过度设计 + 2. 添加多种路由配置 + 3. 统一渲染引擎,不在分离 + 4. 优化字符串分割 + +### v1.4.0 + 1. 添加CSRF防御 + 2. 添加XSS防御 + 3. 优化JSON解析 + +### v1.2.9 + 1. 优化路由配置 + 2. 优化数据库缓存操作 + 3. 添加插件释放资源 + 4. 支持多路由配置 + +### v1.2.8 + 1. 优化500页面,错误在后台显示 + 2. 添加Path上带后缀 + 3. 优化多表联查返回数据 + 4. 解决java多参数传递问题 + 5. 多表联查返回Map类型数据 + 6. 解决blade-sql2o连接未释放BUG + 7. 支持单次查询是否缓存设置 + 8. 精简BladeBase + +### v1.2.6 + 1. 添加多重路由配置方式 + 2. 添加函数式定义拦截器 + 3. 优化Http请求 + +### v1.2.5 + 1. 添加JSON、Properties文件配置 + 2. 优化代码性能 + 3. 去除内置jetty服务 + +### v1.2.2 + 1. `DateKit`添加获取当前unix时间戳 + 2. 修复`blade-sql2o`分页bug + 3. 修复`blade-beetl`没有存储`ModelAndView`的数据 + +### v1.2 + 1. 修复sql2o更新Bug + 2. 去除blade-kit无用类 + 3. 添加邮件支持 + 4. 添加程序计时支持 + 5. 添加http网络请求支持 + 6. 优化内置日志输出 + 7. 添加定时任务支持 + 8. 重构项目结构 + +### v1.1.x + 1. 去除对外公开的多余方法展示 + 2. 添加`Blade.run()`方式运行jetty + 3. 添加`Blade.register()`方法注册bean对象 + 4. 优化IOC对象管理 + 5. 优化底层IO + 6. 简化插件扩展 + 7. 拦截器路由匹配分离 + 8. 修复jetty在多maven环境下运行bug + 9. 添加初始化监听context + 10. 优化文件上传 + 11. 优化路由匹配 + 12. 添加方法执行监测 + 13. 添加缓存支持 + +### v1.0.0 + 第一个稳定版本发布 \ No newline at end of file diff --git a/blade-jetbrick/pom.xml b/blade-aop/pom.xml similarity index 51% rename from blade-jetbrick/pom.xml rename to blade-aop/pom.xml index b130b1e5c..0974d130e 100644 --- a/blade-jetbrick/pom.xml +++ b/blade-aop/pom.xml @@ -5,28 +5,32 @@ 4.0.0 com.bladejava - blade-root + blade 1.0 + blade-aop + ${blade-aop.version} + blade-aop + http://maven.apache.org - blade-jetbrick - jar - ${blade.version} - blade-jetbrick - https://github.com/biezhi/blade/blade-jetbrick + + UTF-8 + + + org.slf4j + slf4j-api + com.bladejava blade-core - ${blade.version} - provided + ${blade-core.version} - com.github.subchen - jetbrick-template - 2.0.11 + junit + junit + test - diff --git a/blade-aop/src/main/java/com/blade/aop/AbstractMethodInterceptor.java b/blade-aop/src/main/java/com/blade/aop/AbstractMethodInterceptor.java new file mode 100644 index 000000000..339207050 --- /dev/null +++ b/blade-aop/src/main/java/com/blade/aop/AbstractMethodInterceptor.java @@ -0,0 +1,46 @@ +package com.blade.aop; + +import java.lang.reflect.Method; + +import com.blade.aop.annotation.Aop; + +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +public abstract class AbstractMethodInterceptor implements MethodInterceptor { + + public abstract Object doInvoke(Invocaction invocaction) throws Throwable; + + public void before(Invocaction invocaction) {} + + public void after(Invocaction invocaction) {} + + /** + * 切面逻辑 obj 代理对象实例 method 源对象的方法名 args 传递给方法的实际入参 proxyMethod + * 与源对象中的method相对应的代理对象中的方法 + */ + public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable { + // 执行源对象的method方法 + try { + Aop aop = method.getDeclaringClass().getAnnotation(Aop.class); + if(null != aop){ + String methodPrefix = aop.methodPrefix(); + String methodName = method.getName(); + if(!"".equals(methodPrefix) && !methodName.startsWith(methodPrefix)){ + return proxy.invokeSuper(target, args); + } + } else { + return proxy.invokeSuper(target, args); + } + + Invocaction invocaction = new Invocaction(target, args, proxy); + before(invocaction); + Object returnValue = doInvoke(invocaction); + after(invocaction); + return returnValue; + } catch (Exception e) { + throw e; + } + } + +} diff --git a/blade-aop/src/main/java/com/blade/aop/Invocaction.java b/blade-aop/src/main/java/com/blade/aop/Invocaction.java new file mode 100644 index 000000000..c44c4559a --- /dev/null +++ b/blade-aop/src/main/java/com/blade/aop/Invocaction.java @@ -0,0 +1,33 @@ +package com.blade.aop; + +import java.lang.reflect.InvocationTargetException; + +import net.sf.cglib.proxy.MethodProxy; + +public class Invocaction { + + private Object target; + private Object[] args; + private MethodProxy proxy; + + public Invocaction(Object target, Object[] args, MethodProxy proxy) { + this.target = target; + this.args = args; + this.proxy = proxy; + } + + public Object invoke() throws Throwable{ + try { + return proxy.invokeSuper(target, args); + } catch (IllegalAccessException e) { + throw e; + } catch (IllegalArgumentException e) { + throw e; + } catch (InvocationTargetException e) { + throw e; + } catch (Throwable e) { + throw e; + } + } + +} diff --git a/blade-aop/src/main/java/com/blade/aop/ProxyFactory.java b/blade-aop/src/main/java/com/blade/aop/ProxyFactory.java new file mode 100644 index 000000000..a3701975b --- /dev/null +++ b/blade-aop/src/main/java/com/blade/aop/ProxyFactory.java @@ -0,0 +1,68 @@ +package com.blade.aop; + +import java.util.ArrayList; +import java.util.List; + +import com.blade.aop.annotation.Aop; +import com.blade.ioc.IocApplication; + +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.NoOp; + +/** + * 代理工厂 + */ +public final class ProxyFactory { + + private ProxyFactory() { + } + + private static MethodInterceptor[] aopInterceptors; + + static { + List aops = IocApplication.getAopInterceptors(); + if (null != aops && aops.size() > 0) { + aopInterceptors = new MethodInterceptor[aops.size()]; + for (int i = 0, len = aops.size(); i < len; i++) { + aopInterceptors[i] = (MethodInterceptor) aops.get(i); + } + } + } + + /* + * 获得代理对象 + */ + public static Object getProxyObj(Class clazz) throws Exception { + Enhancer hancer = new Enhancer(); + // 设置代理对象的父类 + hancer.setSuperclass(clazz); + // 设置回调对象,即调用代理对象里面的方法时,实际上执行的是回调对象(里的intercept方法)。 + MethodInterceptor[] methodInterceptors = filter(clazz); + if (null != methodInterceptors && methodInterceptors.length > 0) { + hancer.setCallbacks(methodInterceptors); + } else { + hancer.setCallback(NoOp.INSTANCE); + } + // 创建代理对象 + return hancer.create(); + } + + private static MethodInterceptor[] filter(Class clazz) { + if (null != aopInterceptors) { + Aop aop = clazz.getAnnotation(Aop.class); + if (null != aop) { + Class inteceptorType = aop.value(); + List methodInterceptors = new ArrayList(); + for (MethodInterceptor methodInterceptor : aopInterceptors) { + if (inteceptorType.equals(methodInterceptor.getClass())) { + methodInterceptors.add(methodInterceptor); + } + } + return methodInterceptors.toArray(new MethodInterceptor[methodInterceptors.size()]); + } + } + return null; + } + +} \ No newline at end of file diff --git a/blade-aop/src/main/java/com/blade/aop/ProxyIocImpl.java b/blade-aop/src/main/java/com/blade/aop/ProxyIocImpl.java new file mode 100644 index 000000000..7a573ac80 --- /dev/null +++ b/blade-aop/src/main/java/com/blade/aop/ProxyIocImpl.java @@ -0,0 +1,22 @@ +package com.blade.aop; + +import com.blade.ioc.BeanDefine; +import com.blade.ioc.SimpleIoc; + +public class ProxyIocImpl extends SimpleIoc { + + public BeanDefine getBeanDefine(Class beanClass, boolean singleton) { + try { + Object object = ProxyFactory.getProxyObj(beanClass); + return new BeanDefine(object, beanClass, singleton); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/blade-aop/src/main/java/com/blade/aop/annotation/Aop.java b/blade-aop/src/main/java/com/blade/aop/annotation/Aop.java new file mode 100644 index 000000000..982005010 --- /dev/null +++ b/blade-aop/src/main/java/com/blade/aop/annotation/Aop.java @@ -0,0 +1,20 @@ +package com.blade.aop.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import net.sf.cglib.proxy.MethodInterceptor; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Aop{ + + Class value(); + + String methodPrefix() default ""; + +} \ No newline at end of file diff --git a/blade-aop/src/main/java/com/blade/plugin/AopPlugin.java b/blade-aop/src/main/java/com/blade/plugin/AopPlugin.java new file mode 100644 index 000000000..63071e219 --- /dev/null +++ b/blade-aop/src/main/java/com/blade/plugin/AopPlugin.java @@ -0,0 +1,24 @@ +package com.blade.plugin; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.blade.Blade; +import com.blade.aop.ProxyIocImpl; + +public class AopPlugin implements Plugin { + + private static final Logger LOGGER = LoggerFactory.getLogger(AopPlugin.class); + + @Override + public void startup() { + LOGGER.info("Set Ioc container is {}", ProxyIocImpl.class.getName()); + Blade.$().container(new ProxyIocImpl()); + } + + @Override + public void shutdown() { + + } + +} diff --git a/blade-aop/src/main/java/net/sf/cglib/beans/BeanCopier.java b/blade-aop/src/main/java/net/sf/cglib/beans/BeanCopier.java new file mode 100644 index 000000000..67b49ee2b --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/beans/BeanCopier.java @@ -0,0 +1,176 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.beans; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.*; +import java.security.ProtectionDomain; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Type; +import java.util.*; + +/** + * @author Chris Nokleberg + */ +abstract public class BeanCopier +{ + private static final BeanCopierKey KEY_FACTORY = + (BeanCopierKey)KeyFactory.create(BeanCopierKey.class); + private static final Type CONVERTER = + TypeUtils.parseType("net.sf.cglib.core.Converter"); + private static final Type BEAN_COPIER = + TypeUtils.parseType("net.sf.cglib.beans.BeanCopier"); + private static final Signature COPY = + new Signature("copy", Type.VOID_TYPE, new Type[]{ Constants.TYPE_OBJECT, Constants.TYPE_OBJECT, CONVERTER }); + private static final Signature CONVERT = + TypeUtils.parseSignature("Object convert(Object, Class, Object)"); + + interface BeanCopierKey { + public Object newInstance(String source, String target, boolean useConverter); + } + + public static BeanCopier create(Class source, Class target, boolean useConverter) { + Generator gen = new Generator(); + gen.setSource(source); + gen.setTarget(target); + gen.setUseConverter(useConverter); + return gen.create(); + } + + abstract public void copy(Object from, Object to, Converter converter); + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(BeanCopier.class.getName()); + private Class source; + private Class target; + private boolean useConverter; + + public Generator() { + super(SOURCE); + } + + public void setSource(Class source) { + if(!Modifier.isPublic(source.getModifiers())){ + setNamePrefix(source.getName()); + } + this.source = source; + } + + public void setTarget(Class target) { + if(!Modifier.isPublic(target.getModifiers())){ + setNamePrefix(target.getName()); + } + + this.target = target; + } + + public void setUseConverter(boolean useConverter) { + this.useConverter = useConverter; + } + + protected ClassLoader getDefaultClassLoader() { + return source.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(source); + } + + public BeanCopier create() { + Object key = KEY_FACTORY.newInstance(source.getName(), target.getName(), useConverter); + return (BeanCopier)super.create(key); + } + + public void generateClass(ClassVisitor v) { + Type sourceType = Type.getType(source); + Type targetType = Type.getType(target); + ClassEmitter ce = new ClassEmitter(v); + ce.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + getClassName(), + BEAN_COPIER, + null, + Constants.SOURCE_FILE); + + EmitUtils.null_constructor(ce); + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, COPY, null); + PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(source); + PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target); + + Map names = new HashMap(); + for (int i = 0; i < getters.length; i++) { + names.put(getters[i].getName(), getters[i]); + } + Local targetLocal = e.make_local(); + Local sourceLocal = e.make_local(); + if (useConverter) { + e.load_arg(1); + e.checkcast(targetType); + e.store_local(targetLocal); + e.load_arg(0); + e.checkcast(sourceType); + e.store_local(sourceLocal); + } else { + e.load_arg(1); + e.checkcast(targetType); + e.load_arg(0); + e.checkcast(sourceType); + } + for (int i = 0; i < setters.length; i++) { + PropertyDescriptor setter = setters[i]; + PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName()); + if (getter != null) { + MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod()); + MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod()); + if (useConverter) { + Type setterType = write.getSignature().getArgumentTypes()[0]; + e.load_local(targetLocal); + e.load_arg(2); + e.load_local(sourceLocal); + e.invoke(read); + e.box(read.getSignature().getReturnType()); + EmitUtils.load_class(e, setterType); + e.push(write.getSignature().getName()); + e.invoke_interface(CONVERTER, CONVERT); + e.unbox_or_zero(setterType); + e.invoke(write); + } else if (compatible(getter, setter)) { + e.dup2(); + e.invoke(read); + e.invoke(write); + } + } + } + e.return_value(); + e.end_method(); + ce.end_class(); + } + + private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) { + // TODO: allow automatic widening conversions? + return setter.getPropertyType().isAssignableFrom(getter.getPropertyType()); + } + + protected Object firstInstance(Class type) { + return ReflectUtils.newInstance(type); + } + + protected Object nextInstance(Object instance) { + return instance; + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/beans/BeanGenerator.java b/blade-aop/src/main/java/net/sf/cglib/beans/BeanGenerator.java new file mode 100644 index 000000000..6b668b71a --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/beans/BeanGenerator.java @@ -0,0 +1,149 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.beans; + +import java.beans.PropertyDescriptor; +import java.security.ProtectionDomain; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Type; + +/** + * @author Juozas Baliuka, Chris Nokleberg + */ +public class BeanGenerator extends AbstractClassGenerator +{ + private static final Source SOURCE = new Source(BeanGenerator.class.getName()); + private static final BeanGeneratorKey KEY_FACTORY = + (BeanGeneratorKey)KeyFactory.create(BeanGeneratorKey.class); + + interface BeanGeneratorKey { + public Object newInstance(String superclass, Map props); + } + + private Class superclass; + private Map props = new HashMap(); + private boolean classOnly; + + public BeanGenerator() { + super(SOURCE); + } + + /** + * Set the class which the generated class will extend. The class + * must not be declared as final, and must have a non-private + * no-argument constructor. + * @param superclass class to extend, or null to extend Object + */ + public void setSuperclass(Class superclass) { + if (superclass != null && superclass.equals(Object.class)) { + superclass = null; + } + this.superclass = superclass; + } + + public void addProperty(String name, Class type) { + if (props.containsKey(name)) { + throw new IllegalArgumentException("Duplicate property name \"" + name + "\""); + } + props.put(name, Type.getType(type)); + } + + protected ClassLoader getDefaultClassLoader() { + if (superclass != null) { + return superclass.getClassLoader(); + } else { + return null; + } + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(superclass); + } + + public Object create() { + classOnly = false; + return createHelper(); + } + + public Object createClass() { + classOnly = true; + return createHelper(); + } + + private Object createHelper() { + if (superclass != null) { + setNamePrefix(superclass.getName()); + } + String superName = (superclass != null) ? superclass.getName() : "java.lang.Object"; + Object key = KEY_FACTORY.newInstance(superName, props); + return super.create(key); + } + + public void generateClass(ClassVisitor v) throws Exception { + int size = props.size(); + String[] names = (String[])props.keySet().toArray(new String[size]); + Type[] types = new Type[size]; + for (int i = 0; i < size; i++) { + types[i] = (Type)props.get(names[i]); + } + ClassEmitter ce = new ClassEmitter(v); + ce.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + getClassName(), + superclass != null ? Type.getType(superclass) : Constants.TYPE_OBJECT, + null, + null); + EmitUtils.null_constructor(ce); + EmitUtils.add_properties(ce, names, types); + ce.end_class(); + } + + protected Object firstInstance(Class type) { + if (classOnly) { + return type; + } else { + return ReflectUtils.newInstance(type); + } + } + + protected Object nextInstance(Object instance) { + Class protoclass = (instance instanceof Class) ? (Class)instance : instance.getClass(); + if (classOnly) { + return protoclass; + } else { + return ReflectUtils.newInstance(protoclass); + } + } + + public static void addProperties(BeanGenerator gen, Map props) { + for (Iterator it = props.keySet().iterator(); it.hasNext();) { + String name = (String)it.next(); + gen.addProperty(name, (Class)props.get(name)); + } + } + + public static void addProperties(BeanGenerator gen, Class type) { + addProperties(gen, ReflectUtils.getBeanProperties(type)); + } + + public static void addProperties(BeanGenerator gen, PropertyDescriptor[] descriptors) { + for (int i = 0; i < descriptors.length; i++) { + gen.addProperty(descriptors[i].getName(), descriptors[i].getPropertyType()); + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/beans/BeanMap.java b/blade-aop/src/main/java/net/sf/cglib/beans/BeanMap.java new file mode 100644 index 000000000..580f20a70 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/beans/BeanMap.java @@ -0,0 +1,320 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.beans; + +import java.security.ProtectionDomain; +import java.beans.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; + +/** + * A Map-based view of a JavaBean. The default set of keys is the + * union of all property names (getters or setters). An attempt to set + * a read-only property will be ignored, and write-only properties will + * be returned as null. Removal of objects is not a + * supported (the key set is fixed). + * @author Chris Nokleberg + */ +abstract public class BeanMap implements Map { + /** + * Limit the properties reflected in the key set of the map + * to readable properties. + * @see BeanMap.Generator#setRequire + */ + public static final int REQUIRE_GETTER = 1; + + /** + * Limit the properties reflected in the key set of the map + * to writable properties. + * @see BeanMap.Generator#setRequire + */ + public static final int REQUIRE_SETTER = 2; + + /** + * Helper method to create a new BeanMap. For finer + * control over the generated instance, use a new instance of + * BeanMap.Generator instead of this static method. + * @param bean the JavaBean underlying the map + * @return a new BeanMap instance + */ + public static BeanMap create(Object bean) { + Generator gen = new Generator(); + gen.setBean(bean); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(BeanMap.class.getName()); + + private static final BeanMapKey KEY_FACTORY = + (BeanMapKey)KeyFactory.create(BeanMapKey.class, KeyFactory.CLASS_BY_NAME); + + interface BeanMapKey { + public Object newInstance(Class type, int require); + } + + private Object bean; + private Class beanClass; + private int require; + + public Generator() { + super(SOURCE); + } + + /** + * Set the bean that the generated map should reflect. The bean may be swapped + * out for another bean of the same type using {@link #setBean}. + * Calling this method overrides any value previously set using {@link #setBeanClass}. + * You must call either this method or {@link #setBeanClass} before {@link #create}. + * @param bean the initial bean + */ + public void setBean(Object bean) { + this.bean = bean; + if (bean != null) + beanClass = bean.getClass(); + } + + /** + * Set the class of the bean that the generated map should support. + * You must call either this method or {@link #setBeanClass} before {@link #create}. + * @param beanClass the class of the bean + */ + public void setBeanClass(Class beanClass) { + this.beanClass = beanClass; + } + + /** + * Limit the properties reflected by the generated map. + * @param require any combination of {@link #REQUIRE_GETTER} and + * {@link #REQUIRE_SETTER}; default is zero (any property allowed) + */ + public void setRequire(int require) { + this.require = require; + } + + protected ClassLoader getDefaultClassLoader() { + return beanClass.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(beanClass); + } + + /** + * Create a new instance of the BeanMap. An existing + * generated class will be reused if possible. + */ + public BeanMap create() { + if (beanClass == null) + throw new IllegalArgumentException("Class of bean unknown"); + setNamePrefix(beanClass.getName()); + return (BeanMap)super.create(KEY_FACTORY.newInstance(beanClass, require)); + } + + public void generateClass(ClassVisitor v) throws Exception { + new BeanMapEmitter(v, getClassName(), beanClass, require); + } + + protected Object firstInstance(Class type) { + return ((BeanMap)ReflectUtils.newInstance(type)).newInstance(bean); + } + + protected Object nextInstance(Object instance) { + return ((BeanMap)instance).newInstance(bean); + } + } + + /** + * Create a new BeanMap instance using the specified bean. + * This is faster than using the {@link #create} static method. + * @param bean the JavaBean underlying the map + * @return a new BeanMap instance + */ + abstract public BeanMap newInstance(Object bean); + + /** + * Get the type of a property. + * @param name the name of the JavaBean property + * @return the type of the property, or null if the property does not exist + */ + abstract public Class getPropertyType(String name); + + protected Object bean; + + protected BeanMap() { + } + + protected BeanMap(Object bean) { + setBean(bean); + } + + public Object get(Object key) { + return get(bean, key); + } + + public Object put(Object key, Object value) { + return put(bean, key, value); + } + + /** + * Get the property of a bean. This allows a BeanMap + * to be used statically for multiple beans--the bean instance tied to the + * map is ignored and the bean passed to this method is used instead. + * @param bean the bean to query; must be compatible with the type of + * this BeanMap + * @param key must be a String + * @return the current value, or null if there is no matching property + */ + abstract public Object get(Object bean, Object key); + + /** + * Set the property of a bean. This allows a BeanMap + * to be used statically for multiple beans--the bean instance tied to the + * map is ignored and the bean passed to this method is used instead. + * @param key must be a String + * @return the old value, if there was one, or null + */ + abstract public Object put(Object bean, Object key, Object value); + + /** + * Change the underlying bean this map should use. + * @param bean the new JavaBean + * @see #getBean + */ + public void setBean(Object bean) { + this.bean = bean; + } + + /** + * Return the bean currently in use by this map. + * @return the current JavaBean + * @see #setBean + */ + public Object getBean() { + return bean; + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + public boolean containsKey(Object key) { + return keySet().contains(key); + } + + public boolean containsValue(Object value) { + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object v = get(it.next()); + if (((value == null) && (v == null)) || (value != null && value.equals(v))) + return true; + } + return false; + } + + public int size() { + return keySet().size(); + } + + public boolean isEmpty() { + return size() == 0; + } + + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + public void putAll(Map t) { + for (Iterator it = t.keySet().iterator(); it.hasNext();) { + Object key = it.next(); + put(key, t.get(key)); + } + } + + public boolean equals(Object o) { + if (o == null || !(o instanceof Map)) { + return false; + } + Map other = (Map)o; + if (size() != other.size()) { + return false; + } + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + if (!other.containsKey(key)) { + return false; + } + Object v1 = get(key); + Object v2 = other.get(key); + if (!((v1 == null) ? v2 == null : v1.equals(v2))) { + return false; + } + } + return true; + } + + public int hashCode() { + int code = 0; + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + Object value = get(key); + code += ((key == null) ? 0 : key.hashCode()) ^ + ((value == null) ? 0 : value.hashCode()); + } + return code; + } + + // TODO: optimize + public Set entrySet() { + HashMap copy = new HashMap(); + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + copy.put(key, get(key)); + } + return Collections.unmodifiableMap(copy).entrySet(); + } + + public Collection values() { + Set keys = keySet(); + List values = new ArrayList(keys.size()); + for (Iterator it = keys.iterator(); it.hasNext();) { + values.add(get(it.next())); + } + return Collections.unmodifiableCollection(values); + } + + /* + * @see java.util.AbstractMap#toString + */ + public String toString() + { + StringBuffer sb = new StringBuffer(); + sb.append('{'); + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + sb.append(key); + sb.append('='); + sb.append(get(key)); + if (it.hasNext()) { + sb.append(", "); + } + } + sb.append('}'); + return sb.toString(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/beans/BeanMapEmitter.java b/blade-aop/src/main/java/net/sf/cglib/beans/BeanMapEmitter.java new file mode 100644 index 000000000..c81314241 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/beans/BeanMapEmitter.java @@ -0,0 +1,192 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.beans; + +import java.beans.*; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; + +class BeanMapEmitter extends ClassEmitter { + private static final Type BEAN_MAP = + TypeUtils.parseType("net.sf.cglib.beans.BeanMap"); + private static final Type FIXED_KEY_SET = + TypeUtils.parseType("net.sf.cglib.beans.FixedKeySet"); + private static final Signature CSTRUCT_OBJECT = + TypeUtils.parseConstructor("Object"); + private static final Signature CSTRUCT_STRING_ARRAY = + TypeUtils.parseConstructor("String[]"); + private static final Signature BEAN_MAP_GET = + TypeUtils.parseSignature("Object get(Object, Object)"); + private static final Signature BEAN_MAP_PUT = + TypeUtils.parseSignature("Object put(Object, Object, Object)"); + private static final Signature KEY_SET = + TypeUtils.parseSignature("java.util.Set keySet()"); + private static final Signature NEW_INSTANCE = + new Signature("newInstance", BEAN_MAP, new Type[]{ Constants.TYPE_OBJECT }); + private static final Signature GET_PROPERTY_TYPE = + TypeUtils.parseSignature("Class getPropertyType(String)"); + + public BeanMapEmitter(ClassVisitor v, String className, Class type, int require) { + super(v); + + begin_class(Constants.V1_2, Constants.ACC_PUBLIC, className, BEAN_MAP, null, Constants.SOURCE_FILE); + EmitUtils.null_constructor(this); + EmitUtils.factory_method(this, NEW_INSTANCE); + generateConstructor(); + + Map getters = makePropertyMap(ReflectUtils.getBeanGetters(type)); + Map setters = makePropertyMap(ReflectUtils.getBeanSetters(type)); + Map allProps = new HashMap(); + allProps.putAll(getters); + allProps.putAll(setters); + + if (require != 0) { + for (Iterator it = allProps.keySet().iterator(); it.hasNext();) { + String name = (String)it.next(); + if ((((require & BeanMap.REQUIRE_GETTER) != 0) && !getters.containsKey(name)) || + (((require & BeanMap.REQUIRE_SETTER) != 0) && !setters.containsKey(name))) { + it.remove(); + getters.remove(name); + setters.remove(name); + } + } + } + generateGet(type, getters); + generatePut(type, setters); + + String[] allNames = getNames(allProps); + generateKeySet(allNames); + generateGetPropertyType(allProps, allNames); + end_class(); + } + + private Map makePropertyMap(PropertyDescriptor[] props) { + Map names = new HashMap(); + for (int i = 0; i < props.length; i++) { + names.put(((PropertyDescriptor)props[i]).getName(), props[i]); + } + return names; + } + + private String[] getNames(Map propertyMap) { + return (String[])propertyMap.keySet().toArray(new String[propertyMap.size()]); + } + + private void generateConstructor() { + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT, null); + e.load_this(); + e.load_arg(0); + e.super_invoke_constructor(CSTRUCT_OBJECT); + e.return_value(); + e.end_method(); + } + + private void generateGet(Class type, final Map getters) { + final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, BEAN_MAP_GET, null); + e.load_arg(0); + e.checkcast(Type.getType(type)); + e.load_arg(1); + e.checkcast(Constants.TYPE_STRING); + EmitUtils.string_switch(e, getNames(getters), Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + PropertyDescriptor pd = (PropertyDescriptor)getters.get(key); + MethodInfo method = ReflectUtils.getMethodInfo(pd.getReadMethod()); + e.invoke(method); + e.box(method.getSignature().getReturnType()); + e.return_value(); + } + public void processDefault() { + e.aconst_null(); + e.return_value(); + } + }); + e.end_method(); + } + + private void generatePut(Class type, final Map setters) { + final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, BEAN_MAP_PUT, null); + e.load_arg(0); + e.checkcast(Type.getType(type)); + e.load_arg(1); + e.checkcast(Constants.TYPE_STRING); + EmitUtils.string_switch(e, getNames(setters), Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + PropertyDescriptor pd = (PropertyDescriptor)setters.get(key); + if (pd.getReadMethod() == null) { + e.aconst_null(); + } else { + MethodInfo read = ReflectUtils.getMethodInfo(pd.getReadMethod()); + e.dup(); + e.invoke(read); + e.box(read.getSignature().getReturnType()); + } + e.swap(); // move old value behind bean + e.load_arg(2); // new value + MethodInfo write = ReflectUtils.getMethodInfo(pd.getWriteMethod()); + e.unbox(write.getSignature().getArgumentTypes()[0]); + e.invoke(write); + e.return_value(); + } + public void processDefault() { + // fall-through + } + }); + e.aconst_null(); + e.return_value(); + e.end_method(); + } + + private void generateKeySet(String[] allNames) { + // static initializer + declare_field(Constants.ACC_STATIC | Constants.ACC_PRIVATE, "keys", FIXED_KEY_SET, null); + + CodeEmitter e = begin_static(); + e.new_instance(FIXED_KEY_SET); + e.dup(); + EmitUtils.push_array(e, allNames); + e.invoke_constructor(FIXED_KEY_SET, CSTRUCT_STRING_ARRAY); + e.putfield("keys"); + e.return_value(); + e.end_method(); + + // keySet + e = begin_method(Constants.ACC_PUBLIC, KEY_SET, null); + e.load_this(); + e.getfield("keys"); + e.return_value(); + e.end_method(); + } + + private void generateGetPropertyType(final Map allProps, String[] allNames) { + final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, GET_PROPERTY_TYPE, null); + e.load_arg(0); + EmitUtils.string_switch(e, allNames, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + PropertyDescriptor pd = (PropertyDescriptor)allProps.get(key); + EmitUtils.load_class(e, Type.getType(pd.getPropertyType())); + e.return_value(); + } + public void processDefault() { + e.aconst_null(); + e.return_value(); + } + }); + e.end_method(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/beans/BulkBean.java b/blade-aop/src/main/java/net/sf/cglib/beans/BulkBean.java new file mode 100644 index 000000000..21398fcfb --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/beans/BulkBean.java @@ -0,0 +1,142 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.beans; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.ProtectionDomain; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; + +/** + * @author Juozas Baliuka + */ +abstract public class BulkBean +{ + private static final BulkBeanKey KEY_FACTORY = + (BulkBeanKey)KeyFactory.create(BulkBeanKey.class); + + interface BulkBeanKey { + public Object newInstance(String target, String[] getters, String[] setters, String[] types); + } + + protected Class target; + protected String[] getters, setters; + protected Class[] types; + + protected BulkBean() { } + + abstract public void getPropertyValues(Object bean, Object[] values); + abstract public void setPropertyValues(Object bean, Object[] values); + + public Object[] getPropertyValues(Object bean) { + Object[] values = new Object[getters.length]; + getPropertyValues(bean, values); + return values; + } + + public Class[] getPropertyTypes() { + return (Class[])types.clone(); + } + + public String[] getGetters() { + return (String[])getters.clone(); + } + + public String[] getSetters() { + return (String[])setters.clone(); + } + + public static BulkBean create(Class target, String[] getters, String[] setters, Class[] types) { + Generator gen = new Generator(); + gen.setTarget(target); + gen.setGetters(getters); + gen.setSetters(setters); + gen.setTypes(types); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(BulkBean.class.getName()); + private Class target; + private String[] getters; + private String[] setters; + private Class[] types; + + public Generator() { + super(SOURCE); + } + + public void setTarget(Class target) { + this.target = target; + } + + public void setGetters(String[] getters) { + this.getters = getters; + } + + public void setSetters(String[] setters) { + this.setters = setters; + } + + public void setTypes(Class[] types) { + this.types = types; + } + + protected ClassLoader getDefaultClassLoader() { + return target.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(target); + } + + public BulkBean create() { + setNamePrefix(target.getName()); + String targetClassName = target.getName(); + String[] typeClassNames = ReflectUtils.getNames(types); + Object key = KEY_FACTORY.newInstance(targetClassName, getters, setters, typeClassNames); + return (BulkBean)super.create(key); + } + + public void generateClass(ClassVisitor v) throws Exception { + new BulkBeanEmitter(v, getClassName(), target, getters, setters, types); + } + + protected Object firstInstance(Class type) { + BulkBean instance = (BulkBean)ReflectUtils.newInstance(type); + instance.target = target; + + int length = getters.length; + instance.getters = new String[length]; + System.arraycopy(getters, 0, instance.getters, 0, length); + + instance.setters = new String[length]; + System.arraycopy(setters, 0, instance.setters, 0, length); + + instance.types = new Class[types.length]; + System.arraycopy(types, 0, instance.types, 0, types.length); + + return instance; + } + + protected Object nextInstance(Object instance) { + return instance; + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/beans/BulkBeanEmitter.java b/blade-aop/src/main/java/net/sf/cglib/beans/BulkBeanEmitter.java new file mode 100644 index 000000000..440069efe --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/beans/BulkBeanEmitter.java @@ -0,0 +1,156 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.beans; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Type; + +class BulkBeanEmitter extends ClassEmitter { + private static final Signature GET_PROPERTY_VALUES = + TypeUtils.parseSignature("void getPropertyValues(Object, Object[])"); + private static final Signature SET_PROPERTY_VALUES = + TypeUtils.parseSignature("void setPropertyValues(Object, Object[])"); + private static final Signature CSTRUCT_EXCEPTION = + TypeUtils.parseConstructor("Throwable, int"); + private static final Type BULK_BEAN = + TypeUtils.parseType("net.sf.cglib.beans.BulkBean"); + private static final Type BULK_BEAN_EXCEPTION = + TypeUtils.parseType("net.sf.cglib.beans.BulkBeanException"); + + public BulkBeanEmitter(ClassVisitor v, + String className, + Class target, + String[] getterNames, + String[] setterNames, + Class[] types) { + super(v); + + Method[] getters = new Method[getterNames.length]; + Method[] setters = new Method[setterNames.length]; + validate(target, getterNames, setterNames, types, getters, setters); + + begin_class(Constants.V1_2, Constants.ACC_PUBLIC, className, BULK_BEAN, null, Constants.SOURCE_FILE); + EmitUtils.null_constructor(this); + generateGet(target, getters); + generateSet(target, setters); + end_class(); + } + + private void generateGet(final Class target, final Method[] getters) { + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, GET_PROPERTY_VALUES, null); + if (getters.length >= 0) { + e.load_arg(0); + e.checkcast(Type.getType(target)); + Local bean = e.make_local(); + e.store_local(bean); + for (int i = 0; i < getters.length; i++) { + if (getters[i] != null) { + MethodInfo getter = ReflectUtils.getMethodInfo(getters[i]); + e.load_arg(1); + e.push(i); + e.load_local(bean); + e.invoke(getter); + e.box(getter.getSignature().getReturnType()); + e.aastore(); + } + } + } + e.return_value(); + e.end_method(); + } + + private void generateSet(final Class target, final Method[] setters) { + // setPropertyValues + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, SET_PROPERTY_VALUES, null); + if (setters.length > 0) { + Local index = e.make_local(Type.INT_TYPE); + e.push(0); + e.store_local(index); + e.load_arg(0); + e.checkcast(Type.getType(target)); + e.load_arg(1); + Block handler = e.begin_block(); + int lastIndex = 0; + for (int i = 0; i < setters.length; i++) { + if (setters[i] != null) { + MethodInfo setter = ReflectUtils.getMethodInfo(setters[i]); + int diff = i - lastIndex; + if (diff > 0) { + e.iinc(index, diff); + lastIndex = i; + } + e.dup2(); + e.aaload(i); + e.unbox(setter.getSignature().getArgumentTypes()[0]); + e.invoke(setter); + } + } + handler.end(); + e.return_value(); + e.catch_exception(handler, Constants.TYPE_THROWABLE); + e.new_instance(BULK_BEAN_EXCEPTION); + e.dup_x1(); + e.swap(); + e.load_local(index); + e.invoke_constructor(BULK_BEAN_EXCEPTION, CSTRUCT_EXCEPTION); + e.athrow(); + } else { + e.return_value(); + } + e.end_method(); + } + + private static void validate(Class target, + String[] getters, + String[] setters, + Class[] types, + Method[] getters_out, + Method[] setters_out) { + int i = -1; + if (setters.length != types.length || getters.length != types.length) { + throw new BulkBeanException("accessor array length must be equal type array length", i); + } + try { + for (i = 0; i < types.length; i++) { + if (getters[i] != null) { + Method method = ReflectUtils.findDeclaredMethod(target, getters[i], null); + if (method.getReturnType() != types[i]) { + throw new BulkBeanException("Specified type " + types[i] + + " does not match declared type " + method.getReturnType(), i); + } + if (Modifier.isPrivate(method.getModifiers())) { + throw new BulkBeanException("Property is private", i); + } + getters_out[i] = method; + } + if (setters[i] != null) { + Method method = ReflectUtils.findDeclaredMethod(target, setters[i], new Class[]{ types[i] }); + if (Modifier.isPrivate(method.getModifiers()) ){ + throw new BulkBeanException("Property is private", i); + } + setters_out[i] = method; + } + } + } catch (NoSuchMethodException e) { + throw new BulkBeanException("Cannot find specified property", i); + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/beans/BulkBeanException.java b/blade-aop/src/main/java/net/sf/cglib/beans/BulkBeanException.java new file mode 100644 index 000000000..fcb4e3648 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/beans/BulkBeanException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.beans; + +import net.sf.cglib.core.CodeGenerationException; + +public class BulkBeanException extends RuntimeException +{ + private int index; + private Throwable cause; + + public BulkBeanException(String message, int index) { + super(message); + this.index = index; + } + + public BulkBeanException(Throwable cause, int index) { + super(cause.getMessage()); + this.index = index; + this.cause = cause; + } + + public int getIndex() { + return index; + } + + public Throwable getCause() { + return cause; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/beans/FixedKeySet.java b/blade-aop/src/main/java/net/sf/cglib/beans/FixedKeySet.java new file mode 100644 index 000000000..13c91e41b --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/beans/FixedKeySet.java @@ -0,0 +1,36 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.beans; + +import java.util.*; + +public /* need it for class loading */ class FixedKeySet extends AbstractSet { + private Set set; + private int size; + + public FixedKeySet(String[] keys) { + size = keys.length; + set = Collections.unmodifiableSet(new HashSet(Arrays.asList(keys))); + } + + public Iterator iterator() { + return set.iterator(); + } + + public int size() { + return size; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/beans/ImmutableBean.java b/blade-aop/src/main/java/net/sf/cglib/beans/ImmutableBean.java new file mode 100644 index 000000000..33350ab33 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/beans/ImmutableBean.java @@ -0,0 +1,128 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.beans; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Type; +/** + * @author Chris Nokleberg + */ +public class ImmutableBean +{ + private static final Type ILLEGAL_STATE_EXCEPTION = + TypeUtils.parseType("IllegalStateException"); + private static final Signature CSTRUCT_OBJECT = + TypeUtils.parseConstructor("Object"); + private static final Class[] OBJECT_CLASSES = { Object.class }; + private static final String FIELD_NAME = "CGLIB$RWBean"; + + private ImmutableBean() { + } + + public static Object create(Object bean) { + Generator gen = new Generator(); + gen.setBean(bean); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(ImmutableBean.class.getName()); + private Object bean; + private Class target; + + public Generator() { + super(SOURCE); + } + + public void setBean(Object bean) { + this.bean = bean; + target = bean.getClass(); + } + + protected ClassLoader getDefaultClassLoader() { + return target.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(target); + } + + public Object create() { + String name = target.getName(); + setNamePrefix(name); + return super.create(name); + } + + public void generateClass(ClassVisitor v) { + Type targetType = Type.getType(target); + ClassEmitter ce = new ClassEmitter(v); + ce.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + getClassName(), + targetType, + null, + Constants.SOURCE_FILE); + + ce.declare_field(Constants.ACC_FINAL | Constants.ACC_PRIVATE, FIELD_NAME, targetType, null); + + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT, null); + e.load_this(); + e.super_invoke_constructor(); + e.load_this(); + e.load_arg(0); + e.checkcast(targetType); + e.putfield(FIELD_NAME); + e.return_value(); + e.end_method(); + + PropertyDescriptor[] descriptors = ReflectUtils.getBeanProperties(target); + Method[] getters = ReflectUtils.getPropertyMethods(descriptors, true, false); + Method[] setters = ReflectUtils.getPropertyMethods(descriptors, false, true); + + for (int i = 0; i < getters.length; i++) { + MethodInfo getter = ReflectUtils.getMethodInfo(getters[i]); + e = EmitUtils.begin_method(ce, getter, Constants.ACC_PUBLIC); + e.load_this(); + e.getfield(FIELD_NAME); + e.invoke(getter); + e.return_value(); + e.end_method(); + } + + for (int i = 0; i < setters.length; i++) { + MethodInfo setter = ReflectUtils.getMethodInfo(setters[i]); + e = EmitUtils.begin_method(ce, setter, Constants.ACC_PUBLIC); + e.throw_exception(ILLEGAL_STATE_EXCEPTION, "Bean is immutable"); + e.end_method(); + } + + ce.end_class(); + } + + protected Object firstInstance(Class type) { + return ReflectUtils.newInstance(type, OBJECT_CLASSES, new Object[]{ bean }); + } + + // TODO: optimize + protected Object nextInstance(Object instance) { + return firstInstance(instance.getClass()); + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/AbstractClassGenerator.java b/blade-aop/src/main/java/net/sf/cglib/core/AbstractClassGenerator.java new file mode 100644 index 000000000..d7abfce39 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/AbstractClassGenerator.java @@ -0,0 +1,353 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import net.sf.cglib.core.internal.Function; +import net.sf.cglib.core.internal.LoadingCache; +import org.objectweb.asm.ClassReader; + +import java.lang.ref.WeakReference; +import java.security.ProtectionDomain; +import java.util.Map; +import java.util.Set; +import java.util.HashSet; +import java.util.WeakHashMap; + +/** + * Abstract class for all code-generating CGLIB utilities. + * In addition to caching generated classes for performance, it provides hooks for + * customizing the ClassLoader, name of the generated class, and transformations + * applied before generation. + */ +abstract public class AbstractClassGenerator +implements ClassGenerator +{ + private static final ThreadLocal CURRENT = new ThreadLocal(); + + private static volatile Map CACHE = new WeakHashMap(); + + private GeneratorStrategy strategy = DefaultGeneratorStrategy.INSTANCE; + private NamingPolicy namingPolicy = DefaultNamingPolicy.INSTANCE; + private Source source; + private ClassLoader classLoader; + private String namePrefix; + private Object key; + private boolean useCache = true; + private String className; + private boolean attemptLoad; + + protected static class ClassLoaderData { + private final Set reservedClassNames = new HashSet(); + + /** + * {@link AbstractClassGenerator} here holds "cache key" (e.g. {@link net.sf.cglib.proxy.Enhancer} + * configuration), and the value is the generated class plus some additional values + * (see {@link #unwrapCachedValue(Object)}. + *

The generated classes can be reused as long as their classloader is reachable.

+ *

Note: the only way to access a class is to find it through generatedClasses cache, thus + * the key should not expire as long as the class itself is alive (its classloader is alive).

+ */ + private final LoadingCache generatedClasses; + + /** + * Note: ClassLoaderData object is stored as a value of {@code WeakHashMap} thus + * this classLoader reference should be weak otherwise it would make classLoader strongly reachable + * and alive forever. + * Reference queue is not required since the cleanup is handled by {@link WeakHashMap}. + */ + private final WeakReference classLoader; + + private final Predicate uniqueNamePredicate = new Predicate() { + public boolean evaluate(Object name) { + return reservedClassNames.contains(name); + } + }; + + private static final Function GET_KEY = new Function() { + public Object apply(AbstractClassGenerator gen) { + return gen.key; + } + }; + + public ClassLoaderData(ClassLoader classLoader) { + if (classLoader == null) { + throw new IllegalArgumentException("classLoader == null is not yet supported"); + } + this.classLoader = new WeakReference(classLoader); + Function load = + new Function() { + public Object apply(AbstractClassGenerator gen) { + Class klass = gen.generate(ClassLoaderData.this); + return gen.wrapCachedClass(klass); + } + }; + generatedClasses = new LoadingCache(GET_KEY, load); + } + + public ClassLoader getClassLoader() { + return classLoader.get(); + } + + public void reserveName(String name) { + reservedClassNames.add(name); + } + + public Predicate getUniqueNamePredicate() { + return uniqueNamePredicate; + } + + public Object get(AbstractClassGenerator gen, boolean useCache) { + if (!useCache) { + return gen.generate(ClassLoaderData.this); + } else { + Object cachedValue = generatedClasses.get(gen); + return gen.unwrapCachedValue(cachedValue); + } + } + } + + protected T wrapCachedClass(Class klass) { + return (T) new WeakReference(klass); + } + + protected Object unwrapCachedValue(T cached) { + return ((WeakReference) cached).get(); + } + + protected static class Source { + String name; + public Source(String name) { + this.name = name; + } + } + + protected AbstractClassGenerator(Source source) { + this.source = source; + } + + protected void setNamePrefix(String namePrefix) { + this.namePrefix = namePrefix; + } + + final protected String getClassName() { + return className; + } + + private void setClassName(String className) { + this.className = className; + } + + private String generateClassName(Predicate nameTestPredicate) { + return namingPolicy.getClassName(namePrefix, source.name, key, nameTestPredicate); + } + + /** + * Set the ClassLoader in which the class will be generated. + * Concrete subclasses of AbstractClassGenerator (such as Enhancer) + * will try to choose an appropriate default if this is unset. + *

+ * Classes are cached per-ClassLoader using a WeakHashMap, to allow + * the generated classes to be removed when the associated loader is garbage collected. + * @param classLoader the loader to generate the new class with, or null to use the default + */ + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + /** + * Override the default naming policy. + * @see DefaultNamingPolicy + * @param namingPolicy the custom policy, or null to use the default + */ + public void setNamingPolicy(NamingPolicy namingPolicy) { + if (namingPolicy == null) + namingPolicy = DefaultNamingPolicy.INSTANCE; + this.namingPolicy = namingPolicy; + } + + /** + * @see #setNamingPolicy + */ + public NamingPolicy getNamingPolicy() { + return namingPolicy; + } + + /** + * Whether use and update the static cache of generated classes + * for a class with the same properties. Default is true. + */ + public void setUseCache(boolean useCache) { + this.useCache = useCache; + } + + /** + * @see #setUseCache + */ + public boolean getUseCache() { + return useCache; + } + + /** + * If set, CGLIB will attempt to load classes from the specified + * ClassLoader before generating them. Because generated + * class names are not guaranteed to be unique, the default is false. + */ + public void setAttemptLoad(boolean attemptLoad) { + this.attemptLoad = attemptLoad; + } + + public boolean getAttemptLoad() { + return attemptLoad; + } + + /** + * Set the strategy to use to create the bytecode from this generator. + * By default an instance of {@see DefaultGeneratorStrategy} is used. + */ + public void setStrategy(GeneratorStrategy strategy) { + if (strategy == null) + strategy = DefaultGeneratorStrategy.INSTANCE; + this.strategy = strategy; + } + + /** + * @see #setStrategy + */ + public GeneratorStrategy getStrategy() { + return strategy; + } + + /** + * Used internally by CGLIB. Returns the AbstractClassGenerator + * that is being used to generate a class in the current thread. + */ + public static AbstractClassGenerator getCurrent() { + return (AbstractClassGenerator)CURRENT.get(); + } + + public ClassLoader getClassLoader() { + ClassLoader t = classLoader; + if (t == null) { + t = getDefaultClassLoader(); + } + if (t == null) { + t = getClass().getClassLoader(); + } + if (t == null) { + t = Thread.currentThread().getContextClassLoader(); + } + if (t == null) { + throw new IllegalStateException("Cannot determine classloader"); + } + return t; + } + + abstract protected ClassLoader getDefaultClassLoader(); + + /** + * Returns the protection domain to use when defining the class. + *

+ * Default implementation returns null for using a default protection domain. Sub-classes may + * override to use a more specific protection domain. + *

+ * + * @return the protection domain (null for using a default) + */ + protected ProtectionDomain getProtectionDomain() { + return null; + } + + protected Object create(Object key) { + try { + ClassLoader loader = getClassLoader(); + Map cache = CACHE; + ClassLoaderData data = cache.get(loader); + if (data == null) { + synchronized (AbstractClassGenerator.class) { + cache = CACHE; + data = cache.get(loader); + if (data == null) { + Map newCache = new WeakHashMap(cache); + data = new ClassLoaderData(loader); + newCache.put(loader, data); + CACHE = newCache; + } + } + } + this.key = key; + Object obj = data.get(this, getUseCache()); + if (obj instanceof Class) { + return firstInstance((Class) obj); + } + return nextInstance(obj); + } catch (RuntimeException e) { + throw e; + } catch (Error e) { + throw e; + } catch (Exception e) { + throw new CodeGenerationException(e); + } + } + + protected Class generate(ClassLoaderData data) { + Class gen; + Object save = CURRENT.get(); + CURRENT.set(this); + try { + ClassLoader classLoader = data.getClassLoader(); + if (classLoader == null) { + throw new IllegalStateException("ClassLoader is null while trying to define class " + + getClassName() + ". It seems that the loader has been expired from a weak reference somehow. " + + "Please file an issue at cglib's issue tracker."); + } + synchronized (classLoader) { + String name = generateClassName(data.getUniqueNamePredicate()); + data.reserveName(name); + this.setClassName(name); + } + if (attemptLoad) { + try { + gen = classLoader.loadClass(getClassName()); + return gen; + } catch (ClassNotFoundException e) { + // ignore + } + } + byte[] b = strategy.generate(this); + String className = ClassNameReader.getClassName(new ClassReader(b)); + ProtectionDomain protectionDomain = getProtectionDomain(); + synchronized (classLoader) { // just in case + if (protectionDomain == null) { + gen = ReflectUtils.defineClass(className, b, classLoader); + } else { + gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain); + } + } + return gen; + } catch (RuntimeException e) { + throw e; + } catch (Error e) { + throw e; + } catch (Exception e) { + throw new CodeGenerationException(e); + } finally { + CURRENT.set(save); + } + } + + abstract protected Object firstInstance(Class type) throws Exception; + abstract protected Object nextInstance(Object instance) throws Exception; +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/Block.java b/blade-aop/src/main/java/net/sf/cglib/core/Block.java new file mode 100644 index 000000000..4fb2e18e5 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/Block.java @@ -0,0 +1,49 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.Label; + +public class Block +{ + private CodeEmitter e; + private Label start; + private Label end; + + public Block(CodeEmitter e) { + this.e = e; + start = e.mark(); + } + + public CodeEmitter getCodeEmitter() { + return e; + } + + public void end() { + if (end != null) { + throw new IllegalStateException("end of label already set"); + } + end = e.mark(); + } + + public Label getStart() { + return start; + } + + public Label getEnd() { + return end; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/ClassEmitter.java b/blade-aop/src/main/java/net/sf/cglib/core/ClassEmitter.java new file mode 100644 index 000000000..1bee38207 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/ClassEmitter.java @@ -0,0 +1,282 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import net.sf.cglib.transform.ClassTransformer; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Juozas Baliuka, Chris Nokleberg + */ +public class ClassEmitter extends ClassTransformer { + private ClassInfo classInfo; + private Map fieldInfo; + + private static int hookCounter; + private MethodVisitor rawStaticInit; + private CodeEmitter staticInit; + private CodeEmitter staticHook; + private Signature staticHookSig; + + public ClassEmitter(ClassVisitor cv) { + setTarget(cv); + } + + public ClassEmitter() { + super(Opcodes.ASM5); + } + + public void setTarget(ClassVisitor cv) { + this.cv = cv; + fieldInfo = new HashMap(); + + // just to be safe + staticInit = staticHook = null; + staticHookSig = null; + } + + synchronized private static int getNextHook() { + return ++hookCounter; + } + + public ClassInfo getClassInfo() { + return classInfo; + } + + public void begin_class(int version, final int access, String className, final Type superType, final Type[] interfaces, String source) { + final Type classType = Type.getType("L" + className.replace('.', '/') + ";"); + classInfo = new ClassInfo() { + public Type getType() { + return classType; + } + public Type getSuperType() { + return (superType != null) ? superType : Constants.TYPE_OBJECT; + } + public Type[] getInterfaces() { + return interfaces; + } + public int getModifiers() { + return access; + } + }; + cv.visit(version, + access, + classInfo.getType().getInternalName(), + null, + classInfo.getSuperType().getInternalName(), + TypeUtils.toInternalNames(interfaces)); + if (source != null) + cv.visitSource(source, null); + init(); + } + + public CodeEmitter getStaticHook() { + if (TypeUtils.isInterface(getAccess())) { + throw new IllegalStateException("static hook is invalid for this class"); + } + if (staticHook == null) { + staticHookSig = new Signature("CGLIB$STATICHOOK" + getNextHook(), "()V"); + staticHook = begin_method(Constants.ACC_STATIC, + staticHookSig, + null); + if (staticInit != null) { + staticInit.invoke_static_this(staticHookSig); + } + } + return staticHook; + } + + protected void init() { + } + + public int getAccess() { + return classInfo.getModifiers(); + } + + public Type getClassType() { + return classInfo.getType(); + } + + public Type getSuperType() { + return classInfo.getSuperType(); + } + + public void end_class() { + if (staticHook != null && staticInit == null) { + // force creation of static init + begin_static(); + } + if (staticInit != null) { + staticHook.return_value(); + staticHook.end_method(); + rawStaticInit.visitInsn(Constants.RETURN); + rawStaticInit.visitMaxs(0, 0); + staticInit = staticHook = null; + staticHookSig = null; + } + cv.visitEnd(); + } + + public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) { + if (classInfo == null) + throw new IllegalStateException("classInfo is null! " + this); + MethodVisitor v = cv.visitMethod(access, + sig.getName(), + sig.getDescriptor(), + null, + TypeUtils.toInternalNames(exceptions)); + if (sig.equals(Constants.SIG_STATIC) && !TypeUtils.isInterface(getAccess())) { + rawStaticInit = v; + MethodVisitor wrapped = new MethodVisitor(Opcodes.ASM5, v) { + public void visitMaxs(int maxStack, int maxLocals) { + // ignore + } + public void visitInsn(int insn) { + if (insn != Constants.RETURN) { + super.visitInsn(insn); + } + } + }; + staticInit = new CodeEmitter(this, wrapped, access, sig, exceptions); + if (staticHook == null) { + // force static hook creation + getStaticHook(); + } else { + staticInit.invoke_static_this(staticHookSig); + } + return staticInit; + } else if (sig.equals(staticHookSig)) { + return new CodeEmitter(this, v, access, sig, exceptions) { + public boolean isStaticHook() { + return true; + } + }; + } else { + return new CodeEmitter(this, v, access, sig, exceptions); + } + } + + public CodeEmitter begin_static() { + return begin_method(Constants.ACC_STATIC, Constants.SIG_STATIC, null); + } + + public void declare_field(int access, String name, Type type, Object value) { + FieldInfo existing = (FieldInfo)fieldInfo.get(name); + FieldInfo info = new FieldInfo(access, name, type, value); + if (existing != null) { + if (!info.equals(existing)) { + throw new IllegalArgumentException("Field \"" + name + "\" has been declared differently"); + } + } else { + fieldInfo.put(name, info); + cv.visitField(access, name, type.getDescriptor(), null, value); + } + } + + // TODO: make public? + boolean isFieldDeclared(String name) { + return fieldInfo.get(name) != null; + } + + FieldInfo getFieldInfo(String name) { + FieldInfo field = (FieldInfo)fieldInfo.get(name); + if (field == null) { + throw new IllegalArgumentException("Field " + name + " is not declared in " + getClassType().getClassName()); + } + return field; + } + + static class FieldInfo { + int access; + String name; + Type type; + Object value; + + public FieldInfo(int access, String name, Type type, Object value) { + this.access = access; + this.name = name; + this.type = type; + this.value = value; + } + + public boolean equals(Object o) { + if (o == null) + return false; + if (!(o instanceof FieldInfo)) + return false; + FieldInfo other = (FieldInfo)o; + if (access != other.access || + !name.equals(other.name) || + !type.equals(other.type)) { + return false; + } + if ((value == null) ^ (other.value == null)) + return false; + if (value != null && !value.equals(other.value)) + return false; + return true; + } + + public int hashCode() { + return access ^ name.hashCode() ^ type.hashCode() ^ ((value == null) ? 0 : value.hashCode()); + } + } + + public void visit(int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + begin_class(version, + access, + name.replace('/', '.'), + TypeUtils.fromInternalName(superName), + TypeUtils.fromInternalNames(interfaces), + null); // TODO + } + + public void visitEnd() { + end_class(); + } + + public FieldVisitor visitField(int access, + String name, + String desc, + String signature, + Object value) { + declare_field(access, name, Type.getType(desc), value); + return null; // TODO + } + + public MethodVisitor visitMethod(int access, + String name, + String desc, + String signature, + String[] exceptions) { + return begin_method(access, + new Signature(name, desc), + TypeUtils.fromInternalNames(exceptions)); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/ClassGenerator.java b/blade-aop/src/main/java/net/sf/cglib/core/ClassGenerator.java new file mode 100644 index 000000000..fe61815f7 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/ClassGenerator.java @@ -0,0 +1,22 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.ClassVisitor; + +public interface ClassGenerator { + void generateClass(ClassVisitor v) throws Exception; +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/ClassInfo.java b/blade-aop/src/main/java/net/sf/cglib/core/ClassInfo.java new file mode 100644 index 000000000..2e066b537 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/ClassInfo.java @@ -0,0 +1,47 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Type; + +abstract public class ClassInfo { + + protected ClassInfo() { + } + + abstract public Type getType(); + abstract public Type getSuperType(); + abstract public Type[] getInterfaces(); + abstract public int getModifiers(); + + public boolean equals(Object o) { + if (o == null) + return false; + if (!(o instanceof ClassInfo)) + return false; + return getType().equals(((ClassInfo)o).getType()); + } + + public int hashCode() { + return getType().hashCode(); + } + + public String toString() { + // TODO: include modifiers, superType, interfaces + return getType().getClassName(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/ClassNameReader.java b/blade-aop/src/main/java/net/sf/cglib/core/ClassNameReader.java new file mode 100644 index 000000000..1e198e55e --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/ClassNameReader.java @@ -0,0 +1,63 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.*; + +// TODO: optimize (ClassReader buffers entire class before accept) +public class ClassNameReader { + private ClassNameReader() { + } + + private static final EarlyExitException EARLY_EXIT = new EarlyExitException(); + private static class EarlyExitException extends RuntimeException { } + + public static String getClassName(ClassReader r) { + + return getClassInfo(r)[0]; + + } + + public static String[] getClassInfo(ClassReader r) { + final List array = new ArrayList(); + try { + r.accept(new ClassVisitor(Opcodes.ASM5, null) { + public void visit(int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + array.add( name.replace('/', '.') ); + if(superName != null){ + array.add( superName.replace('/', '.') ); + } + for(int i = 0; i < interfaces.length; i++ ){ + array.add( interfaces[i].replace('/', '.') ); + } + + throw EARLY_EXIT; + } + }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + } catch (EarlyExitException e) { } + + return (String[])array.toArray( new String[]{} ); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/ClassesKey.java b/blade-aop/src/main/java/net/sf/cglib/core/ClassesKey.java new file mode 100644 index 000000000..0a0d8df3c --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/ClassesKey.java @@ -0,0 +1,46 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +public class ClassesKey { + private static final Key FACTORY = (Key)KeyFactory.create(Key.class); + + interface Key { + Object newInstance(Object[] array); + } + + private ClassesKey() { + } + + public static Object create(Object[] array) { + return FACTORY.newInstance(classNames(array)); + } + + private static String[] classNames(Object[] objects) { + if (objects == null) { + return null; + } + String[] classNames = new String[objects.length]; + for (int i = 0; i < objects.length; i++) { + Object object = objects[i]; + if (object != null) { + Class aClass = object.getClass(); + classNames[i] = aClass == null ? null : aClass.getName(); + } + } + return classNames; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/CodeEmitter.java b/blade-aop/src/main/java/net/sf/cglib/core/CodeEmitter.java new file mode 100644 index 000000000..63150503d --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/CodeEmitter.java @@ -0,0 +1,864 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import java.io.*; +import java.util.*; +import org.objectweb.asm.*; + +/** + * @author Juozas Baliuka, Chris Nokleberg + */ +public class CodeEmitter extends LocalVariablesSorter { + private static final Signature BOOLEAN_VALUE = + TypeUtils.parseSignature("boolean booleanValue()"); + private static final Signature CHAR_VALUE = + TypeUtils.parseSignature("char charValue()"); + private static final Signature LONG_VALUE = + TypeUtils.parseSignature("long longValue()"); + private static final Signature DOUBLE_VALUE = + TypeUtils.parseSignature("double doubleValue()"); + private static final Signature FLOAT_VALUE = + TypeUtils.parseSignature("float floatValue()"); + private static final Signature INT_VALUE = + TypeUtils.parseSignature("int intValue()"); + private static final Signature CSTRUCT_NULL = + TypeUtils.parseConstructor(""); + private static final Signature CSTRUCT_STRING = + TypeUtils.parseConstructor("String"); + + public static final int ADD = Constants.IADD; + public static final int MUL = Constants.IMUL; + public static final int XOR = Constants.IXOR; + public static final int USHR = Constants.IUSHR; + public static final int SUB = Constants.ISUB; + public static final int DIV = Constants.IDIV; + public static final int NEG = Constants.INEG; + public static final int REM = Constants.IREM; + public static final int AND = Constants.IAND; + public static final int OR = Constants.IOR; + + public static final int GT = Constants.IFGT; + public static final int LT = Constants.IFLT; + public static final int GE = Constants.IFGE; + public static final int LE = Constants.IFLE; + public static final int NE = Constants.IFNE; + public static final int EQ = Constants.IFEQ; + + private ClassEmitter ce; + private State state; + + private static class State + extends MethodInfo + { + ClassInfo classInfo; + int access; + Signature sig; + Type[] argumentTypes; + int localOffset; + Type[] exceptionTypes; + + State(ClassInfo classInfo, int access, Signature sig, Type[] exceptionTypes) { + this.classInfo = classInfo; + this.access = access; + this.sig = sig; + this.exceptionTypes = exceptionTypes; + localOffset = TypeUtils.isStatic(access) ? 0 : 1; + argumentTypes = sig.getArgumentTypes(); + } + + public ClassInfo getClassInfo() { + return classInfo; + } + + public int getModifiers() { + return access; + } + + public Signature getSignature() { + return sig; + } + + public Type[] getExceptionTypes() { + return exceptionTypes; + } + + public Attribute getAttribute() { + // TODO + return null; + } + } + + CodeEmitter(ClassEmitter ce, MethodVisitor mv, int access, Signature sig, Type[] exceptionTypes) { + super(access, sig.getDescriptor(), mv); + this.ce = ce; + state = new State(ce.getClassInfo(), access, sig, exceptionTypes); + } + + public CodeEmitter(CodeEmitter wrap) { + super(wrap); + this.ce = wrap.ce; + this.state = wrap.state; + } + + public boolean isStaticHook() { + return false; + } + + public Signature getSignature() { + return state.sig; + } + + public Type getReturnType() { + return state.sig.getReturnType(); + } + + public MethodInfo getMethodInfo() { + return state; + } + + public ClassEmitter getClassEmitter() { + return ce; + } + + public void end_method() { + visitMaxs(0, 0); + } + + public Block begin_block() { + return new Block(this); + } + + public void catch_exception(Block block, Type exception) { + if (block.getEnd() == null) { + throw new IllegalStateException("end of block is unset"); + } + mv.visitTryCatchBlock(block.getStart(), + block.getEnd(), + mark(), + exception.getInternalName()); + } + + public void goTo(Label label) { mv.visitJumpInsn(Constants.GOTO, label); } + public void ifnull(Label label) { mv.visitJumpInsn(Constants.IFNULL, label); } + public void ifnonnull(Label label) { mv.visitJumpInsn(Constants.IFNONNULL, label); } + + public void if_jump(int mode, Label label) { + mv.visitJumpInsn(mode, label); + } + + public void if_icmp(int mode, Label label) { + if_cmp(Type.INT_TYPE, mode, label); + } + + public void if_cmp(Type type, int mode, Label label) { + int intOp = -1; + int jumpmode = mode; + switch (mode) { + case GE: jumpmode = LT; break; + case LE: jumpmode = GT; break; + } + switch (type.getSort()) { + case Type.LONG: + mv.visitInsn(Constants.LCMP); + break; + case Type.DOUBLE: + mv.visitInsn(Constants.DCMPG); + break; + case Type.FLOAT: + mv.visitInsn(Constants.FCMPG); + break; + case Type.ARRAY: + case Type.OBJECT: + switch (mode) { + case EQ: + mv.visitJumpInsn(Constants.IF_ACMPEQ, label); + return; + case NE: + mv.visitJumpInsn(Constants.IF_ACMPNE, label); + return; + } + throw new IllegalArgumentException("Bad comparison for type " + type); + default: + switch (mode) { + case EQ: intOp = Constants.IF_ICMPEQ; break; + case NE: intOp = Constants.IF_ICMPNE; break; + case GE: swap(); /* fall through */ + case LT: intOp = Constants.IF_ICMPLT; break; + case LE: swap(); /* fall through */ + case GT: intOp = Constants.IF_ICMPGT; break; + } + mv.visitJumpInsn(intOp, label); + return; + } + if_jump(jumpmode, label); + } + + public void pop() { mv.visitInsn(Constants.POP); } + public void pop2() { mv.visitInsn(Constants.POP2); } + public void dup() { mv.visitInsn(Constants.DUP); } + public void dup2() { mv.visitInsn(Constants.DUP2); } + public void dup_x1() { mv.visitInsn(Constants.DUP_X1); } + public void dup_x2() { mv.visitInsn(Constants.DUP_X2); } + public void dup2_x1() { mv.visitInsn(Constants.DUP2_X1); } + public void dup2_x2() { mv.visitInsn(Constants.DUP2_X2); } + public void swap() { mv.visitInsn(Constants.SWAP); } + public void aconst_null() { mv.visitInsn(Constants.ACONST_NULL); } + + public void swap(Type prev, Type type) { + if (type.getSize() == 1) { + if (prev.getSize() == 1) { + swap(); // same as dup_x1(), pop(); + } else { + dup_x2(); + pop(); + } + } else { + if (prev.getSize() == 1) { + dup2_x1(); + pop2(); + } else { + dup2_x2(); + pop2(); + } + } + } + + public void monitorenter() { mv.visitInsn(Constants.MONITORENTER); } + public void monitorexit() { mv.visitInsn(Constants.MONITOREXIT); } + + public void math(int op, Type type) { mv.visitInsn(type.getOpcode(op)); } + + public void array_load(Type type) { mv.visitInsn(type.getOpcode(Constants.IALOAD)); } + public void array_store(Type type) { mv.visitInsn(type.getOpcode(Constants.IASTORE)); } + + /** + * Casts from one primitive numeric type to another + */ + public void cast_numeric(Type from, Type to) { + if (from != to) { + if (from == Type.DOUBLE_TYPE) { + if (to == Type.FLOAT_TYPE) { + mv.visitInsn(Constants.D2F); + } else if (to == Type.LONG_TYPE) { + mv.visitInsn(Constants.D2L); + } else { + mv.visitInsn(Constants.D2I); + cast_numeric(Type.INT_TYPE, to); + } + } else if (from == Type.FLOAT_TYPE) { + if (to == Type.DOUBLE_TYPE) { + mv.visitInsn(Constants.F2D); + } else if (to == Type.LONG_TYPE) { + mv.visitInsn(Constants.F2L); + } else { + mv.visitInsn(Constants.F2I); + cast_numeric(Type.INT_TYPE, to); + } + } else if (from == Type.LONG_TYPE) { + if (to == Type.DOUBLE_TYPE) { + mv.visitInsn(Constants.L2D); + } else if (to == Type.FLOAT_TYPE) { + mv.visitInsn(Constants.L2F); + } else { + mv.visitInsn(Constants.L2I); + cast_numeric(Type.INT_TYPE, to); + } + } else { + if (to == Type.BYTE_TYPE) { + mv.visitInsn(Constants.I2B); + } else if (to == Type.CHAR_TYPE) { + mv.visitInsn(Constants.I2C); + } else if (to == Type.DOUBLE_TYPE) { + mv.visitInsn(Constants.I2D); + } else if (to == Type.FLOAT_TYPE) { + mv.visitInsn(Constants.I2F); + } else if (to == Type.LONG_TYPE) { + mv.visitInsn(Constants.I2L); + } else if (to == Type.SHORT_TYPE) { + mv.visitInsn(Constants.I2S); + } + } + } + } + + public void push(int i) { + if (i < -1) { + mv.visitLdcInsn(new Integer(i)); + } else if (i <= 5) { + mv.visitInsn(TypeUtils.ICONST(i)); + } else if (i <= Byte.MAX_VALUE) { + mv.visitIntInsn(Constants.BIPUSH, i); + } else if (i <= Short.MAX_VALUE) { + mv.visitIntInsn(Constants.SIPUSH, i); + } else { + mv.visitLdcInsn(new Integer(i)); + } + } + + public void push(long value) { + if (value == 0L || value == 1L) { + mv.visitInsn(TypeUtils.LCONST(value)); + } else { + mv.visitLdcInsn(new Long(value)); + } + } + + public void push(float value) { + if (value == 0f || value == 1f || value == 2f) { + mv.visitInsn(TypeUtils.FCONST(value)); + } else { + mv.visitLdcInsn(new Float(value)); + } + } + public void push(double value) { + if (value == 0d || value == 1d) { + mv.visitInsn(TypeUtils.DCONST(value)); + } else { + mv.visitLdcInsn(new Double(value)); + } + } + + public void push(String value) { + mv.visitLdcInsn(value); + } + + public void newarray() { + newarray(Constants.TYPE_OBJECT); + } + + public void newarray(Type type) { + if (TypeUtils.isPrimitive(type)) { + mv.visitIntInsn(Constants.NEWARRAY, TypeUtils.NEWARRAY(type)); + } else { + emit_type(Constants.ANEWARRAY, type); + } + } + + public void arraylength() { + mv.visitInsn(Constants.ARRAYLENGTH); + } + + public void load_this() { + if (TypeUtils.isStatic(state.access)) { + throw new IllegalStateException("no 'this' pointer within static method"); + } + mv.visitVarInsn(Constants.ALOAD, 0); + } + + /** + * Pushes all of the arguments of the current method onto the stack. + */ + public void load_args() { + load_args(0, state.argumentTypes.length); + } + + /** + * Pushes the specified argument of the current method onto the stack. + * @param index the zero-based index into the argument list + */ + public void load_arg(int index) { + load_local(state.argumentTypes[index], + state.localOffset + skipArgs(index)); + } + + // zero-based (see load_this) + public void load_args(int fromArg, int count) { + int pos = state.localOffset + skipArgs(fromArg); + for (int i = 0; i < count; i++) { + Type t = state.argumentTypes[fromArg + i]; + load_local(t, pos); + pos += t.getSize(); + } + } + + private int skipArgs(int numArgs) { + int amount = 0; + for (int i = 0; i < numArgs; i++) { + amount += state.argumentTypes[i].getSize(); + } + return amount; + } + + private void load_local(Type t, int pos) { + // TODO: make t == null ok? + mv.visitVarInsn(t.getOpcode(Constants.ILOAD), pos); + } + + private void store_local(Type t, int pos) { + // TODO: make t == null ok? + mv.visitVarInsn(t.getOpcode(Constants.ISTORE), pos); + } + + public void iinc(Local local, int amount) { + mv.visitIincInsn(local.getIndex(), amount); + } + + public void store_local(Local local) { + store_local(local.getType(), local.getIndex()); + } + + public void load_local(Local local) { + load_local(local.getType(), local.getIndex()); + } + + public void return_value() { + mv.visitInsn(state.sig.getReturnType().getOpcode(Constants.IRETURN)); + } + + public void getfield(String name) { + ClassEmitter.FieldInfo info = ce.getFieldInfo(name); + int opcode = TypeUtils.isStatic(info.access) ? Constants.GETSTATIC : Constants.GETFIELD; + emit_field(opcode, ce.getClassType(), name, info.type); + } + + public void putfield(String name) { + ClassEmitter.FieldInfo info = ce.getFieldInfo(name); + int opcode = TypeUtils.isStatic(info.access) ? Constants.PUTSTATIC : Constants.PUTFIELD; + emit_field(opcode, ce.getClassType(), name, info.type); + } + + public void super_getfield(String name, Type type) { + emit_field(Constants.GETFIELD, ce.getSuperType(), name, type); + } + + public void super_putfield(String name, Type type) { + emit_field(Constants.PUTFIELD, ce.getSuperType(), name, type); + } + + public void super_getstatic(String name, Type type) { + emit_field(Constants.GETSTATIC, ce.getSuperType(), name, type); + } + + public void super_putstatic(String name, Type type) { + emit_field(Constants.PUTSTATIC, ce.getSuperType(), name, type); + } + + public void getfield(Type owner, String name, Type type) { + emit_field(Constants.GETFIELD, owner, name, type); + } + + public void putfield(Type owner, String name, Type type) { + emit_field(Constants.PUTFIELD, owner, name, type); + } + + public void getstatic(Type owner, String name, Type type) { + emit_field(Constants.GETSTATIC, owner, name, type); + } + + public void putstatic(Type owner, String name, Type type) { + emit_field(Constants.PUTSTATIC, owner, name, type); + } + + // package-protected for EmitUtils, try to fix + void emit_field(int opcode, Type ctype, String name, Type ftype) { + mv.visitFieldInsn(opcode, + ctype.getInternalName(), + name, + ftype.getDescriptor()); + } + + public void super_invoke() { + super_invoke(state.sig); + } + + public void super_invoke(Signature sig) { + emit_invoke(Constants.INVOKESPECIAL, ce.getSuperType(), sig); + } + + public void invoke_constructor(Type type) { + invoke_constructor(type, CSTRUCT_NULL); + } + + public void super_invoke_constructor() { + invoke_constructor(ce.getSuperType()); + } + + public void invoke_constructor_this() { + invoke_constructor(ce.getClassType()); + } + + private void emit_invoke(int opcode, Type type, Signature sig) { + if (sig.getName().equals(Constants.CONSTRUCTOR_NAME) && + ((opcode == Constants.INVOKEVIRTUAL) || + (opcode == Constants.INVOKESTATIC))) { + // TODO: error + } + mv.visitMethodInsn(opcode, + type.getInternalName(), + sig.getName(), + sig.getDescriptor(), + opcode == Opcodes.INVOKEINTERFACE); + } + + public void invoke_interface(Type owner, Signature sig) { + emit_invoke(Constants.INVOKEINTERFACE, owner, sig); + } + + public void invoke_virtual(Type owner, Signature sig) { + emit_invoke(Constants.INVOKEVIRTUAL, owner, sig); + } + + public void invoke_static(Type owner, Signature sig) { + emit_invoke(Constants.INVOKESTATIC, owner, sig); + } + + public void invoke_virtual_this(Signature sig) { + invoke_virtual(ce.getClassType(), sig); + } + + public void invoke_static_this(Signature sig) { + invoke_static(ce.getClassType(), sig); + } + + public void invoke_constructor(Type type, Signature sig) { + emit_invoke(Constants.INVOKESPECIAL, type, sig); + } + + public void invoke_constructor_this(Signature sig) { + invoke_constructor(ce.getClassType(), sig); + } + + public void super_invoke_constructor(Signature sig) { + invoke_constructor(ce.getSuperType(), sig); + } + + public void new_instance_this() { + new_instance(ce.getClassType()); + } + + public void new_instance(Type type) { + emit_type(Constants.NEW, type); + } + + private void emit_type(int opcode, Type type) { + String desc; + if (TypeUtils.isArray(type)) { + desc = type.getDescriptor(); + } else { + desc = type.getInternalName(); + } + mv.visitTypeInsn(opcode, desc); + } + + public void aaload(int index) { + push(index); + aaload(); + } + + public void aaload() { mv.visitInsn(Constants.AALOAD); } + public void aastore() { mv.visitInsn(Constants.AASTORE); } + public void athrow() { mv.visitInsn(Constants.ATHROW); } + + public Label make_label() { + return new Label(); + } + + public Local make_local() { + return make_local(Constants.TYPE_OBJECT); + } + + public Local make_local(Type type) { + return new Local(newLocal(type.getSize()), type); + } + + public void checkcast_this() { + checkcast(ce.getClassType()); + } + + public void checkcast(Type type) { + if (!type.equals(Constants.TYPE_OBJECT)) { + emit_type(Constants.CHECKCAST, type); + } + } + + public void instance_of(Type type) { + emit_type(Constants.INSTANCEOF, type); + } + + public void instance_of_this() { + instance_of(ce.getClassType()); + } + + public void process_switch(int[] keys, ProcessSwitchCallback callback) { + float density; + if (keys.length == 0) { + density = 0; + } else { + density = (float)keys.length / (keys[keys.length - 1] - keys[0] + 1); + } + process_switch(keys, callback, density >= 0.5f); + } + + public void process_switch(int[] keys, ProcessSwitchCallback callback, boolean useTable) { + if (!isSorted(keys)) + throw new IllegalArgumentException("keys to switch must be sorted ascending"); + Label def = make_label(); + Label end = make_label(); + + try { + if (keys.length > 0) { + int len = keys.length; + int min = keys[0]; + int max = keys[len - 1]; + int range = max - min + 1; + + if (useTable) { + Label[] labels = new Label[range]; + Arrays.fill(labels, def); + for (int i = 0; i < len; i++) { + labels[keys[i] - min] = make_label(); + } + mv.visitTableSwitchInsn(min, max, def, labels); + for (int i = 0; i < range; i++) { + Label label = labels[i]; + if (label != def) { + mark(label); + callback.processCase(i + min, end); + } + } + } else { + Label[] labels = new Label[len]; + for (int i = 0; i < len; i++) { + labels[i] = make_label(); + } + mv.visitLookupSwitchInsn(def, keys, labels); + for (int i = 0; i < len; i++) { + mark(labels[i]); + callback.processCase(keys[i], end); + } + } + } + + mark(def); + callback.processDefault(); + mark(end); + + } catch (RuntimeException e) { + throw e; + } catch (Error e) { + throw e; + } catch (Exception e) { + throw new CodeGenerationException(e); + } + } + + private static boolean isSorted(int[] keys) { + for (int i = 1; i < keys.length; i++) { + if (keys[i] < keys[i - 1]) + return false; + } + return true; + } + + public void mark(Label label) { + mv.visitLabel(label); + } + + Label mark() { + Label label = make_label(); + mv.visitLabel(label); + return label; + } + + public void push(boolean value) { + push(value ? 1 : 0); + } + + /** + * Toggles the integer on the top of the stack from 1 to 0 or vice versa + */ + public void not() { + push(1); + math(XOR, Type.INT_TYPE); + } + + public void throw_exception(Type type, String msg) { + new_instance(type); + dup(); + push(msg); + invoke_constructor(type, CSTRUCT_STRING); + athrow(); + } + + /** + * If the argument is a primitive class, replaces the primitive value + * on the top of the stack with the wrapped (Object) equivalent. For + * example, char -> Character. + * If the class is Void, a null is pushed onto the stack instead. + * @param type the class indicating the current type of the top stack value + */ + public void box(Type type) { + if (TypeUtils.isPrimitive(type)) { + if (type == Type.VOID_TYPE) { + aconst_null(); + } else { + Type boxed = TypeUtils.getBoxedType(type); + new_instance(boxed); + if (type.getSize() == 2) { + // Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o + dup_x2(); + dup_x2(); + pop(); + } else { + // p -> po -> opo -> oop -> o + dup_x1(); + swap(); + } + invoke_constructor(boxed, new Signature(Constants.CONSTRUCTOR_NAME, Type.VOID_TYPE, new Type[]{ type })); + } + } + } + + /** + * If the argument is a primitive class, replaces the object + * on the top of the stack with the unwrapped (primitive) + * equivalent. For example, Character -> char. + * @param type the class indicating the desired type of the top stack value + * @return true if the value was unboxed + */ + public void unbox(Type type) { + Type t = Constants.TYPE_NUMBER; + Signature sig = null; + switch (type.getSort()) { + case Type.VOID: + return; + case Type.CHAR: + t = Constants.TYPE_CHARACTER; + sig = CHAR_VALUE; + break; + case Type.BOOLEAN: + t = Constants.TYPE_BOOLEAN; + sig = BOOLEAN_VALUE; + break; + case Type.DOUBLE: + sig = DOUBLE_VALUE; + break; + case Type.FLOAT: + sig = FLOAT_VALUE; + break; + case Type.LONG: + sig = LONG_VALUE; + break; + case Type.INT: + case Type.SHORT: + case Type.BYTE: + sig = INT_VALUE; + } + + if (sig == null) { + checkcast(type); + } else { + checkcast(t); + invoke_virtual(t, sig); + } + } + + /** + * Allocates and fills an Object[] array with the arguments to the + * current method. Primitive values are inserted as their boxed + * (Object) equivalents. + */ + public void create_arg_array() { + /* generates: + Object[] args = new Object[]{ arg1, new Integer(arg2) }; + */ + + push(state.argumentTypes.length); + newarray(); + for (int i = 0; i < state.argumentTypes.length; i++) { + dup(); + push(i); + load_arg(i); + box(state.argumentTypes[i]); + aastore(); + } + } + + + /** + * Pushes a zero onto the stack if the argument is a primitive class, or a null otherwise. + */ + public void zero_or_null(Type type) { + if (TypeUtils.isPrimitive(type)) { + switch (type.getSort()) { + case Type.DOUBLE: + push(0d); + break; + case Type.LONG: + push(0L); + break; + case Type.FLOAT: + push(0f); + break; + case Type.VOID: + aconst_null(); + default: + push(0); + } + } else { + aconst_null(); + } + } + + /** + * Unboxes the object on the top of the stack. If the object is null, the + * unboxed primitive value becomes zero. + */ + public void unbox_or_zero(Type type) { + if (TypeUtils.isPrimitive(type)) { + if (type != Type.VOID_TYPE) { + Label nonNull = make_label(); + Label end = make_label(); + dup(); + ifnonnull(nonNull); + pop(); + zero_or_null(type); + goTo(end); + mark(nonNull); + unbox(type); + mark(end); + } + } else { + checkcast(type); + } + } + + public void visitMaxs(int maxStack, int maxLocals) { + if (!TypeUtils.isAbstract(state.access)) { + mv.visitMaxs(0, 0); + } + } + + public void invoke(MethodInfo method, Type virtualType) { + ClassInfo classInfo = method.getClassInfo(); + Type type = classInfo.getType(); + Signature sig = method.getSignature(); + if (sig.getName().equals(Constants.CONSTRUCTOR_NAME)) { + invoke_constructor(type, sig); + } else if (TypeUtils.isInterface(classInfo.getModifiers())) { + invoke_interface(type, sig); + } else if (TypeUtils.isStatic(method.getModifiers())) { + invoke_static(type, sig); + } else { + invoke_virtual(virtualType, sig); + } + } + + public void invoke(MethodInfo method) { + invoke(method, method.getClassInfo().getType()); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/CodeGenerationException.java b/blade-aop/src/main/java/net/sf/cglib/core/CodeGenerationException.java new file mode 100644 index 000000000..14824e29d --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/CodeGenerationException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +/** + * @version $Id: CodeGenerationException.java,v 1.3 2004/06/24 21:15:21 herbyderby Exp $ + */ +public class CodeGenerationException extends RuntimeException { + private Throwable cause; + + public CodeGenerationException(Throwable cause) { + super(cause.getClass().getName() + "-->" + cause.getMessage()); + this.cause = cause; + } + + public Throwable getCause() { + return cause; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/CollectionUtils.java b/blade-aop/src/main/java/net/sf/cglib/core/CollectionUtils.java new file mode 100644 index 000000000..60e5140fe --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/CollectionUtils.java @@ -0,0 +1,76 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import java.util.*; +import java.lang.reflect.Array; + +/** + * @author Chris Nokleberg + * @version $Id: CollectionUtils.java,v 1.7 2004/06/24 21:15:21 herbyderby Exp $ + */ +public class CollectionUtils { + private CollectionUtils() { } + + public static Map bucket(Collection c, Transformer t) { + Map buckets = new HashMap(); + for (Iterator it = c.iterator(); it.hasNext();) { + Object value = (Object)it.next(); + Object key = t.transform(value); + List bucket = (List)buckets.get(key); + if (bucket == null) { + buckets.put(key, bucket = new LinkedList()); + } + bucket.add(value); + } + return buckets; + } + + public static void reverse(Map source, Map target) { + for (Iterator it = source.keySet().iterator(); it.hasNext();) { + Object key = it.next(); + target.put(source.get(key), key); + } + } + + public static Collection filter(Collection c, Predicate p) { + Iterator it = c.iterator(); + while (it.hasNext()) { + if (!p.evaluate(it.next())) { + it.remove(); + } + } + return c; + } + + public static List transform(Collection c, Transformer t) { + List result = new ArrayList(c.size()); + for (Iterator it = c.iterator(); it.hasNext();) { + result.add(t.transform(it.next())); + } + return result; + } + + public static Map getIndexMap(List list) { + Map indexes = new HashMap(); + int index = 0; + for (Iterator it = list.iterator(); it.hasNext();) { + indexes.put(it.next(), new Integer(index++)); + } + return indexes; + } +} + diff --git a/blade-aop/src/main/java/net/sf/cglib/core/Constants.java b/blade-aop/src/main/java/net/sf/cglib/core/Constants.java new file mode 100644 index 000000000..c652bc568 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/Constants.java @@ -0,0 +1,68 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.Type; + +/** + * @author Juozas Baliuka baliuka@mwm.lt + * @version $Id: Constants.java,v 1.21 2006/03/05 02:43:19 herbyderby Exp $ + */ +public interface Constants extends org.objectweb.asm.Opcodes { + public static final Class[] EMPTY_CLASS_ARRAY = {}; + public static final Type[] TYPES_EMPTY = {}; + + public static final Signature SIG_STATIC = + TypeUtils.parseSignature("void ()"); + + public static final Type TYPE_OBJECT_ARRAY = TypeUtils.parseType("Object[]"); + public static final Type TYPE_CLASS_ARRAY = TypeUtils.parseType("Class[]"); + public static final Type TYPE_STRING_ARRAY = TypeUtils.parseType("String[]"); + + public static final Type TYPE_OBJECT = TypeUtils.parseType("Object"); + public static final Type TYPE_CLASS = TypeUtils.parseType("Class"); + public static final Type TYPE_CLASS_LOADER = TypeUtils.parseType("ClassLoader"); + public static final Type TYPE_CHARACTER = TypeUtils.parseType("Character"); + public static final Type TYPE_BOOLEAN = TypeUtils.parseType("Boolean"); + public static final Type TYPE_DOUBLE = TypeUtils.parseType("Double"); + public static final Type TYPE_FLOAT = TypeUtils.parseType("Float"); + public static final Type TYPE_LONG = TypeUtils.parseType("Long"); + public static final Type TYPE_INTEGER = TypeUtils.parseType("Integer"); + public static final Type TYPE_SHORT = TypeUtils.parseType("Short"); + public static final Type TYPE_BYTE = TypeUtils.parseType("Byte"); + public static final Type TYPE_NUMBER = TypeUtils.parseType("Number"); + public static final Type TYPE_STRING = TypeUtils.parseType("String"); + public static final Type TYPE_THROWABLE = TypeUtils.parseType("Throwable"); + public static final Type TYPE_BIG_INTEGER = TypeUtils.parseType("java.math.BigInteger"); + public static final Type TYPE_BIG_DECIMAL = TypeUtils.parseType("java.math.BigDecimal"); + public static final Type TYPE_STRING_BUFFER = TypeUtils.parseType("StringBuffer"); + public static final Type TYPE_RUNTIME_EXCEPTION = TypeUtils.parseType("RuntimeException"); + public static final Type TYPE_ERROR = TypeUtils.parseType("Error"); + public static final Type TYPE_SYSTEM = TypeUtils.parseType("System"); + public static final Type TYPE_SIGNATURE = TypeUtils.parseType("net.sf.cglib.core.Signature"); + public static final Type TYPE_TYPE = Type.getType(Type.class); + + public static final String CONSTRUCTOR_NAME = ""; + public static final String STATIC_NAME = ""; + public static final String SOURCE_FILE = ""; + public static final String SUID_FIELD_NAME = "serialVersionUID"; + + public static final int PRIVATE_FINAL_STATIC = ACC_PRIVATE | ACC_FINAL | ACC_STATIC; + + public static final int SWITCH_STYLE_TRIE = 0; + public static final int SWITCH_STYLE_HASH = 1; + public static final int SWITCH_STYLE_HASHONLY = 2; +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/Converter.java b/blade-aop/src/main/java/net/sf/cglib/core/Converter.java new file mode 100644 index 000000000..00414358d --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/Converter.java @@ -0,0 +1,20 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +public interface Converter { + Object convert(Object value, Class target, Object context); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/Customizer.java b/blade-aop/src/main/java/net/sf/cglib/core/Customizer.java new file mode 100644 index 000000000..4297b163a --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/Customizer.java @@ -0,0 +1,28 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.Type; + +/** + * Customizes key types for {@link KeyFactory} when building equals, hashCode, and toString. + * For customization of field types, use {@link FieldTypeCustomizer} + * + * @see KeyFactory#CLASS_BY_NAME + */ +public interface Customizer extends KeyFactoryCustomizer { + void customize(CodeEmitter e, Type type); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/DebuggingClassWriter.java b/blade-aop/src/main/java/net/sf/cglib/core/DebuggingClassWriter.java new file mode 100644 index 000000000..78f1ae7d4 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/DebuggingClassWriter.java @@ -0,0 +1,114 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; + +import java.io.*; +import java.lang.reflect.Constructor; + +public class DebuggingClassWriter extends ClassVisitor { + + public static final String DEBUG_LOCATION_PROPERTY = "cglib.debugLocation"; + + private static String debugLocation; + private static Constructor traceCtor; + + private String className; + private String superName; + + static { + debugLocation = System.getProperty(DEBUG_LOCATION_PROPERTY); + if (debugLocation != null) { + System.err.println("CGLIB debugging enabled, writing to '" + debugLocation + "'"); + try { + Class clazz = Class.forName("org.objectweb.asm.util.TraceClassVisitor"); + traceCtor = clazz.getConstructor(new Class[]{ClassVisitor.class, PrintWriter.class}); + } catch (Throwable ignore) { + } + } + } + + public DebuggingClassWriter(int flags) { + super(Opcodes.ASM5, new ClassWriter(flags)); + } + + public void visit(int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + className = name.replace('/', '.'); + this.superName = superName.replace('/', '.'); + super.visit(version, access, name, signature, superName, interfaces); + } + + public String getClassName() { + return className; + } + + public String getSuperName() { + return superName; + } + + public byte[] toByteArray() { + + return (byte[]) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + + + byte[] b = ((ClassWriter) DebuggingClassWriter.super.cv).toByteArray(); + if (debugLocation != null) { + String dirs = className.replace('.', File.separatorChar); + try { + new File(debugLocation + File.separatorChar + dirs).getParentFile().mkdirs(); + + File file = new File(new File(debugLocation), dirs + ".class"); + OutputStream out = new BufferedOutputStream(new FileOutputStream(file)); + try { + out.write(b); + } finally { + out.close(); + } + + if (traceCtor != null) { + file = new File(new File(debugLocation), dirs + ".asm"); + out = new BufferedOutputStream(new FileOutputStream(file)); + try { + ClassReader cr = new ClassReader(b); + PrintWriter pw = new PrintWriter(new OutputStreamWriter(out)); + ClassVisitor tcv = (ClassVisitor)traceCtor.newInstance(new Object[]{null, pw}); + cr.accept(tcv, 0); + pw.flush(); + } finally { + out.close(); + } + } + } catch (Exception e) { + throw new CodeGenerationException(e); + } + } + return b; + } + }); + + } + } diff --git a/blade-aop/src/main/java/net/sf/cglib/core/DefaultGeneratorStrategy.java b/blade-aop/src/main/java/net/sf/cglib/core/DefaultGeneratorStrategy.java new file mode 100644 index 000000000..dd876c760 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/DefaultGeneratorStrategy.java @@ -0,0 +1,47 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.ClassWriter; + +public class DefaultGeneratorStrategy implements GeneratorStrategy { + public static final DefaultGeneratorStrategy INSTANCE = new DefaultGeneratorStrategy(); + + public byte[] generate(ClassGenerator cg) throws Exception { + DebuggingClassWriter cw = getClassVisitor(); + transform(cg).generateClass(cw); + return transform(cw.toByteArray()); + } + + protected DebuggingClassWriter getClassVisitor() throws Exception { + return new DebuggingClassWriter(ClassWriter.COMPUTE_FRAMES); + } + + protected final ClassWriter getClassWriter() { + // Cause compile / runtime errors for people who implemented the old + // interface without using @Override + throw new UnsupportedOperationException("You are calling " + + "getClassWriter, which no longer exists in this cglib version."); + } + + protected byte[] transform(byte[] b) throws Exception { + return b; + } + + protected ClassGenerator transform(ClassGenerator cg) throws Exception { + return cg; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/DefaultNamingPolicy.java b/blade-aop/src/main/java/net/sf/cglib/core/DefaultNamingPolicy.java new file mode 100644 index 000000000..dfdb6ab63 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/DefaultNamingPolicy.java @@ -0,0 +1,71 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import java.util.Set; + +/** + * The default policy used by {@link AbstractClassGenerator}. + * Generates names such as + *

net.sf.cglib.Foo$$EnhancerByCGLIB$$38272841

+ * This is composed of a prefix based on the name of the superclass, a fixed + * string incorporating the CGLIB class responsible for generation, and a + * hashcode derived from the parameters used to create the object. If the same + * name has been previously been used in the same ClassLoader, a + * suffix is added to ensure uniqueness. + */ +public class DefaultNamingPolicy implements NamingPolicy { + public static final DefaultNamingPolicy INSTANCE = new DefaultNamingPolicy(); + + /** + * This allows to test collisions of {@code key.hashCode()}. + */ + private final static boolean STRESS_HASH_CODE = Boolean.getBoolean("net.sf.cglib.test.stressHashCodes"); + + public String getClassName(String prefix, String source, Object key, Predicate names) { + if (prefix == null) { + prefix = "net.sf.cglib.empty.Object"; + } else if (prefix.startsWith("java")) { + prefix = "$" + prefix; + } + String base = + prefix + "$$" + + source.substring(source.lastIndexOf('.') + 1) + + getTag() + "$$" + + Integer.toHexString(STRESS_HASH_CODE ? 0 : key.hashCode()); + String attempt = base; + int index = 2; + while (names.evaluate(attempt)) + attempt = base + "_" + index++; + return attempt; + } + + /** + * Returns a string which is incorporated into every generated class name. + * By default returns "ByCGLIB" + */ + protected String getTag() { + return "ByCGLIB"; + } + + public int hashCode() { + return getTag().hashCode(); + } + + public boolean equals(Object o) { + return (o instanceof DefaultNamingPolicy) && ((DefaultNamingPolicy) o).getTag().equals(getTag()); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/DuplicatesPredicate.java b/blade-aop/src/main/java/net/sf/cglib/core/DuplicatesPredicate.java new file mode 100644 index 000000000..6ebee2c35 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/DuplicatesPredicate.java @@ -0,0 +1,27 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import java.lang.reflect.Method; +import java.util.*; + +public class DuplicatesPredicate implements Predicate { + private Set unique = new HashSet(); + + public boolean evaluate(Object arg) { + return unique.add(MethodWrapper.create((Method)arg)); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/EmitUtils.java b/blade-aop/src/main/java/net/sf/cglib/core/EmitUtils.java new file mode 100644 index 000000000..07bdab156 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/EmitUtils.java @@ -0,0 +1,960 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.*; + +import net.sf.cglib.core.internal.CustomizerRegistry; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; + +public class EmitUtils { + private static final Signature CSTRUCT_NULL = + TypeUtils.parseConstructor(""); + private static final Signature CSTRUCT_THROWABLE = + TypeUtils.parseConstructor("Throwable"); + + private static final Signature GET_NAME = + TypeUtils.parseSignature("String getName()"); + private static final Signature HASH_CODE = + TypeUtils.parseSignature("int hashCode()"); + private static final Signature EQUALS = + TypeUtils.parseSignature("boolean equals(Object)"); + private static final Signature STRING_LENGTH = + TypeUtils.parseSignature("int length()"); + private static final Signature STRING_CHAR_AT = + TypeUtils.parseSignature("char charAt(int)"); + private static final Signature FOR_NAME = + TypeUtils.parseSignature("Class forName(String)"); + private static final Signature DOUBLE_TO_LONG_BITS = + TypeUtils.parseSignature("long doubleToLongBits(double)"); + private static final Signature FLOAT_TO_INT_BITS = + TypeUtils.parseSignature("int floatToIntBits(float)"); + private static final Signature TO_STRING = + TypeUtils.parseSignature("String toString()"); + private static final Signature APPEND_STRING = + TypeUtils.parseSignature("StringBuffer append(String)"); + private static final Signature APPEND_INT = + TypeUtils.parseSignature("StringBuffer append(int)"); + private static final Signature APPEND_DOUBLE = + TypeUtils.parseSignature("StringBuffer append(double)"); + private static final Signature APPEND_FLOAT = + TypeUtils.parseSignature("StringBuffer append(float)"); + private static final Signature APPEND_CHAR = + TypeUtils.parseSignature("StringBuffer append(char)"); + private static final Signature APPEND_LONG = + TypeUtils.parseSignature("StringBuffer append(long)"); + private static final Signature APPEND_BOOLEAN = + TypeUtils.parseSignature("StringBuffer append(boolean)"); + private static final Signature LENGTH = + TypeUtils.parseSignature("int length()"); + private static final Signature SET_LENGTH = + TypeUtils.parseSignature("void setLength(int)"); + private static final Signature GET_DECLARED_METHOD = + TypeUtils.parseSignature("java.lang.reflect.Method getDeclaredMethod(String, Class[])"); + + + + public static final ArrayDelimiters DEFAULT_DELIMITERS = new ArrayDelimiters("{", ", ", "}"); + + private EmitUtils() { + } + + public static void factory_method(ClassEmitter ce, Signature sig) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, sig, null); + e.new_instance_this(); + e.dup(); + e.load_args(); + e.invoke_constructor_this(TypeUtils.parseConstructor(sig.getArgumentTypes())); + e.return_value(); + e.end_method(); + } + + public static void null_constructor(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, CSTRUCT_NULL, null); + e.load_this(); + e.super_invoke_constructor(); + e.return_value(); + e.end_method(); + } + + /** + * Process an array on the stack. Assumes the top item on the stack + * is an array of the specified type. For each element in the array, + * puts the element on the stack and triggers the callback. + * @param type the type of the array (type.isArray() must be true) + * @param callback the callback triggered for each element + */ + public static void process_array(CodeEmitter e, Type type, ProcessArrayCallback callback) { + Type componentType = TypeUtils.getComponentType(type); + Local array = e.make_local(); + Local loopvar = e.make_local(Type.INT_TYPE); + Label loopbody = e.make_label(); + Label checkloop = e.make_label(); + e.store_local(array); + e.push(0); + e.store_local(loopvar); + e.goTo(checkloop); + + e.mark(loopbody); + e.load_local(array); + e.load_local(loopvar); + e.array_load(componentType); + callback.processElement(componentType); + e.iinc(loopvar, 1); + + e.mark(checkloop); + e.load_local(loopvar); + e.load_local(array); + e.arraylength(); + e.if_icmp(e.LT, loopbody); + } + + /** + * Process two arrays on the stack in parallel. Assumes the top two items on the stack + * are arrays of the specified class. The arrays must be the same length. For each pair + * of elements in the arrays, puts the pair on the stack and triggers the callback. + * @param type the type of the arrays (type.isArray() must be true) + * @param callback the callback triggered for each pair of elements + */ + public static void process_arrays(CodeEmitter e, Type type, ProcessArrayCallback callback) { + Type componentType = TypeUtils.getComponentType(type); + Local array1 = e.make_local(); + Local array2 = e.make_local(); + Local loopvar = e.make_local(Type.INT_TYPE); + Label loopbody = e.make_label(); + Label checkloop = e.make_label(); + e.store_local(array1); + e.store_local(array2); + e.push(0); + e.store_local(loopvar); + e.goTo(checkloop); + + e.mark(loopbody); + e.load_local(array1); + e.load_local(loopvar); + e.array_load(componentType); + e.load_local(array2); + e.load_local(loopvar); + e.array_load(componentType); + callback.processElement(componentType); + e.iinc(loopvar, 1); + + e.mark(checkloop); + e.load_local(loopvar); + e.load_local(array1); + e.arraylength(); + e.if_icmp(e.LT, loopbody); + } + + public static void string_switch(CodeEmitter e, String[] strings, int switchStyle, ObjectSwitchCallback callback) { + try { + switch (switchStyle) { + case Constants.SWITCH_STYLE_TRIE: + string_switch_trie(e, strings, callback); + break; + case Constants.SWITCH_STYLE_HASH: + string_switch_hash(e, strings, callback, false); + break; + case Constants.SWITCH_STYLE_HASHONLY: + string_switch_hash(e, strings, callback, true); + break; + default: + throw new IllegalArgumentException("unknown switch style " + switchStyle); + } + } catch (RuntimeException ex) { + throw ex; + } catch (Error ex) { + throw ex; + } catch (Exception ex) { + throw new CodeGenerationException(ex); + } + } + + private static void string_switch_trie(final CodeEmitter e, + String[] strings, + final ObjectSwitchCallback callback) throws Exception { + final Label def = e.make_label(); + final Label end = e.make_label(); + final Map buckets = CollectionUtils.bucket(Arrays.asList(strings), new Transformer() { + public Object transform(Object value) { + return new Integer(((String)value).length()); + } + }); + e.dup(); + e.invoke_virtual(Constants.TYPE_STRING, STRING_LENGTH); + e.process_switch(getSwitchKeys(buckets), new ProcessSwitchCallback() { + public void processCase(int key, Label ignore_end) throws Exception { + List bucket = (List)buckets.get(new Integer(key)); + stringSwitchHelper(e, bucket, callback, def, end, 0); + } + public void processDefault() { + e.goTo(def); + } + }); + e.mark(def); + e.pop(); + callback.processDefault(); + e.mark(end); + } + + private static void stringSwitchHelper(final CodeEmitter e, + List strings, + final ObjectSwitchCallback callback, + final Label def, + final Label end, + final int index) throws Exception { + final int len = ((String)strings.get(0)).length(); + final Map buckets = CollectionUtils.bucket(strings, new Transformer() { + public Object transform(Object value) { + return new Integer(((String)value).charAt(index)); + } + }); + e.dup(); + e.push(index); + e.invoke_virtual(Constants.TYPE_STRING, STRING_CHAR_AT); + e.process_switch(getSwitchKeys(buckets), new ProcessSwitchCallback() { + public void processCase(int key, Label ignore_end) throws Exception { + List bucket = (List)buckets.get(new Integer(key)); + if (index + 1 == len) { + e.pop(); + callback.processCase(bucket.get(0), end); + } else { + stringSwitchHelper(e, bucket, callback, def, end, index + 1); + } + } + public void processDefault() { + e.goTo(def); + } + }); + } + + static int[] getSwitchKeys(Map buckets) { + int[] keys = new int[buckets.size()]; + int index = 0; + for (Iterator it = buckets.keySet().iterator(); it.hasNext();) { + keys[index++] = ((Integer)it.next()).intValue(); + } + Arrays.sort(keys); + return keys; + } + + private static void string_switch_hash(final CodeEmitter e, + final String[] strings, + final ObjectSwitchCallback callback, + final boolean skipEquals) throws Exception { + final Map buckets = CollectionUtils.bucket(Arrays.asList(strings), new Transformer() { + public Object transform(Object value) { + return new Integer(value.hashCode()); + } + }); + final Label def = e.make_label(); + final Label end = e.make_label(); + e.dup(); + e.invoke_virtual(Constants.TYPE_OBJECT, HASH_CODE); + e.process_switch(getSwitchKeys(buckets), new ProcessSwitchCallback() { + public void processCase(int key, Label ignore_end) throws Exception { + List bucket = (List)buckets.get(new Integer(key)); + Label next = null; + if (skipEquals && bucket.size() == 1) { + if (skipEquals) + e.pop(); + callback.processCase((String)bucket.get(0), end); + } else { + for (Iterator it = bucket.iterator(); it.hasNext();) { + String string = (String)it.next(); + if (next != null) { + e.mark(next); + } + if (it.hasNext()) { + e.dup(); + } + e.push(string); + e.invoke_virtual(Constants.TYPE_OBJECT, EQUALS); + if (it.hasNext()) { + e.if_jump(e.EQ, next = e.make_label()); + e.pop(); + } else { + e.if_jump(e.EQ, def); + } + callback.processCase(string, end); + } + } + } + public void processDefault() { + e.pop(); + } + }); + e.mark(def); + callback.processDefault(); + e.mark(end); + } + + public static void load_class_this(CodeEmitter e) { + load_class_helper(e, e.getClassEmitter().getClassType()); + } + + public static void load_class(CodeEmitter e, Type type) { + if (TypeUtils.isPrimitive(type)) { + if (type == Type.VOID_TYPE) { + throw new IllegalArgumentException("cannot load void type"); + } + e.getstatic(TypeUtils.getBoxedType(type), "TYPE", Constants.TYPE_CLASS); + } else { + load_class_helper(e, type); + } + } + + private static void load_class_helper(CodeEmitter e, final Type type) { + if (e.isStaticHook()) { + // have to fall back on non-optimized load + e.push(TypeUtils.emulateClassGetName(type)); + e.invoke_static(Constants.TYPE_CLASS, FOR_NAME); + } else { + ClassEmitter ce = e.getClassEmitter(); + String typeName = TypeUtils.emulateClassGetName(type); + + // TODO: can end up with duplicated field names when using chained transformers; incorporate static hook # somehow + String fieldName = "CGLIB$load_class$" + TypeUtils.escapeType(typeName); + if (!ce.isFieldDeclared(fieldName)) { + ce.declare_field(Constants.PRIVATE_FINAL_STATIC, fieldName, Constants.TYPE_CLASS, null); + CodeEmitter hook = ce.getStaticHook(); + hook.push(typeName); + hook.invoke_static(Constants.TYPE_CLASS, FOR_NAME); + hook.putstatic(ce.getClassType(), fieldName, Constants.TYPE_CLASS); + } + e.getfield(fieldName); + } + } + + public static void push_array(CodeEmitter e, Object[] array) { + e.push(array.length); + e.newarray(Type.getType(remapComponentType(array.getClass().getComponentType()))); + for (int i = 0; i < array.length; i++) { + e.dup(); + e.push(i); + push_object(e, array[i]); + e.aastore(); + } + } + + private static Class remapComponentType(Class componentType) { + if (componentType.equals(Type.class)) + return Class.class; + return componentType; + } + + public static void push_object(CodeEmitter e, Object obj) { + if (obj == null) { + e.aconst_null(); + } else { + Class type = obj.getClass(); + if (type.isArray()) { + push_array(e, (Object[])obj); + } else if (obj instanceof String) { + e.push((String)obj); + } else if (obj instanceof Type) { + load_class(e, (Type)obj); + } else if (obj instanceof Class) { + load_class(e, Type.getType((Class)obj)); + } else if (obj instanceof BigInteger) { + e.new_instance(Constants.TYPE_BIG_INTEGER); + e.dup(); + e.push(obj.toString()); + e.invoke_constructor(Constants.TYPE_BIG_INTEGER); + } else if (obj instanceof BigDecimal) { + e.new_instance(Constants.TYPE_BIG_DECIMAL); + e.dup(); + e.push(obj.toString()); + e.invoke_constructor(Constants.TYPE_BIG_DECIMAL); + } else { + throw new IllegalArgumentException("unknown type: " + obj.getClass()); + } + } + } + + /** + * @deprecated use {@link #hash_code(CodeEmitter, Type, int, CustomizerRegistry)} instead + */ + @Deprecated + public static void hash_code(CodeEmitter e, Type type, int multiplier, final Customizer customizer) { + hash_code(e, type, multiplier, CustomizerRegistry.singleton(customizer)); + } + + public static void hash_code(CodeEmitter e, Type type, int multiplier, final CustomizerRegistry registry) { + if (TypeUtils.isArray(type)) { + hash_array(e, type, multiplier, registry); + } else { + e.swap(Type.INT_TYPE, type); + e.push(multiplier); + e.math(e.MUL, Type.INT_TYPE); + e.swap(type, Type.INT_TYPE); + if (TypeUtils.isPrimitive(type)) { + hash_primitive(e, type); + } else { + hash_object(e, type, registry); + } + e.math(e.ADD, Type.INT_TYPE); + } + } + + private static void hash_array(final CodeEmitter e, Type type, final int multiplier, final CustomizerRegistry registry) { + Label skip = e.make_label(); + Label end = e.make_label(); + e.dup(); + e.ifnull(skip); + EmitUtils.process_array(e, type, new ProcessArrayCallback() { + public void processElement(Type type) { + hash_code(e, type, multiplier, registry); + } + }); + e.goTo(end); + e.mark(skip); + e.pop(); + e.mark(end); + } + + private static void hash_object(CodeEmitter e, Type type, CustomizerRegistry registry) { + // (f == null) ? 0 : f.hashCode(); + Label skip = e.make_label(); + Label end = e.make_label(); + e.dup(); + e.ifnull(skip); + boolean customHashCode = false; + for (HashCodeCustomizer customizer : registry.get(HashCodeCustomizer.class)) { + if (customizer.customize(e, type)) { + customHashCode = true; + break; + } + } + if (!customHashCode) { + for (Customizer customizer : registry.get(Customizer.class)) { + customizer.customize(e, type); + } + e.invoke_virtual(Constants.TYPE_OBJECT, HASH_CODE); + } + e.goTo(end); + e.mark(skip); + e.pop(); + e.push(0); + e.mark(end); + } + + private static void hash_primitive(CodeEmitter e, Type type) { + switch (type.getSort()) { + case Type.BOOLEAN: + // f ? 0 : 1 + e.push(1); + e.math(e.XOR, Type.INT_TYPE); + break; + case Type.FLOAT: + // Float.floatToIntBits(f) + e.invoke_static(Constants.TYPE_FLOAT, FLOAT_TO_INT_BITS); + break; + case Type.DOUBLE: + // Double.doubleToLongBits(f), hash_code(Long.TYPE) + e.invoke_static(Constants.TYPE_DOUBLE, DOUBLE_TO_LONG_BITS); + // fall through + case Type.LONG: + hash_long(e); + } + } + + private static void hash_long(CodeEmitter e) { + // (int)(f ^ (f >>> 32)) + e.dup2(); + e.push(32); + e.math(e.USHR, Type.LONG_TYPE); + e.math(e.XOR, Type.LONG_TYPE); + e.cast_numeric(Type.LONG_TYPE, Type.INT_TYPE); + } + +// public static void not_equals(CodeEmitter e, Type type, Label notEquals) { +// not_equals(e, type, notEquals, null); +// } + + /** + * @deprecated use {@link #not_equals(CodeEmitter, Type, Label, CustomizerRegistry)} instead + */ + @Deprecated + public static void not_equals(CodeEmitter e, Type type, final Label notEquals, final Customizer customizer) { + not_equals(e, type, notEquals, CustomizerRegistry.singleton(customizer)); + } + + /** + * Branches to the specified label if the top two items on the stack + * are not equal. The items must both be of the specified + * class. Equality is determined by comparing primitive values + * directly and by invoking the equals method for + * Objects. Arrays are recursively processed in the same manner. + */ + public static void not_equals(final CodeEmitter e, Type type, final Label notEquals, final CustomizerRegistry registry) { + (new ProcessArrayCallback() { + public void processElement(Type type) { + not_equals_helper(e, type, notEquals, registry, this); + } + }).processElement(type); + } + + private static void not_equals_helper(CodeEmitter e, + Type type, + Label notEquals, + CustomizerRegistry registry, + ProcessArrayCallback callback) { + if (TypeUtils.isPrimitive(type)) { + e.if_cmp(type, e.NE, notEquals); + } else { + Label end = e.make_label(); + nullcmp(e, notEquals, end); + if (TypeUtils.isArray(type)) { + Label checkContents = e.make_label(); + e.dup2(); + e.arraylength(); + e.swap(); + e.arraylength(); + e.if_icmp(e.EQ, checkContents); + e.pop2(); + e.goTo(notEquals); + e.mark(checkContents); + EmitUtils.process_arrays(e, type, callback); + } else { + List customizers = registry.get(Customizer.class); + if (!customizers.isEmpty()) { + for (Customizer customizer : customizers) { + customizer.customize(e, type); + } + e.swap(); + for (Customizer customizer : customizers) { + customizer.customize(e, type); + } + } + e.invoke_virtual(Constants.TYPE_OBJECT, EQUALS); + e.if_jump(e.EQ, notEquals); + } + e.mark(end); + } + } + + /** + * If both objects on the top of the stack are non-null, does nothing. + * If one is null, or both are null, both are popped off and execution + * branches to the respective label. + * @param oneNull label to branch to if only one of the objects is null + * @param bothNull label to branch to if both of the objects are null + */ + private static void nullcmp(CodeEmitter e, Label oneNull, Label bothNull) { + e.dup2(); + Label nonNull = e.make_label(); + Label oneNullHelper = e.make_label(); + Label end = e.make_label(); + e.ifnonnull(nonNull); + e.ifnonnull(oneNullHelper); + e.pop2(); + e.goTo(bothNull); + + e.mark(nonNull); + e.ifnull(oneNullHelper); + e.goTo(end); + + e.mark(oneNullHelper); + e.pop2(); + e.goTo(oneNull); + + e.mark(end); + } + + /* + public static void to_string(CodeEmitter e, + Type type, + ArrayDelimiters delims, + CustomizerRegistry registry) { + e.new_instance(Constants.TYPE_STRING_BUFFER); + e.dup(); + e.invoke_constructor(Constants.TYPE_STRING_BUFFER); + e.swap(); + append_string(e, type, delims, registry); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, TO_STRING); + } + */ + + /** + * @deprecated use {@link #append_string(CodeEmitter, Type, ArrayDelimiters, CustomizerRegistry)} instead + */ + @Deprecated + public static void append_string(final CodeEmitter e, + Type type, + final ArrayDelimiters delims, + final Customizer customizer) { + append_string(e, type, delims, CustomizerRegistry.singleton(customizer)); + } + + public static void append_string(final CodeEmitter e, + Type type, + final ArrayDelimiters delims, + final CustomizerRegistry registry) { + final ArrayDelimiters d = (delims != null) ? delims : DEFAULT_DELIMITERS; + ProcessArrayCallback callback = new ProcessArrayCallback() { + public void processElement(Type type) { + append_string_helper(e, type, d, registry, this); + e.push(d.inside); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING); + } + }; + append_string_helper(e, type, d, registry, callback); + } + + private static void append_string_helper(CodeEmitter e, + Type type, + ArrayDelimiters delims, + CustomizerRegistry registry, + ProcessArrayCallback callback) { + Label skip = e.make_label(); + Label end = e.make_label(); + if (TypeUtils.isPrimitive(type)) { + switch (type.getSort()) { + case Type.INT: + case Type.SHORT: + case Type.BYTE: + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_INT); + break; + case Type.DOUBLE: + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_DOUBLE); + break; + case Type.FLOAT: + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_FLOAT); + break; + case Type.LONG: + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_LONG); + break; + case Type.BOOLEAN: + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_BOOLEAN); + break; + case Type.CHAR: + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_CHAR); + break; + } + } else if (TypeUtils.isArray(type)) { + e.dup(); + e.ifnull(skip); + e.swap(); + if (delims != null && delims.before != null && !"".equals(delims.before)) { + e.push(delims.before); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING); + e.swap(); + } + EmitUtils.process_array(e, type, callback); + shrinkStringBuffer(e, 2); + if (delims != null && delims.after != null && !"".equals(delims.after)) { + e.push(delims.after); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING); + } + } else { + e.dup(); + e.ifnull(skip); + for (Customizer customizer : registry.get(Customizer.class)) { + customizer.customize(e, type); + } + e.invoke_virtual(Constants.TYPE_OBJECT, TO_STRING); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING); + } + e.goTo(end); + e.mark(skip); + e.pop(); + e.push("null"); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING); + e.mark(end); + } + + private static void shrinkStringBuffer(CodeEmitter e, int amt) { + e.dup(); + e.dup(); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, LENGTH); + e.push(amt); + e.math(e.SUB, Type.INT_TYPE); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, SET_LENGTH); + } + + public static class ArrayDelimiters { + private String before; + private String inside; + private String after; + + public ArrayDelimiters(String before, String inside, String after) { + this.before = before; + this.inside = inside; + this.after = after; + } + } + + public static void load_method(CodeEmitter e, MethodInfo method) { + load_class(e, method.getClassInfo().getType()); + e.push(method.getSignature().getName()); + push_object(e, method.getSignature().getArgumentTypes()); + e.invoke_virtual(Constants.TYPE_CLASS, GET_DECLARED_METHOD); + } + + private interface ParameterTyper { + Type[] getParameterTypes(MethodInfo member); + } + + public static void method_switch(CodeEmitter e, + List methods, + ObjectSwitchCallback callback) { + member_switch_helper(e, methods, callback, true); + } + + public static void constructor_switch(CodeEmitter e, + List constructors, + ObjectSwitchCallback callback) { + member_switch_helper(e, constructors, callback, false); + } + + private static void member_switch_helper(final CodeEmitter e, + List members, + final ObjectSwitchCallback callback, + boolean useName) { + try { + final Map cache = new HashMap(); + final ParameterTyper cached = new ParameterTyper() { + public Type[] getParameterTypes(MethodInfo member) { + Type[] types = (Type[])cache.get(member); + if (types == null) { + cache.put(member, types = member.getSignature().getArgumentTypes()); + } + return types; + } + }; + final Label def = e.make_label(); + final Label end = e.make_label(); + if (useName) { + e.swap(); + final Map buckets = CollectionUtils.bucket(members, new Transformer() { + public Object transform(Object value) { + return ((MethodInfo)value).getSignature().getName(); + } + }); + String[] names = (String[])buckets.keySet().toArray(new String[buckets.size()]); + EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { + public void processCase(Object key, Label dontUseEnd) throws Exception { + member_helper_size(e, (List)buckets.get(key), callback, cached, def, end); + } + public void processDefault() throws Exception { + e.goTo(def); + } + }); + } else { + member_helper_size(e, members, callback, cached, def, end); + } + e.mark(def); + e.pop(); + callback.processDefault(); + e.mark(end); + } catch (RuntimeException ex) { + throw ex; + } catch (Error ex) { + throw ex; + } catch (Exception ex) { + throw new CodeGenerationException(ex); + } + } + + private static void member_helper_size(final CodeEmitter e, + List members, + final ObjectSwitchCallback callback, + final ParameterTyper typer, + final Label def, + final Label end) throws Exception { + final Map buckets = CollectionUtils.bucket(members, new Transformer() { + public Object transform(Object value) { + return new Integer(typer.getParameterTypes((MethodInfo)value).length); + } + }); + e.dup(); + e.arraylength(); + e.process_switch(EmitUtils.getSwitchKeys(buckets), new ProcessSwitchCallback() { + public void processCase(int key, Label dontUseEnd) throws Exception { + List bucket = (List)buckets.get(new Integer(key)); + member_helper_type(e, bucket, callback, typer, def, end, new BitSet()); + } + public void processDefault() throws Exception { + e.goTo(def); + } + }); + } + + private static void member_helper_type(final CodeEmitter e, + List members, + final ObjectSwitchCallback callback, + final ParameterTyper typer, + final Label def, + final Label end, + final BitSet checked) throws Exception { + if (members.size() == 1) { + MethodInfo member = (MethodInfo)members.get(0); + Type[] types = typer.getParameterTypes(member); + // need to check classes that have not already been checked via switches + for (int i = 0; i < types.length; i++) { + if (checked == null || !checked.get(i)) { + e.dup(); + e.aaload(i); + e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME); + e.push(TypeUtils.emulateClassGetName(types[i])); + e.invoke_virtual(Constants.TYPE_OBJECT, EQUALS); + e.if_jump(e.EQ, def); + } + } + e.pop(); + callback.processCase(member, end); + } else { + // choose the index that has the best chance of uniquely identifying member + Type[] example = typer.getParameterTypes((MethodInfo)members.get(0)); + Map buckets = null; + int index = -1; + for (int i = 0; i < example.length; i++) { + final int j = i; + Map test = CollectionUtils.bucket(members, new Transformer() { + public Object transform(Object value) { + return TypeUtils.emulateClassGetName(typer.getParameterTypes((MethodInfo)value)[j]); + } + }); + if (buckets == null || test.size() > buckets.size()) { + buckets = test; + index = i; + } + } + if (buckets == null || buckets.size() == 1) { + // TODO: switch by returnType + // must have two methods with same name, types, and different return types + e.goTo(def); + } else { + checked.set(index); + + e.dup(); + e.aaload(index); + e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME); + + final Map fbuckets = buckets; + String[] names = (String[])buckets.keySet().toArray(new String[buckets.size()]); + EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { + public void processCase(Object key, Label dontUseEnd) throws Exception { + member_helper_type(e, (List)fbuckets.get(key), callback, typer, def, end, checked); + } + public void processDefault() throws Exception { + e.goTo(def); + } + }); + } + } + } + + public static void wrap_throwable(Block block, Type wrapper) { + CodeEmitter e = block.getCodeEmitter(); + e.catch_exception(block, Constants.TYPE_THROWABLE); + e.new_instance(wrapper); + e.dup_x1(); + e.swap(); + e.invoke_constructor(wrapper, CSTRUCT_THROWABLE); + e.athrow(); + } + + public static void add_properties(ClassEmitter ce, String[] names, Type[] types) { + for (int i = 0; i < names.length; i++) { + String fieldName = "$cglib_prop_" + names[i]; + ce.declare_field(Constants.ACC_PRIVATE, fieldName, types[i], null); + EmitUtils.add_property(ce, names[i], types[i], fieldName); + } + } + + public static void add_property(ClassEmitter ce, String name, Type type, String fieldName) { + String property = TypeUtils.upperFirst(name); + CodeEmitter e; + e = ce.begin_method(Constants.ACC_PUBLIC, + new Signature("get" + property, + type, + Constants.TYPES_EMPTY), + null); + e.load_this(); + e.getfield(fieldName); + e.return_value(); + e.end_method(); + + e = ce.begin_method(Constants.ACC_PUBLIC, + new Signature("set" + property, + Type.VOID_TYPE, + new Type[]{ type }), + null); + e.load_this(); + e.load_arg(0); + e.putfield(fieldName); + e.return_value(); + e.end_method(); + } + + /* generates: + } catch (RuntimeException e) { + throw e; + } catch (Error e) { + throw e; + } catch ( e) { + throw e; + } catch (Throwable e) { + throw new (e); + } + */ + public static void wrap_undeclared_throwable(CodeEmitter e, Block handler, Type[] exceptions, Type wrapper) { + Set set = (exceptions == null) ? Collections.EMPTY_SET : new HashSet(Arrays.asList(exceptions)); + + if (set.contains(Constants.TYPE_THROWABLE)) + return; + + boolean needThrow = exceptions != null; + if (!set.contains(Constants.TYPE_RUNTIME_EXCEPTION)) { + e.catch_exception(handler, Constants.TYPE_RUNTIME_EXCEPTION); + needThrow = true; + } + if (!set.contains(Constants.TYPE_ERROR)) { + e.catch_exception(handler, Constants.TYPE_ERROR); + needThrow = true; + } + if (exceptions != null) { + for (int i = 0; i < exceptions.length; i++) { + e.catch_exception(handler, exceptions[i]); + } + } + if (needThrow) { + e.athrow(); + } + // e -> eo -> oeo -> ooe -> o + e.catch_exception(handler, Constants.TYPE_THROWABLE); + e.new_instance(wrapper); + e.dup_x1(); + e.swap(); + e.invoke_constructor(wrapper, CSTRUCT_THROWABLE); + e.athrow(); + } + + public static CodeEmitter begin_method(ClassEmitter e, MethodInfo method) { + return begin_method(e, method, method.getModifiers()); + } + + public static CodeEmitter begin_method(ClassEmitter e, MethodInfo method, int access) { + return e.begin_method(access, + method.getSignature(), + method.getExceptionTypes()); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/FieldTypeCustomizer.java b/blade-aop/src/main/java/net/sf/cglib/core/FieldTypeCustomizer.java new file mode 100644 index 000000000..1caa076b7 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/FieldTypeCustomizer.java @@ -0,0 +1,23 @@ +package net.sf.cglib.core; + +import org.objectweb.asm.Type; + +/** + * Customizes key types for {@link KeyFactory} right in constructor. + */ +public interface FieldTypeCustomizer extends KeyFactoryCustomizer { + /** + * Customizes {@code this.FIELD_0 = ?} assignment in key constructor + * @param e code emitter + * @param index parameter index + * @param type parameter type + */ + void customize(CodeEmitter e, int index, Type type); + + /** + * Computes type of field for storing given parameter + * @param index parameter index + * @param type parameter type + */ + Type getOutType(int index, Type type); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/GeneratorStrategy.java b/blade-aop/src/main/java/net/sf/cglib/core/GeneratorStrategy.java new file mode 100644 index 000000000..8d5ee694f --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/GeneratorStrategy.java @@ -0,0 +1,44 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +/** + * The GeneratorStrategyClass. By providing your + * own strategy you may examine or modify the generated class before + * it is loaded. Typically this will be accomplished by subclassing + * {@link DefaultGeneratorStrategy} and overriding the appropriate + * protected method. + * @see AbstractClassGenerator#setStrategy + */ +public interface GeneratorStrategy { + /** + * Generate the class. + * @param cg a class generator on which you can call {@link ClassGenerator#generateClass} + * @return a byte array containing the bits of a valid Class + */ + byte[] generate(ClassGenerator cg) throws Exception; + + /** + * The GeneratorStrategy in use does not currently, but may + * in the future, affect the caching of classes generated by {@link + * AbstractClassGenerator}, so this is a reminder that you should + * correctly implement equals and hashCode + * to avoid generating too many classes. + */ + boolean equals(Object o); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/HashCodeCustomizer.java b/blade-aop/src/main/java/net/sf/cglib/core/HashCodeCustomizer.java new file mode 100644 index 000000000..b66b791cb --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/HashCodeCustomizer.java @@ -0,0 +1,12 @@ +package net.sf.cglib.core; + +import org.objectweb.asm.Type; + +public interface HashCodeCustomizer extends KeyFactoryCustomizer { + /** + * Customizes calculation of hashcode + * @param e code emitter + * @param type parameter type + */ + boolean customize(CodeEmitter e, Type type); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/KeyFactory.java b/blade-aop/src/main/java/net/sf/cglib/core/KeyFactory.java new file mode 100644 index 000000000..b9ea79851 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/KeyFactory.java @@ -0,0 +1,346 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.cglib.core; + +import net.sf.cglib.core.internal.CustomizerRegistry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; + +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.Collections; +import java.util.List; + +/** + * Generates classes to handle multi-valued keys, for use in things such as Maps and Sets. + * Code for equals and hashCode methods follow the + * the rules laid out in Effective Java by Joshua Bloch. + *

+ * To generate a KeyFactory, you need to supply an interface which + * describes the structure of the key. The interface should have a + * single method named newInstance, which returns an + * Object. The arguments array can be + * anything--Objects, primitive values, or single or + * multi-dimension arrays of either. For example: + *

+ *     private interface IntStringKey {
+ *         public Object newInstance(int i, String s);
+ *     }
+ * 

+ * Once you have made a KeyFactory, you generate a new key by calling + * the newInstance method defined by your interface. + *

+ *     IntStringKey factory = (IntStringKey)KeyFactory.create(IntStringKey.class);
+ *     Object key1 = factory.newInstance(4, "Hello");
+ *     Object key2 = factory.newInstance(4, "World");
+ * 

+ * Note: + * hashCode equality between two keys key1 and key2 is only guaranteed if + * key1.equals(key2) and the keys were produced by the same factory. + * + * @version $Id: KeyFactory.java,v 1.26 2006/03/05 02:43:19 herbyderby Exp $ + */ +abstract public class KeyFactory { + private static final Signature GET_NAME = + TypeUtils.parseSignature("String getName()"); + private static final Signature GET_CLASS = + TypeUtils.parseSignature("Class getClass()"); + private static final Signature HASH_CODE = + TypeUtils.parseSignature("int hashCode()"); + private static final Signature EQUALS = + TypeUtils.parseSignature("boolean equals(Object)"); + private static final Signature TO_STRING = + TypeUtils.parseSignature("String toString()"); + private static final Signature APPEND_STRING = + TypeUtils.parseSignature("StringBuffer append(String)"); + private static final Type KEY_FACTORY = + TypeUtils.parseType("net.sf.cglib.core.KeyFactory"); + private static final Signature GET_SORT = + TypeUtils.parseSignature("int getSort()"); + + //generated numbers: + private final static int PRIMES[] = { + 11, 73, 179, 331, + 521, 787, 1213, 1823, + 2609, 3691, 5189, 7247, + 10037, 13931, 19289, 26627, + 36683, 50441, 69403, 95401, + 131129, 180179, 247501, 340057, + 467063, 641371, 880603, 1209107, + 1660097, 2279161, 3129011, 4295723, + 5897291, 8095873, 11114263, 15257791, + 20946017, 28754629, 39474179, 54189869, + 74391461, 102123817, 140194277, 192456917, + 264202273, 362693231, 497900099, 683510293, + 938313161, 1288102441, 1768288259 }; + + + public static final Customizer CLASS_BY_NAME = new Customizer() { + public void customize(CodeEmitter e, Type type) { + if (type.equals(Constants.TYPE_CLASS)) { + e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME); + } + } + }; + + public static final FieldTypeCustomizer STORE_CLASS_AS_STRING = new FieldTypeCustomizer() { + public void customize(CodeEmitter e, int index, Type type) { + if (type.equals(Constants.TYPE_CLASS)) { + e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME); + } + } + + public Type getOutType(int index, Type type) { + if (type.equals(Constants.TYPE_CLASS)) { + return Constants.TYPE_STRING; + } + return type; + } + }; + + /** + * {@link Type#hashCode()} is very expensive as it traverses full descriptor to calculate hash code. + * This customizer uses {@link Type#getSort()} as a hash code. + */ + public static final HashCodeCustomizer HASH_ASM_TYPE = new HashCodeCustomizer() { + public boolean customize(CodeEmitter e, Type type) { + if (Constants.TYPE_TYPE.equals(type)) { + e.invoke_virtual(type, GET_SORT); + return true; + } + return false; + } + }; + + /** + * @deprecated this customizer might result in unexpected class leak since key object still holds a strong reference to the Object and class. + * It is recommended to have pre-processing method that would strip Objects and represent Classes as Strings + */ + @Deprecated + public static final Customizer OBJECT_BY_CLASS = new Customizer() { + public void customize(CodeEmitter e, Type type) { + e.invoke_virtual(Constants.TYPE_OBJECT, GET_CLASS); + } + }; + + protected KeyFactory() { + } + + public static KeyFactory create(Class keyInterface) { + return create(keyInterface, null); + } + + public static KeyFactory create(Class keyInterface, Customizer customizer) { + return create(keyInterface.getClassLoader(), keyInterface, customizer); + } + + public static KeyFactory create(Class keyInterface, KeyFactoryCustomizer first, List next) { + return create(keyInterface.getClassLoader(), keyInterface, first, next); + } + + public static KeyFactory create(ClassLoader loader, Class keyInterface, Customizer customizer) { + return create(loader, keyInterface, customizer, Collections.emptyList()); + } + + public static KeyFactory create(ClassLoader loader, Class keyInterface, KeyFactoryCustomizer customizer, + List next) { + Generator gen = new Generator(); + gen.setInterface(keyInterface); + + if (customizer != null) { + gen.addCustomizer(customizer); + } + if (next != null && !next.isEmpty()) { + for (KeyFactoryCustomizer keyFactoryCustomizer : next) { + gen.addCustomizer(keyFactoryCustomizer); + } + } + gen.setClassLoader(loader); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(KeyFactory.class.getName()); + private static final Class[] KNOWN_CUSTOMIZER_TYPES = new Class[]{Customizer.class, FieldTypeCustomizer.class}; + + private Class keyInterface; + // TODO: Make me final when deprecated methods are removed + private CustomizerRegistry customizers = new CustomizerRegistry(KNOWN_CUSTOMIZER_TYPES); + private int constant; + private int multiplier; + + public Generator() { + super(SOURCE); + } + + protected ClassLoader getDefaultClassLoader() { + return keyInterface.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(keyInterface); + } + + /** + * @deprecated Use {@link #addCustomizer(KeyFactoryCustomizer)} instead. + */ + @Deprecated + public void setCustomizer(Customizer customizer) { + customizers = CustomizerRegistry.singleton(customizer); + } + + public void addCustomizer(KeyFactoryCustomizer customizer) { + customizers.add(customizer); + } + + public List getCustomizers(Class klass) { + return customizers.get(klass); + } + + public void setInterface(Class keyInterface) { + this.keyInterface = keyInterface; + } + + public KeyFactory create() { + setNamePrefix(keyInterface.getName()); + return (KeyFactory)super.create(keyInterface.getName()); + } + + public void setHashConstant(int constant) { + this.constant = constant; + } + + public void setHashMultiplier(int multiplier) { + this.multiplier = multiplier; + } + + protected Object firstInstance(Class type) { + return ReflectUtils.newInstance(type); + } + + protected Object nextInstance(Object instance) { + return instance; + } + + public void generateClass(ClassVisitor v) { + ClassEmitter ce = new ClassEmitter(v); + + Method newInstance = ReflectUtils.findNewInstance(keyInterface); + if (!newInstance.getReturnType().equals(Object.class)) { + throw new IllegalArgumentException("newInstance method must return Object"); + } + + Type[] parameterTypes = TypeUtils.getTypes(newInstance.getParameterTypes()); + ce.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + getClassName(), + KEY_FACTORY, + new Type[]{ Type.getType(keyInterface) }, + Constants.SOURCE_FILE); + EmitUtils.null_constructor(ce); + EmitUtils.factory_method(ce, ReflectUtils.getSignature(newInstance)); + + int seed = 0; + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, + TypeUtils.parseConstructor(parameterTypes), + null); + e.load_this(); + e.super_invoke_constructor(); + e.load_this(); + List fieldTypeCustomizers = getCustomizers(FieldTypeCustomizer.class); + for (int i = 0; i < parameterTypes.length; i++) { + Type parameterType = parameterTypes[i]; + Type fieldType = parameterType; + for (FieldTypeCustomizer customizer : fieldTypeCustomizers) { + fieldType = customizer.getOutType(i, fieldType); + } + seed += fieldType.hashCode(); + ce.declare_field(Constants.ACC_PRIVATE | Constants.ACC_FINAL, + getFieldName(i), + fieldType, + null); + e.dup(); + e.load_arg(i); + for (FieldTypeCustomizer customizer : fieldTypeCustomizers) { + customizer.customize(e, i, parameterType); + } + e.putfield(getFieldName(i)); + } + e.return_value(); + e.end_method(); + + // hash code + e = ce.begin_method(Constants.ACC_PUBLIC, HASH_CODE, null); + int hc = (constant != 0) ? constant : PRIMES[(int)(Math.abs(seed) % PRIMES.length)]; + int hm = (multiplier != 0) ? multiplier : PRIMES[(int)(Math.abs(seed * 13) % PRIMES.length)]; + e.push(hc); + for (int i = 0; i < parameterTypes.length; i++) { + e.load_this(); + e.getfield(getFieldName(i)); + EmitUtils.hash_code(e, parameterTypes[i], hm, customizers); + } + e.return_value(); + e.end_method(); + + // equals + e = ce.begin_method(Constants.ACC_PUBLIC, EQUALS, null); + Label fail = e.make_label(); + e.load_arg(0); + e.instance_of_this(); + e.if_jump(e.EQ, fail); + for (int i = 0; i < parameterTypes.length; i++) { + e.load_this(); + e.getfield(getFieldName(i)); + e.load_arg(0); + e.checkcast_this(); + e.getfield(getFieldName(i)); + EmitUtils.not_equals(e, parameterTypes[i], fail, customizers); + } + e.push(1); + e.return_value(); + e.mark(fail); + e.push(0); + e.return_value(); + e.end_method(); + + // toString + e = ce.begin_method(Constants.ACC_PUBLIC, TO_STRING, null); + e.new_instance(Constants.TYPE_STRING_BUFFER); + e.dup(); + e.invoke_constructor(Constants.TYPE_STRING_BUFFER); + for (int i = 0; i < parameterTypes.length; i++) { + if (i > 0) { + e.push(", "); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING); + } + e.load_this(); + e.getfield(getFieldName(i)); + EmitUtils.append_string(e, parameterTypes[i], EmitUtils.DEFAULT_DELIMITERS, customizers); + } + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, TO_STRING); + e.return_value(); + e.end_method(); + + ce.end_class(); + } + + private String getFieldName(int arg) { + return "FIELD_" + arg; + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/KeyFactoryCustomizer.java b/blade-aop/src/main/java/net/sf/cglib/core/KeyFactoryCustomizer.java new file mode 100644 index 000000000..e6a5f2c56 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/KeyFactoryCustomizer.java @@ -0,0 +1,7 @@ +package net.sf.cglib.core; + +/** + * Marker interface for customizers of {@link KeyFactory} + */ +public interface KeyFactoryCustomizer { +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/Local.java b/blade-aop/src/main/java/net/sf/cglib/core/Local.java new file mode 100644 index 000000000..f39a1e298 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/Local.java @@ -0,0 +1,37 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.Type; + +public class Local +{ + private Type type; + private int index; + + public Local(int index, Type type) { + this.type = type; + this.index = index; + } + + public int getIndex() { + return index; + } + + public Type getType() { + return type; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/LocalVariablesSorter.java b/blade-aop/src/main/java/net/sf/cglib/core/LocalVariablesSorter.java new file mode 100644 index 000000000..6846d15b7 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/LocalVariablesSorter.java @@ -0,0 +1,158 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2005 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** + * A {@link MethodVisitor} that renumbers local variables in their order of + * appearance. This adapter allows one to easily add new local variables to a + * method. + * + * @author Chris Nokleberg + * @author Eric Bruneton + */ +public class LocalVariablesSorter extends MethodVisitor { + + /** + * Mapping from old to new local variable indexes. A local variable at index + * i of size 1 is remapped to 'mapping[2*i]', while a local variable at + * index i of size 2 is remapped to 'mapping[2*i+1]'. + */ + private static class State + { + int[] mapping = new int[40]; + int nextLocal; + } + + protected final int firstLocal; + private final State state; + + public LocalVariablesSorter( + final int access, + final String desc, + final MethodVisitor mv) + { + super(Opcodes.ASM5, mv); + state = new State(); + Type[] args = Type.getArgumentTypes(desc); + state.nextLocal = ((Opcodes.ACC_STATIC & access) != 0) ? 0 : 1; + for (int i = 0; i < args.length; i++) { + state.nextLocal += args[i].getSize(); + } + firstLocal = state.nextLocal; + } + + public LocalVariablesSorter(LocalVariablesSorter lvs) { + super(Opcodes.ASM5, lvs.mv); + state = lvs.state; + firstLocal = lvs.firstLocal; + } + + public void visitVarInsn(final int opcode, final int var) { + int size; + switch (opcode) { + case Opcodes.LLOAD: + case Opcodes.LSTORE: + case Opcodes.DLOAD: + case Opcodes.DSTORE: + size = 2; + break; + default: + size = 1; + } + mv.visitVarInsn(opcode, remap(var, size)); + } + + public void visitIincInsn(final int var, final int increment) { + mv.visitIincInsn(remap(var, 1), increment); + } + + public void visitMaxs(final int maxStack, final int maxLocals) { + mv.visitMaxs(maxStack, state.nextLocal); + } + + public void visitLocalVariable( + final String name, + final String desc, + final String signature, + final Label start, + final Label end, + final int index) + { + mv.visitLocalVariable(name, desc, signature, start, end, remap(index)); + } + + // ------------- + + protected int newLocal(final int size) { + int var = state.nextLocal; + state.nextLocal += size; + return var; + } + + private int remap(final int var, final int size) { + if (var < firstLocal) { + return var; + } + int key = 2 * var + size - 1; + int length = state.mapping.length; + if (key >= length) { + int[] newMapping = new int[Math.max(2 * length, key + 1)]; + System.arraycopy(state.mapping, 0, newMapping, 0, length); + state.mapping = newMapping; + } + int value = state.mapping[key]; + if (value == 0) { + value = state.nextLocal + 1; + state.mapping[key] = value; + state.nextLocal += size; + } + return value - 1; + } + + private int remap(final int var) { + if (var < firstLocal) { + return var; + } + int key = 2 * var; + int value = key < state.mapping.length ? state.mapping[key] : 0; + if (value == 0) { + value = key + 1 < state.mapping.length ? state.mapping[key + 1] : 0; + } + if (value == 0) { + throw new IllegalStateException("Unknown local variable " + var); + } + return value - 1; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/MethodInfo.java b/blade-aop/src/main/java/net/sf/cglib/core/MethodInfo.java new file mode 100644 index 000000000..dfaacd821 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/MethodInfo.java @@ -0,0 +1,47 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Type; + +abstract public class MethodInfo { + + protected MethodInfo() { + } + + abstract public ClassInfo getClassInfo(); + abstract public int getModifiers(); + abstract public Signature getSignature(); + abstract public Type[] getExceptionTypes(); + + public boolean equals(Object o) { + if (o == null) + return false; + if (!(o instanceof MethodInfo)) + return false; + return getSignature().equals(((MethodInfo)o).getSignature()); + } + + public int hashCode() { + return getSignature().hashCode(); + } + + public String toString() { + // TODO: include modifiers, exceptions + return getSignature().toString(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/MethodInfoTransformer.java b/blade-aop/src/main/java/net/sf/cglib/core/MethodInfoTransformer.java new file mode 100644 index 000000000..7b3a8fc3c --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/MethodInfoTransformer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import java.lang.reflect.*; + +public class MethodInfoTransformer implements Transformer +{ + private static final MethodInfoTransformer INSTANCE = new MethodInfoTransformer(); + + public static MethodInfoTransformer getInstance() { + return INSTANCE; + } + + public Object transform(Object value) { + if (value instanceof Method) { + return ReflectUtils.getMethodInfo((Method)value); + } else if (value instanceof Constructor) { + return ReflectUtils.getMethodInfo((Constructor)value); + } else { + throw new IllegalArgumentException("cannot get method info for " + value); + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/MethodWrapper.java b/blade-aop/src/main/java/net/sf/cglib/core/MethodWrapper.java new file mode 100644 index 000000000..5bea1acbe --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/MethodWrapper.java @@ -0,0 +1,46 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import java.lang.reflect.Method; +import java.util.*; + +public class MethodWrapper { + private static final MethodWrapperKey KEY_FACTORY = + (MethodWrapperKey)KeyFactory.create(MethodWrapperKey.class); + + /** Internal interface, only public due to ClassLoader issues. */ + public interface MethodWrapperKey { + public Object newInstance(String name, String[] parameterTypes, String returnType); + } + + private MethodWrapper() { + } + + public static Object create(Method method) { + return KEY_FACTORY.newInstance(method.getName(), + ReflectUtils.getNames(method.getParameterTypes()), + method.getReturnType().getName()); + } + + public static Set createSet(Collection methods) { + Set set = new HashSet(); + for (Iterator it = methods.iterator(); it.hasNext();) { + set.add(create((Method)it.next())); + } + return set; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/NamingPolicy.java b/blade-aop/src/main/java/net/sf/cglib/core/NamingPolicy.java new file mode 100644 index 000000000..d7dc68e3a --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/NamingPolicy.java @@ -0,0 +1,43 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import java.util.Set; + +/** + * Customize the generated class name for {@link AbstractClassGenerator}-based utilities. + */ +public interface NamingPolicy { + /** + * Choose a name for a generated class. + * @param prefix a dotted-name chosen by the generating class (possibly to put the generated class in a particular package) + * @param source the fully-qualified class name of the generating class (for example "net.sf.cglib.Enhancer") + * @param key A key object representing the state of the parameters; for caching to work properly, equal keys should result + * in the same generated class name. The default policy incorporates key.hashCode() into the class name. + * @param names a predicate that returns true if the given classname has already been used in the same ClassLoader. + * @return the fully-qualified class name + */ + String getClassName(String prefix, String source, Object key, Predicate names); + + /** + * The NamingPolicy in use does not currently, but may + * in the future, affect the caching of classes generated by {@link + * AbstractClassGenerator}, so this is a reminder that you should + * correctly implement equals and hashCode + * to avoid generating too many classes. + */ + boolean equals(Object o); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/ObjectSwitchCallback.java b/blade-aop/src/main/java/net/sf/cglib/core/ObjectSwitchCallback.java new file mode 100644 index 000000000..df179e015 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/ObjectSwitchCallback.java @@ -0,0 +1,24 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.Label; + +public interface ObjectSwitchCallback { + void processCase(Object key, Label end) throws Exception; + void processDefault() throws Exception; +} + diff --git a/blade-aop/src/main/java/net/sf/cglib/core/Predicate.java b/blade-aop/src/main/java/net/sf/cglib/core/Predicate.java new file mode 100644 index 000000000..ab367a83d --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/Predicate.java @@ -0,0 +1,21 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +public interface Predicate { + boolean evaluate(Object arg); +} + diff --git a/blade-aop/src/main/java/net/sf/cglib/core/ProcessArrayCallback.java b/blade-aop/src/main/java/net/sf/cglib/core/ProcessArrayCallback.java new file mode 100644 index 000000000..bb2a79712 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/ProcessArrayCallback.java @@ -0,0 +1,22 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.Type; + +public interface ProcessArrayCallback { + void processElement(Type type); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/ProcessSwitchCallback.java b/blade-aop/src/main/java/net/sf/cglib/core/ProcessSwitchCallback.java new file mode 100644 index 000000000..cac098562 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/ProcessSwitchCallback.java @@ -0,0 +1,23 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.Label; + +public interface ProcessSwitchCallback { + void processCase(int key, Label end) throws Exception; + void processDefault() throws Exception; +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/ReflectUtils.java b/blade-aop/src/main/java/net/sf/cglib/core/ReflectUtils.java new file mode 100644 index 000000000..df9579a0a --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/ReflectUtils.java @@ -0,0 +1,492 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import java.beans.*; +import java.lang.reflect.*; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.*; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Type; + +/** + * @version $Id: ReflectUtils.java,v 1.30 2009/01/11 19:47:49 herbyderby Exp $ + */ +public class ReflectUtils { + private ReflectUtils() { } + + private static final Map primitives = new HashMap(8); + private static final Map transforms = new HashMap(8); + private static final ClassLoader defaultLoader = ReflectUtils.class.getClassLoader(); + private static Method DEFINE_CLASS; + private static final ProtectionDomain PROTECTION_DOMAIN; + + private static final List OBJECT_METHODS = new ArrayList(); + + static { + PROTECTION_DOMAIN = getProtectionDomain(ReflectUtils.class); + + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + try { + Class loader = Class.forName("java.lang.ClassLoader"); // JVM crash w/o this + DEFINE_CLASS = loader.getDeclaredMethod("defineClass", + new Class[]{ String.class, + byte[].class, + Integer.TYPE, + Integer.TYPE, + ProtectionDomain.class }); + DEFINE_CLASS.setAccessible(true); + } catch (ClassNotFoundException e) { + throw new CodeGenerationException(e); + } catch (NoSuchMethodException e) { + throw new CodeGenerationException(e); + } + return null; + } + }); + Method[] methods = Object.class.getDeclaredMethods(); + for (Method method : methods) { + if ("finalize".equals(method.getName()) + || (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) { + continue; + } + OBJECT_METHODS.add(method); + } + } + + private static final String[] CGLIB_PACKAGES = { + "java.lang", + }; + + static { + primitives.put("byte", Byte.TYPE); + primitives.put("char", Character.TYPE); + primitives.put("double", Double.TYPE); + primitives.put("float", Float.TYPE); + primitives.put("int", Integer.TYPE); + primitives.put("long", Long.TYPE); + primitives.put("short", Short.TYPE); + primitives.put("boolean", Boolean.TYPE); + + transforms.put("byte", "B"); + transforms.put("char", "C"); + transforms.put("double", "D"); + transforms.put("float", "F"); + transforms.put("int", "I"); + transforms.put("long", "J"); + transforms.put("short", "S"); + transforms.put("boolean", "Z"); + } + + public static ProtectionDomain getProtectionDomain(final Class source) { + if(source == null) { + return null; + } + return (ProtectionDomain)AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return source.getProtectionDomain(); + } + }); + } + + public static Type[] getExceptionTypes(Member member) { + if (member instanceof Method) { + return TypeUtils.getTypes(((Method)member).getExceptionTypes()); + } else if (member instanceof Constructor) { + return TypeUtils.getTypes(((Constructor)member).getExceptionTypes()); + } else { + throw new IllegalArgumentException("Cannot get exception types of a field"); + } + } + + public static Signature getSignature(Member member) { + if (member instanceof Method) { + return new Signature(member.getName(), Type.getMethodDescriptor((Method)member)); + } else if (member instanceof Constructor) { + Type[] types = TypeUtils.getTypes(((Constructor)member).getParameterTypes()); + return new Signature(Constants.CONSTRUCTOR_NAME, + Type.getMethodDescriptor(Type.VOID_TYPE, types)); + + } else { + throw new IllegalArgumentException("Cannot get signature of a field"); + } + } + + public static Constructor findConstructor(String desc) { + return findConstructor(desc, defaultLoader); + } + + public static Constructor findConstructor(String desc, ClassLoader loader) { + try { + int lparen = desc.indexOf('('); + String className = desc.substring(0, lparen).trim(); + return getClass(className, loader).getConstructor(parseTypes(desc, loader)); + } catch (ClassNotFoundException e) { + throw new CodeGenerationException(e); + } catch (NoSuchMethodException e) { + throw new CodeGenerationException(e); + } + } + + public static Method findMethod(String desc) { + return findMethod(desc, defaultLoader); + } + + public static Method findMethod(String desc, ClassLoader loader) { + try { + int lparen = desc.indexOf('('); + int dot = desc.lastIndexOf('.', lparen); + String className = desc.substring(0, dot).trim(); + String methodName = desc.substring(dot + 1, lparen).trim(); + return getClass(className, loader).getDeclaredMethod(methodName, parseTypes(desc, loader)); + } catch (ClassNotFoundException e) { + throw new CodeGenerationException(e); + } catch (NoSuchMethodException e) { + throw new CodeGenerationException(e); + } + } + + private static Class[] parseTypes(String desc, ClassLoader loader) throws ClassNotFoundException { + int lparen = desc.indexOf('('); + int rparen = desc.indexOf(')', lparen); + List params = new ArrayList(); + int start = lparen + 1; + for (;;) { + int comma = desc.indexOf(',', start); + if (comma < 0) { + break; + } + params.add(desc.substring(start, comma).trim()); + start = comma + 1; + } + if (start < rparen) { + params.add(desc.substring(start, rparen).trim()); + } + Class[] types = new Class[params.size()]; + for (int i = 0; i < types.length; i++) { + types[i] = getClass((String)params.get(i), loader); + } + return types; + } + + private static Class getClass(String className, ClassLoader loader) throws ClassNotFoundException { + return getClass(className, loader, CGLIB_PACKAGES); + } + + private static Class getClass(String className, ClassLoader loader, String[] packages) throws ClassNotFoundException { + String save = className; + int dimensions = 0; + int index = 0; + while ((index = className.indexOf("[]", index) + 1) > 0) { + dimensions++; + } + StringBuffer brackets = new StringBuffer(className.length() - dimensions); + for (int i = 0; i < dimensions; i++) { + brackets.append('['); + } + className = className.substring(0, className.length() - 2 * dimensions); + + String prefix = (dimensions > 0) ? brackets + "L" : ""; + String suffix = (dimensions > 0) ? ";" : ""; + try { + return Class.forName(prefix + className + suffix, false, loader); + } catch (ClassNotFoundException ignore) { } + for (int i = 0; i < packages.length; i++) { + try { + return Class.forName(prefix + packages[i] + '.' + className + suffix, false, loader); + } catch (ClassNotFoundException ignore) { } + } + if (dimensions == 0) { + Class c = (Class)primitives.get(className); + if (c != null) { + return c; + } + } else { + String transform = (String)transforms.get(className); + if (transform != null) { + try { + return Class.forName(brackets + transform, false, loader); + } catch (ClassNotFoundException ignore) { } + } + } + throw new ClassNotFoundException(save); + } + + + public static Object newInstance(Class type) { + return newInstance(type, Constants.EMPTY_CLASS_ARRAY, null); + } + + public static Object newInstance(Class type, Class[] parameterTypes, Object[] args) { + return newInstance(getConstructor(type, parameterTypes), args); + } + + public static Object newInstance(final Constructor cstruct, final Object[] args) { + + boolean flag = cstruct.isAccessible(); + try { + if (!flag) { + cstruct.setAccessible(true); + } + Object result = cstruct.newInstance(args); + return result; + } catch (InstantiationException e) { + throw new CodeGenerationException(e); + } catch (IllegalAccessException e) { + throw new CodeGenerationException(e); + } catch (InvocationTargetException e) { + throw new CodeGenerationException(e.getTargetException()); + } finally { + if (!flag) { + cstruct.setAccessible(flag); + } + } + + } + + public static Constructor getConstructor(Class type, Class[] parameterTypes) { + try { + Constructor constructor = type.getDeclaredConstructor(parameterTypes); + constructor.setAccessible(true); + return constructor; + } catch (NoSuchMethodException e) { + throw new CodeGenerationException(e); + } + } + + public static String[] getNames(Class[] classes) + { + if (classes == null) + return null; + String[] names = new String[classes.length]; + for (int i = 0; i < names.length; i++) { + names[i] = classes[i].getName(); + } + return names; + } + + public static Class[] getClasses(Object[] objects) { + Class[] classes = new Class[objects.length]; + for (int i = 0; i < objects.length; i++) { + classes[i] = objects[i].getClass(); + } + return classes; + } + + public static Method findNewInstance(Class iface) { + Method m = findInterfaceMethod(iface); + if (!m.getName().equals("newInstance")) { + throw new IllegalArgumentException(iface + " missing newInstance method"); + } + return m; + } + + public static Method[] getPropertyMethods(PropertyDescriptor[] properties, boolean read, boolean write) { + Set methods = new HashSet(); + for (int i = 0; i < properties.length; i++) { + PropertyDescriptor pd = properties[i]; + if (read) { + methods.add(pd.getReadMethod()); + } + if (write) { + methods.add(pd.getWriteMethod()); + } + } + methods.remove(null); + return (Method[])methods.toArray(new Method[methods.size()]); + } + + public static PropertyDescriptor[] getBeanProperties(Class type) { + return getPropertiesHelper(type, true, true); + } + + public static PropertyDescriptor[] getBeanGetters(Class type) { + return getPropertiesHelper(type, true, false); + } + + public static PropertyDescriptor[] getBeanSetters(Class type) { + return getPropertiesHelper(type, false, true); + } + + private static PropertyDescriptor[] getPropertiesHelper(Class type, boolean read, boolean write) { + try { + BeanInfo info = Introspector.getBeanInfo(type, Object.class); + PropertyDescriptor[] all = info.getPropertyDescriptors(); + if (read && write) { + return all; + } + List properties = new ArrayList(all.length); + for (int i = 0; i < all.length; i++) { + PropertyDescriptor pd = all[i]; + if ((read && pd.getReadMethod() != null) || + (write && pd.getWriteMethod() != null)) { + properties.add(pd); + } + } + return (PropertyDescriptor[])properties.toArray(new PropertyDescriptor[properties.size()]); + } catch (IntrospectionException e) { + throw new CodeGenerationException(e); + } + } + + + + public static Method findDeclaredMethod(final Class type, + final String methodName, final Class[] parameterTypes) + throws NoSuchMethodException { + + Class cl = type; + while (cl != null) { + try { + return cl.getDeclaredMethod(methodName, parameterTypes); + } catch (NoSuchMethodException e) { + cl = cl.getSuperclass(); + } + } + throw new NoSuchMethodException(methodName); + + } + + public static List addAllMethods(final Class type, final List list) { + + + if (type == Object.class) { + list.addAll(OBJECT_METHODS); + } else + list.addAll(java.util.Arrays.asList(type.getDeclaredMethods())); + + Class superclass = type.getSuperclass(); + if (superclass != null) { + addAllMethods(superclass, list); + } + Class[] interfaces = type.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + addAllMethods(interfaces[i], list); + } + + return list; + } + + public static List addAllInterfaces(Class type, List list) { + Class superclass = type.getSuperclass(); + if (superclass != null) { + list.addAll(Arrays.asList(type.getInterfaces())); + addAllInterfaces(superclass, list); + } + return list; + } + + + public static Method findInterfaceMethod(Class iface) { + if (!iface.isInterface()) { + throw new IllegalArgumentException(iface + " is not an interface"); + } + Method[] methods = iface.getDeclaredMethods(); + if (methods.length != 1) { + throw new IllegalArgumentException("expecting exactly 1 method in " + iface); + } + return methods[0]; + } + + public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception { + return defineClass(className, b, loader, PROTECTION_DOMAIN); + } + + public static Class defineClass(String className, byte[] b, ClassLoader loader, ProtectionDomain protectionDomain) throws Exception { + Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length), protectionDomain }; + Class c = (Class)DEFINE_CLASS.invoke(loader, args); + // Force static initializers to run. + Class.forName(className, true, loader); + return c; + } + + public static int findPackageProtected(Class[] classes) { + for (int i = 0; i < classes.length; i++) { + if (!Modifier.isPublic(classes[i].getModifiers())) { + return i; + } + } + return 0; + } + + public static MethodInfo getMethodInfo(final Member member, final int modifiers) { + final Signature sig = getSignature(member); + return new MethodInfo() { + private ClassInfo ci; + public ClassInfo getClassInfo() { + if (ci == null) + ci = ReflectUtils.getClassInfo(member.getDeclaringClass()); + return ci; + } + public int getModifiers() { + return modifiers; + } + public Signature getSignature() { + return sig; + } + public Type[] getExceptionTypes() { + return ReflectUtils.getExceptionTypes(member); + } + public Attribute getAttribute() { + return null; + } + }; + } + + public static MethodInfo getMethodInfo(Member member) { + return getMethodInfo(member, member.getModifiers()); + } + + public static ClassInfo getClassInfo(final Class clazz) { + final Type type = Type.getType(clazz); + final Type sc = (clazz.getSuperclass() == null) ? null : Type.getType(clazz.getSuperclass()); + return new ClassInfo() { + public Type getType() { + return type; + } + public Type getSuperType() { + return sc; + } + public Type[] getInterfaces() { + return TypeUtils.getTypes(clazz.getInterfaces()); + } + public int getModifiers() { + return clazz.getModifiers(); + } + }; + } + + // used by MethodInterceptorGenerated generated code + public static Method[] findMethods(String[] namesAndDescriptors, Method[] methods) + { + Map map = new HashMap(); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + map.put(method.getName() + Type.getMethodDescriptor(method), method); + } + Method[] result = new Method[namesAndDescriptors.length / 2]; + for (int i = 0; i < result.length; i++) { + result[i] = (Method)map.get(namesAndDescriptors[i * 2] + namesAndDescriptors[i * 2 + 1]); + if (result[i] == null) { + // TODO: error? + } + } + return result; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/RejectModifierPredicate.java b/blade-aop/src/main/java/net/sf/cglib/core/RejectModifierPredicate.java new file mode 100644 index 000000000..5ad7758a8 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/RejectModifierPredicate.java @@ -0,0 +1,30 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import java.lang.reflect.*; + +public class RejectModifierPredicate implements Predicate { + private int rejectMask; + + public RejectModifierPredicate(int rejectMask) { + this.rejectMask = rejectMask; + } + + public boolean evaluate(Object arg) { + return (((Member)arg).getModifiers() & rejectMask) == 0; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/Signature.java b/blade-aop/src/main/java/net/sf/cglib/core/Signature.java new file mode 100644 index 000000000..6a176e898 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/Signature.java @@ -0,0 +1,73 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import org.objectweb.asm.Type; + +/** + * A representation of a method signature, containing the method name, + * return type, and parameter types. + */ +public class Signature { + private String name; + private String desc; + + public Signature(String name, String desc) { + // TODO: better error checking + if (name.indexOf('(') >= 0) { + throw new IllegalArgumentException("Name '" + name + "' is invalid"); + } + this.name = name; + this.desc = desc; + } + + public Signature(String name, Type returnType, Type[] argumentTypes) { + this(name, Type.getMethodDescriptor(returnType, argumentTypes)); + } + + public String getName() { + return name; + } + + public String getDescriptor() { + return desc; + } + + public Type getReturnType() { + return Type.getReturnType(desc); + } + + public Type[] getArgumentTypes() { + return Type.getArgumentTypes(desc); + } + + public String toString() { + return name + desc; + } + + public boolean equals(Object o) { + if (o == null) + return false; + if (!(o instanceof Signature)) + return false; + Signature other = (Signature)o; + return name.equals(other.name) && desc.equals(other.desc); + } + + public int hashCode() { + return name.hashCode() ^ desc.hashCode(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/TinyBitSet.java b/blade-aop/src/main/java/net/sf/cglib/core/TinyBitSet.java new file mode 100644 index 000000000..993456e40 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/TinyBitSet.java @@ -0,0 +1,78 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +public class TinyBitSet { + private static int[] T = new int[256]; + private int value = 0; + + private static int gcount(int x) { + int c = 0; + while (x != 0) { + c++; + x &= (x - 1); + } + return c; + } + + static { + for(int j = 0; j < 256; j++) { + T[j] = gcount(j); + } + } + + private static int topbit(int i) { + int j; + for (j = 0; i != 0; i ^= j) { + j = i & -i; + } + return j; + } + + private static int log2(int i) { + int j = 0; + for (j = 0; i != 0; i >>= 1) { + j++; + } + return j; + } + + public int length() { + return log2(topbit(value)); + } + + public int cardinality() { + int w = value; + int c = 0; + while (w != 0) { + c += T[w & 255]; + w >>= 8; + } + return c; + } + + public boolean get(int index) { + return (value & (1 << index)) != 0; + } + + public void set(int index) { + value |= (1 << index); + } + + public void clear(int index) { + value &= ~(1 << index); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/Transformer.java b/blade-aop/src/main/java/net/sf/cglib/core/Transformer.java new file mode 100644 index 000000000..fc7afe6b0 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/Transformer.java @@ -0,0 +1,20 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +public interface Transformer { + Object transform(Object value); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/TypeUtils.java b/blade-aop/src/main/java/net/sf/cglib/core/TypeUtils.java new file mode 100644 index 000000000..960eb04d4 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/TypeUtils.java @@ -0,0 +1,421 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import java.util.*; +import org.objectweb.asm.Type; + +public class TypeUtils { + private static final Map transforms = new HashMap(); + private static final Map rtransforms = new HashMap(); + + private TypeUtils() { + } + + static { + transforms.put("void", "V"); + transforms.put("byte", "B"); + transforms.put("char", "C"); + transforms.put("double", "D"); + transforms.put("float", "F"); + transforms.put("int", "I"); + transforms.put("long", "J"); + transforms.put("short", "S"); + transforms.put("boolean", "Z"); + + CollectionUtils.reverse(transforms, rtransforms); + } + + public static Type getType(String className) { + return Type.getType("L" + className.replace('.', '/') + ";"); + } + + public static boolean isFinal(int access) { + return (Constants.ACC_FINAL & access) != 0; + } + + public static boolean isStatic(int access) { + return (Constants.ACC_STATIC & access) != 0; + } + + public static boolean isProtected(int access) { + return (Constants.ACC_PROTECTED & access) != 0; + } + + public static boolean isPublic(int access) { + return (Constants.ACC_PUBLIC & access) != 0; + } + + public static boolean isAbstract(int access) { + return (Constants.ACC_ABSTRACT & access) != 0; + } + + public static boolean isInterface(int access) { + return (Constants.ACC_INTERFACE & access) != 0; + } + + public static boolean isPrivate(int access) { + return (Constants.ACC_PRIVATE & access) != 0; + } + + public static boolean isSynthetic(int access) { + return (Constants.ACC_SYNTHETIC & access) != 0; + } + + public static boolean isBridge(int access) { + return (Constants.ACC_BRIDGE & access) != 0; + } + + // getPackage returns null on JDK 1.2 + public static String getPackageName(Type type) { + return getPackageName(getClassName(type)); + } + + public static String getPackageName(String className) { + int idx = className.lastIndexOf('.'); + return (idx < 0) ? "" : className.substring(0, idx); + } + + public static String upperFirst(String s) { + if (s == null || s.length() == 0) { + return s; + } + return Character.toUpperCase(s.charAt(0)) + s.substring(1); + } + + public static String getClassName(Type type) { + if (isPrimitive(type)) { + return (String)rtransforms.get(type.getDescriptor()); + } else if (isArray(type)) { + return getClassName(getComponentType(type)) + "[]"; + } else { + return type.getClassName(); + } + } + + public static Type[] add(Type[] types, Type extra) { + if (types == null) { + return new Type[]{ extra }; + } else { + List list = Arrays.asList(types); + if (list.contains(extra)) { + return types; + } + Type[] copy = new Type[types.length + 1]; + System.arraycopy(types, 0, copy, 0, types.length); + copy[types.length] = extra; + return copy; + } + } + + public static Type[] add(Type[] t1, Type[] t2) { + // TODO: set semantics? + Type[] all = new Type[t1.length + t2.length]; + System.arraycopy(t1, 0, all, 0, t1.length); + System.arraycopy(t2, 0, all, t1.length, t2.length); + return all; + } + + public static Type fromInternalName(String name) { + // TODO; primitives? + return Type.getType("L" + name + ";"); + } + + public static Type[] fromInternalNames(String[] names) { + if (names == null) { + return null; + } + Type[] types = new Type[names.length]; + for (int i = 0; i < names.length; i++) { + types[i] = fromInternalName(names[i]); + } + return types; + } + + public static int getStackSize(Type[] types) { + int size = 0; + for (int i = 0; i < types.length; i++) { + size += types[i].getSize(); + } + return size; + } + + public static String[] toInternalNames(Type[] types) { + if (types == null) { + return null; + } + String[] names = new String[types.length]; + for (int i = 0; i < types.length; i++) { + names[i] = types[i].getInternalName(); + } + return names; + } + + public static Signature parseSignature(String s) { + int space = s.indexOf(' '); + int lparen = s.indexOf('(', space); + int rparen = s.indexOf(')', lparen); + String returnType = s.substring(0, space); + String methodName = s.substring(space + 1, lparen); + StringBuffer sb = new StringBuffer(); + sb.append('('); + for (Iterator it = parseTypes(s, lparen + 1, rparen).iterator(); it.hasNext();) { + sb.append(it.next()); + } + sb.append(')'); + sb.append(map(returnType)); + return new Signature(methodName, sb.toString()); + } + + public static Type parseType(String s) { + return Type.getType(map(s)); + } + + public static Type[] parseTypes(String s) { + List names = parseTypes(s, 0, s.length()); + Type[] types = new Type[names.size()]; + for (int i = 0; i < types.length; i++) { + types[i] = Type.getType((String)names.get(i)); + } + return types; + } + + public static Signature parseConstructor(Type[] types) { + StringBuffer sb = new StringBuffer(); + sb.append("("); + for (int i = 0; i < types.length; i++) { + sb.append(types[i].getDescriptor()); + } + sb.append(")"); + sb.append("V"); + return new Signature(Constants.CONSTRUCTOR_NAME, sb.toString()); + } + + public static Signature parseConstructor(String sig) { + return parseSignature("void (" + sig + ")"); // TODO + } + + private static List parseTypes(String s, int mark, int end) { + List types = new ArrayList(5); + for (;;) { + int next = s.indexOf(',', mark); + if (next < 0) { + break; + } + types.add(map(s.substring(mark, next).trim())); + mark = next + 1; + } + types.add(map(s.substring(mark, end).trim())); + return types; + } + + private static String map(String type) { + if (type.equals("")) { + return type; + } + String t = (String)transforms.get(type); + if (t != null) { + return t; + } else if (type.indexOf('.') < 0) { + return map("java.lang." + type); + } else { + StringBuffer sb = new StringBuffer(); + int index = 0; + while ((index = type.indexOf("[]", index) + 1) > 0) { + sb.append('['); + } + type = type.substring(0, type.length() - sb.length() * 2); + sb.append('L').append(type.replace('.', '/')).append(';'); + return sb.toString(); + } + } + + public static Type getBoxedType(Type type) { + switch (type.getSort()) { + case Type.CHAR: + return Constants.TYPE_CHARACTER; + case Type.BOOLEAN: + return Constants.TYPE_BOOLEAN; + case Type.DOUBLE: + return Constants.TYPE_DOUBLE; + case Type.FLOAT: + return Constants.TYPE_FLOAT; + case Type.LONG: + return Constants.TYPE_LONG; + case Type.INT: + return Constants.TYPE_INTEGER; + case Type.SHORT: + return Constants.TYPE_SHORT; + case Type.BYTE: + return Constants.TYPE_BYTE; + default: + return type; + } + } + + public static Type getUnboxedType(Type type) { + if (Constants.TYPE_INTEGER.equals(type)) { + return Type.INT_TYPE; + } else if (Constants.TYPE_BOOLEAN.equals(type)) { + return Type.BOOLEAN_TYPE; + } else if (Constants.TYPE_DOUBLE.equals(type)) { + return Type.DOUBLE_TYPE; + } else if (Constants.TYPE_LONG.equals(type)) { + return Type.LONG_TYPE; + } else if (Constants.TYPE_CHARACTER.equals(type)) { + return Type.CHAR_TYPE; + } else if (Constants.TYPE_BYTE.equals(type)) { + return Type.BYTE_TYPE; + } else if (Constants.TYPE_FLOAT.equals(type)) { + return Type.FLOAT_TYPE; + } else if (Constants.TYPE_SHORT.equals(type)) { + return Type.SHORT_TYPE; + } else { + return type; + } + } + + public static boolean isArray(Type type) { + return type.getSort() == Type.ARRAY; + } + + public static Type getComponentType(Type type) { + if (!isArray(type)) { + throw new IllegalArgumentException("Type " + type + " is not an array"); + } + return Type.getType(type.getDescriptor().substring(1)); + } + + public static boolean isPrimitive(Type type) { + switch (type.getSort()) { + case Type.ARRAY: + case Type.OBJECT: + return false; + default: + return true; + } + } + + public static String emulateClassGetName(Type type) { + if (isArray(type)) { + return type.getDescriptor().replace('/', '.'); + } else { + return getClassName(type); + } + } + + public static boolean isConstructor(MethodInfo method) { + return method.getSignature().getName().equals(Constants.CONSTRUCTOR_NAME); + } + + public static Type[] getTypes(Class[] classes) { + if (classes == null) { + return null; + } + Type[] types = new Type[classes.length]; + for (int i = 0; i < classes.length; i++) { + types[i] = Type.getType(classes[i]); + } + return types; + } + + public static int ICONST(int value) { + switch (value) { + case -1: return Constants.ICONST_M1; + case 0: return Constants.ICONST_0; + case 1: return Constants.ICONST_1; + case 2: return Constants.ICONST_2; + case 3: return Constants.ICONST_3; + case 4: return Constants.ICONST_4; + case 5: return Constants.ICONST_5; + } + return -1; // error + } + + public static int LCONST(long value) { + if (value == 0L) { + return Constants.LCONST_0; + } else if (value == 1L) { + return Constants.LCONST_1; + } else { + return -1; // error + } + } + + public static int FCONST(float value) { + if (value == 0f) { + return Constants.FCONST_0; + } else if (value == 1f) { + return Constants.FCONST_1; + } else if (value == 2f) { + return Constants.FCONST_2; + } else { + return -1; // error + } + } + + public static int DCONST(double value) { + if (value == 0d) { + return Constants.DCONST_0; + } else if (value == 1d) { + return Constants.DCONST_1; + } else { + return -1; // error + } + } + + public static int NEWARRAY(Type type) { + switch (type.getSort()) { + case Type.BYTE: + return Constants.T_BYTE; + case Type.CHAR: + return Constants.T_CHAR; + case Type.DOUBLE: + return Constants.T_DOUBLE; + case Type.FLOAT: + return Constants.T_FLOAT; + case Type.INT: + return Constants.T_INT; + case Type.LONG: + return Constants.T_LONG; + case Type.SHORT: + return Constants.T_SHORT; + case Type.BOOLEAN: + return Constants.T_BOOLEAN; + default: + return -1; // error + } + } + + public static String escapeType(String s) { + StringBuffer sb = new StringBuffer(); + for (int i = 0, len = s.length(); i < len; i++) { + char c = s.charAt(i); + switch (c) { + case '$': sb.append("$24"); break; + case '.': sb.append("$2E"); break; + case '[': sb.append("$5B"); break; + case ';': sb.append("$3B"); break; + case '(': sb.append("$28"); break; + case ')': sb.append("$29"); break; + case '/': sb.append("$2F"); break; + default: + sb.append(c); + } + } + return sb.toString(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/VisibilityPredicate.java b/blade-aop/src/main/java/net/sf/cglib/core/VisibilityPredicate.java new file mode 100644 index 000000000..fbefab121 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/VisibilityPredicate.java @@ -0,0 +1,52 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.core; + +import java.lang.reflect.*; +import org.objectweb.asm.Type; + +public class VisibilityPredicate implements Predicate { + private boolean protectedOk; + private String pkg; + private boolean samePackageOk; + + public VisibilityPredicate(Class source, boolean protectedOk) { + this.protectedOk = protectedOk; + // same package is not ok for the bootstrap loaded classes. In all other cases we are + // generating classes in the same classloader + this.samePackageOk = source.getClassLoader() != null; + pkg = TypeUtils.getPackageName(Type.getType(source)); + } + + public boolean evaluate(Object arg) { + Member member = (Member)arg; + int mod = member.getModifiers(); + if (Modifier.isPrivate(mod)) { + return false; + } else if (Modifier.isPublic(mod)) { + return true; + } else if (Modifier.isProtected(mod) && protectedOk) { + // protected is fine if 'protectedOk' is true (for subclasses) + return true; + } else { + // protected/package private if the member is in the same package as the source class + // and we are generating into the same classloader. + return samePackageOk + && pkg.equals(TypeUtils.getPackageName(Type.getType(member.getDeclaringClass()))); + } + } +} + diff --git a/blade-aop/src/main/java/net/sf/cglib/core/WeakCacheKey.java b/blade-aop/src/main/java/net/sf/cglib/core/WeakCacheKey.java new file mode 100644 index 000000000..ebbb23b04 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/WeakCacheKey.java @@ -0,0 +1,42 @@ +package net.sf.cglib.core; + +import java.lang.ref.WeakReference; + +/** + * Allows to check for object equality, yet the class does not keep strong reference to the target. + * {@link #equals(Object)} returns true if and only if the reference is not yet expired and target + * objects are equal in terms of {@link #equals(Object)}. + *

+ * This an internal class, thus it might disappear in future cglib releases. + * + * @param type of the reference + */ +public class WeakCacheKey extends WeakReference { + private final int hash; + + public WeakCacheKey(T referent) { + super(referent); + this.hash = referent.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof WeakCacheKey)) { + return false; + } + Object ours = get(); + Object theirs = ((WeakCacheKey) obj).get(); + return ours != null && theirs != null && ours.equals(theirs); + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public String toString() { + T t = get(); + return t == null ? "Clean WeakIdentityKey, hash: " + hash : t.toString(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/internal/CustomizerRegistry.java b/blade-aop/src/main/java/net/sf/cglib/core/internal/CustomizerRegistry.java new file mode 100644 index 000000000..e04206b65 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/internal/CustomizerRegistry.java @@ -0,0 +1,48 @@ +package net.sf.cglib.core.internal; + +import net.sf.cglib.core.Customizer; +import net.sf.cglib.core.FieldTypeCustomizer; +import net.sf.cglib.core.KeyFactoryCustomizer; + +import java.util.*; + +public class CustomizerRegistry { + private final Class[] customizerTypes; + private Map> customizers = new HashMap>(); + + public CustomizerRegistry(Class[] customizerTypes) { + this.customizerTypes = customizerTypes; + } + + public void add(KeyFactoryCustomizer customizer) { + Class klass = customizer.getClass(); + for (Class type : customizerTypes) { + if (type.isAssignableFrom(klass)) { + List list = customizers.get(type); + if (list == null) { + customizers.put(type, list = new ArrayList()); + } + list.add(customizer); + } + } + } + + public List get(Class klass) { + List list = customizers.get(klass); + if (list == null) { + return Collections.emptyList(); + } + return (List) list; + } + + /** + * @deprecated Only to keep backward compatibility. + */ + @Deprecated + public static CustomizerRegistry singleton(Customizer customizer) + { + CustomizerRegistry registry = new CustomizerRegistry(new Class[]{Customizer.class}); + registry.add(customizer); + return registry; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/internal/Function.java b/blade-aop/src/main/java/net/sf/cglib/core/internal/Function.java new file mode 100644 index 000000000..5da759c61 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/internal/Function.java @@ -0,0 +1,5 @@ +package net.sf.cglib.core.internal; + +public interface Function { + V apply(K key); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/core/internal/LoadingCache.java b/blade-aop/src/main/java/net/sf/cglib/core/internal/LoadingCache.java new file mode 100644 index 000000000..24cd9e418 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/core/internal/LoadingCache.java @@ -0,0 +1,86 @@ +package net.sf.cglib.core.internal; + +import java.util.concurrent.*; + +public class LoadingCache { + protected final ConcurrentMap map; + protected final Function loader; + protected final Function keyMapper; + + public static final Function IDENTITY = new Function() { + public Object apply(Object key) { + return key; + } + }; + + public LoadingCache(Function keyMapper, Function loader) { + this.keyMapper = keyMapper; + this.loader = loader; + this.map = new ConcurrentHashMap(); + } + + @SuppressWarnings("unchecked") + public static Function identity() { + return IDENTITY; + } + + public V get(K key) { + final KK cacheKey = keyMapper.apply(key); + Object v = map.get(cacheKey); + if (v != null && !(v instanceof FutureTask)) { + return (V) v; + } + + return createEntry(key, cacheKey, v); + } + + /** + * Loads entry to the cache. + * If entry is missing, put {@link FutureTask} first so other competing thread might wait for the result. + * @param key original key that would be used to load the instance + * @param cacheKey key that would be used to store the entry in internal map + * @param v null or {@link FutureTask} + * @return newly created instance + */ + protected V createEntry(final K key, KK cacheKey, Object v) { + FutureTask task; + boolean creator = false; + if (v != null) { + // Another thread is already loading an instance + task = (FutureTask) v; + } else { + task = new FutureTask(new Callable() { + public V call() throws Exception { + return loader.apply(key); + } + }); + Object prevTask = map.putIfAbsent(cacheKey, task); + if (prevTask == null) { + // creator does the load + creator = true; + task.run(); + } else if (prevTask instanceof FutureTask) { + task = (FutureTask) prevTask; + } else { + return (V) prevTask; + } + } + + V result; + try { + result = task.get(); + } catch (InterruptedException e) { + throw new IllegalStateException("Interrupted while loading cache item", e); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw ((RuntimeException) cause); + } + throw new IllegalStateException("Unable to load cache item", cause); + } + if (creator) { + map.put(cacheKey, result); + } + return result; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/BridgeMethodResolver.java b/blade-aop/src/main/java/net/sf/cglib/proxy/BridgeMethodResolver.java new file mode 100644 index 000000000..574857c1c --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/BridgeMethodResolver.java @@ -0,0 +1,114 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.cglib.proxy; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import net.sf.cglib.core.Signature; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * Uses bytecode reflection to figure out the targets of all bridge methods + * that use invokespecial, so that we can later rewrite them to use invokevirtual. + * + * @author sberlin@gmail.com (Sam Berlin) + */ +class BridgeMethodResolver { + + private final Map/* */declToBridge; + + public BridgeMethodResolver(Map declToBridge) { + this.declToBridge = declToBridge; + } + + /** + * Finds all bridge methods that are being called with invokespecial & + * returns them. + */ + public Map/**/resolveAll() { + Map resolved = new HashMap(); + for (Iterator entryIter = declToBridge.entrySet().iterator(); entryIter.hasNext(); ) { + Map.Entry entry = (Map.Entry)entryIter.next(); + Class owner = (Class)entry.getKey(); + Set bridges = (Set)entry.getValue(); + try { + new ClassReader(owner.getName()) + .accept(new BridgedFinder(bridges, resolved), + ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG); + } catch(IOException ignored) {} + } + return resolved; + } + + private static class BridgedFinder extends ClassVisitor { + private Map/**/ resolved; + private Set/**/ eligableMethods; + + private Signature currentMethod = null; + + BridgedFinder(Set eligableMethods, Map resolved) { + super(Opcodes.ASM5); + this.resolved = resolved; + this.eligableMethods = eligableMethods; + } + + public void visit(int version, int access, String name, + String signature, String superName, String[] interfaces) { + } + + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + Signature sig = new Signature(name, desc); + if (eligableMethods.remove(sig)) { + currentMethod = sig; + return new MethodVisitor(Opcodes.ASM5) { + public void visitMethodInsn(int opcode, String owner, String name, + String desc, boolean itf) { + if (opcode == Opcodes.INVOKESPECIAL && currentMethod != null) { + Signature target = new Signature(name, desc); + // If the target signature is the same as the current, + // we shouldn't change our bridge becaues invokespecial + // is the only way to make progress (otherwise we'll + // get infinite recursion). This would typically + // only happen when a bridge method is created to widen + // the visibility of a superclass' method. + if (!target.equals(currentMethod)) { + resolved.put(currentMethod, target); + } + currentMethod = null; + } + } + }; + } else { + return null; + } + } + } + +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/Callback.java b/blade-aop/src/main/java/net/sf/cglib/proxy/Callback.java new file mode 100644 index 000000000..bcd7f5dd7 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/Callback.java @@ -0,0 +1,29 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +/** + * All callback interfaces used by {@link Enhancer} extend this interface. + * @see MethodInterceptor + * @see NoOp + * @see LazyLoader + * @see Dispatcher + * @see InvocationHandler + * @see FixedValue + */ +public interface Callback +{ +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/CallbackFilter.java b/blade-aop/src/main/java/net/sf/cglib/proxy/CallbackFilter.java new file mode 100644 index 000000000..21f001926 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/CallbackFilter.java @@ -0,0 +1,46 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.lang.reflect.Method; + +/** + * Map methods of subclasses generated by {@link Enhancer} to a particular + * callback. The type of the callbacks chosen for each method affects + * the bytecode generated for that method in the subclass, and cannot + * change for the life of the class. + *

Note: {@link CallbackFilter} implementations are supposed to be + * lightweight as cglib might keep {@link CallbackFilter} objects + * alive to enable caching of generated classes. Prefer using {@code static} + * classes for implementation of {@link CallbackFilter}.

+ */ +public interface CallbackFilter { + /** + * Map a method to a callback. + * @param method the intercepted method + * @return the index into the array of callbacks (as specified by {@link Enhancer#setCallbacks}) to use for the method, + */ + int accept(Method method); + + /** + * The CallbackFilter in use affects which cached class + * the Enhancer will use, so this is a reminder that + * you should correctly implement equals and + * hashCode for custom CallbackFilter + * implementations in order to improve performance. + */ + boolean equals(Object o); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/CallbackGenerator.java b/blade-aop/src/main/java/net/sf/cglib/proxy/CallbackGenerator.java new file mode 100644 index 000000000..283f22ac7 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/CallbackGenerator.java @@ -0,0 +1,36 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.util.List; +import net.sf.cglib.core.*; + +interface CallbackGenerator +{ + void generate(ClassEmitter ce, Context context, List methods) throws Exception; + void generateStatic(CodeEmitter e, Context context, List methods) throws Exception; + + interface Context + { + ClassLoader getClassLoader(); + CodeEmitter beginMethod(ClassEmitter ce, MethodInfo method); + int getOriginalModifiers(MethodInfo method); + int getIndex(MethodInfo method); + void emitCallback(CodeEmitter ce, int index); + Signature getImplSignature(MethodInfo method); + void emitInvoke(CodeEmitter e, MethodInfo method); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/CallbackHelper.java b/blade-aop/src/main/java/net/sf/cglib/proxy/CallbackHelper.java new file mode 100644 index 000000000..b2e7e3039 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/CallbackHelper.java @@ -0,0 +1,98 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import net.sf.cglib.core.ReflectUtils; +import java.lang.reflect.Method; +import java.util.*; + +/** + * @version $Id: CallbackHelper.java,v 1.2 2004/06/24 21:15:20 herbyderby Exp $ + */ +abstract public class CallbackHelper +implements CallbackFilter +{ + private Map methodMap = new HashMap(); + private List callbacks = new ArrayList(); + + public CallbackHelper(Class superclass, Class[] interfaces) + { + List methods = new ArrayList(); + Enhancer.getMethods(superclass, interfaces, methods); + Map indexes = new HashMap(); + for (int i = 0, size = methods.size(); i < size; i++) { + Method method = (Method)methods.get(i); + Object callback = getCallback(method); + if (callback == null) + throw new IllegalStateException("getCallback cannot return null"); + boolean isCallback = callback instanceof Callback; + if (!(isCallback || (callback instanceof Class))) + throw new IllegalStateException("getCallback must return a Callback or a Class"); + if (i > 0 && ((callbacks.get(i - 1) instanceof Callback) ^ isCallback)) + throw new IllegalStateException("getCallback must return a Callback or a Class consistently for every Method"); + Integer index = (Integer)indexes.get(callback); + if (index == null) { + index = new Integer(callbacks.size()); + indexes.put(callback, index); + } + methodMap.put(method, index); + callbacks.add(callback); + } + } + + abstract protected Object getCallback(Method method); + + public Callback[] getCallbacks() + { + if (callbacks.size() == 0) + return new Callback[0]; + if (callbacks.get(0) instanceof Callback) { + return (Callback[])callbacks.toArray(new Callback[callbacks.size()]); + } else { + throw new IllegalStateException("getCallback returned classes, not callbacks; call getCallbackTypes instead"); + } + } + + public Class[] getCallbackTypes() + { + if (callbacks.size() == 0) + return new Class[0]; + if (callbacks.get(0) instanceof Callback) { + return ReflectUtils.getClasses(getCallbacks()); + } else { + return (Class[])callbacks.toArray(new Class[callbacks.size()]); + } + } + + public int accept(Method method) + { + return ((Integer)methodMap.get(method)).intValue(); + } + + public int hashCode() + { + return methodMap.hashCode(); + } + + public boolean equals(Object o) + { + if (o == null) + return false; + if (!(o instanceof CallbackHelper)) + return false; + return methodMap.equals(((CallbackHelper)o).methodMap); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/CallbackInfo.java b/blade-aop/src/main/java/net/sf/cglib/proxy/CallbackInfo.java new file mode 100644 index 000000000..2fcece882 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/CallbackInfo.java @@ -0,0 +1,116 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import org.objectweb.asm.Type; + +class CallbackInfo +{ + public static Type[] determineTypes(Class[] callbackTypes) { + return determineTypes(callbackTypes, true); + } + + public static Type[] determineTypes(Class[] callbackTypes, boolean checkAll) { + Type[] types = new Type[callbackTypes.length]; + for (int i = 0; i < types.length; i++) { + types[i] = determineType(callbackTypes[i], checkAll); + } + return types; + } + + public static Type[] determineTypes(Callback[] callbacks) { + return determineTypes(callbacks, true); + } + + public static Type[] determineTypes(Callback[] callbacks, boolean checkAll) { + Type[] types = new Type[callbacks.length]; + for (int i = 0; i < types.length; i++) { + types[i] = determineType(callbacks[i], checkAll); + } + return types; + } + + public static CallbackGenerator[] getGenerators(Type[] callbackTypes) { + CallbackGenerator[] generators = new CallbackGenerator[callbackTypes.length]; + for (int i = 0; i < generators.length; i++) { + generators[i] = getGenerator(callbackTypes[i]); + } + return generators; + } + + //////////////////// PRIVATE //////////////////// + + private Class cls; + private CallbackGenerator generator; + private Type type; + + private static final CallbackInfo[] CALLBACKS = { + new CallbackInfo(NoOp.class, NoOpGenerator.INSTANCE), + new CallbackInfo(MethodInterceptor.class, MethodInterceptorGenerator.INSTANCE), + new CallbackInfo(InvocationHandler.class, InvocationHandlerGenerator.INSTANCE), + new CallbackInfo(LazyLoader.class, LazyLoaderGenerator.INSTANCE), + new CallbackInfo(Dispatcher.class, DispatcherGenerator.INSTANCE), + new CallbackInfo(FixedValue.class, FixedValueGenerator.INSTANCE), + new CallbackInfo(ProxyRefDispatcher.class, DispatcherGenerator.PROXY_REF_INSTANCE), + }; + + private CallbackInfo(Class cls, CallbackGenerator generator) { + this.cls = cls; + this.generator = generator; + type = Type.getType(cls); + } + + private static Type determineType(Callback callback, boolean checkAll) { + if (callback == null) { + throw new IllegalStateException("Callback is null"); + } + return determineType(callback.getClass(), checkAll); + } + + private static Type determineType(Class callbackType, boolean checkAll) { + Class cur = null; + Type type = null; + for (int i = 0; i < CALLBACKS.length; i++) { + CallbackInfo info = CALLBACKS[i]; + if (info.cls.isAssignableFrom(callbackType)) { + if (cur != null) { + throw new IllegalStateException("Callback implements both " + cur + " and " + info.cls); + } + cur = info.cls; + type = info.type; + if (!checkAll) { + break; + } + } + } + if (cur == null) { + throw new IllegalStateException("Unknown callback type " + callbackType); + } + return type; + } + + private static CallbackGenerator getGenerator(Type callbackType) { + for (int i = 0; i < CALLBACKS.length; i++) { + CallbackInfo info = CALLBACKS[i]; + if (info.type.equals(callbackType)) { + return info.generator; + } + } + throw new IllegalStateException("Unknown callback type " + callbackType); + } +} + + diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/Dispatcher.java b/blade-aop/src/main/java/net/sf/cglib/proxy/Dispatcher.java new file mode 100644 index 000000000..374a06efd --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/Dispatcher.java @@ -0,0 +1,30 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +/** + * Dispatching {@link Enhancer} callback. This is identical to the + * {@link LazyLoader} interface but needs to be separate so that Enhancer + * knows which type of code to generate. + */ +public interface Dispatcher extends Callback { + /** + * Return the object which the original method invocation should + * be dispatched. This method is called for every method invocation. + * @return an object that can invoke the method + */ + Object loadObject() throws Exception; +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/DispatcherGenerator.java b/blade-aop/src/main/java/net/sf/cglib/proxy/DispatcherGenerator.java new file mode 100644 index 000000000..3a337ad72 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/DispatcherGenerator.java @@ -0,0 +1,65 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.Type; + +class DispatcherGenerator implements CallbackGenerator { + public static final DispatcherGenerator INSTANCE = + new DispatcherGenerator(false); + public static final DispatcherGenerator PROXY_REF_INSTANCE = + new DispatcherGenerator(true); + + private static final Type DISPATCHER = + TypeUtils.parseType("net.sf.cglib.proxy.Dispatcher"); + private static final Type PROXY_REF_DISPATCHER = + TypeUtils.parseType("net.sf.cglib.proxy.ProxyRefDispatcher"); + private static final Signature LOAD_OBJECT = + TypeUtils.parseSignature("Object loadObject()"); + private static final Signature PROXY_REF_LOAD_OBJECT = + TypeUtils.parseSignature("Object loadObject(Object)"); + + private boolean proxyRef; + + private DispatcherGenerator(boolean proxyRef) { + this.proxyRef = proxyRef; + } + + public void generate(ClassEmitter ce, Context context, List methods) { + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + if (!TypeUtils.isProtected(method.getModifiers())) { + CodeEmitter e = context.beginMethod(ce, method); + context.emitCallback(e, context.getIndex(method)); + if (proxyRef) { + e.load_this(); + e.invoke_interface(PROXY_REF_DISPATCHER, PROXY_REF_LOAD_OBJECT); + } else { + e.invoke_interface(DISPATCHER, LOAD_OBJECT); + } + e.checkcast(method.getClassInfo().getType()); + e.load_args(); + e.invoke(method); + e.return_value(); + e.end_method(); + } + } + } + + public void generateStatic(CodeEmitter e, Context context, List methods) { } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/Enhancer.java b/blade-aop/src/main/java/net/sf/cglib/proxy/Enhancer.java new file mode 100644 index 000000000..7e7d831bc --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/Enhancer.java @@ -0,0 +1,1315 @@ +/* + * Copyright 2002,2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.*; + +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Type; +import org.objectweb.asm.Label; + +/** + * Generates dynamic subclasses to enable method interception. This + * class started as a substitute for the standard Dynamic Proxy support + * included with JDK 1.3, but one that allowed the proxies to extend a + * concrete base class, in addition to implementing interfaces. The dynamically + * generated subclasses override the non-final methods of the superclass and + * have hooks which callback to user-defined interceptor + * implementations. + *

+ * The original and most general callback type is the {@link MethodInterceptor}, which + * in AOP terms enables "around advice"--that is, you can invoke custom code both before + * and after the invocation of the "super" method. In addition you can modify the + * arguments before calling the super method, or not call it at all. + *

+ * Although MethodInterceptor is generic enough to meet any + * interception need, it is often overkill. For simplicity and performance, additional + * specialized callback types, such as {@link LazyLoader} are also available. + * Often a single callback will be used per enhanced class, but you can control + * which callback is used on a per-method basis with a {@link CallbackFilter}. + *

+ * The most common uses of this class are embodied in the static helper methods. For + * advanced needs, such as customizing the ClassLoader to use, you should create + * a new instance of Enhancer. Other classes within CGLIB follow a similar pattern. + *

+ * All enhanced objects implement the {@link Factory} interface, unless {@link #setUseFactory} is + * used to explicitly disable this feature. The Factory interface provides an API + * to change the callbacks of an existing object, as well as a faster and easier way to create + * new instances of the same type. + *

+ * For an almost drop-in replacement for + * java.lang.reflect.Proxy, see the {@link Proxy} class. + */ +public class Enhancer extends AbstractClassGenerator +{ + private static final CallbackFilter ALL_ZERO = new CallbackFilter(){ + public int accept(Method method) { + return 0; + } + }; + + private static final Source SOURCE = new Source(Enhancer.class.getName()); + private static final EnhancerKey KEY_FACTORY = + (EnhancerKey)KeyFactory.create(EnhancerKey.class, KeyFactory.HASH_ASM_TYPE, null); + + private static final String BOUND_FIELD = "CGLIB$BOUND"; + private static final String FACTORY_DATA_FIELD = "CGLIB$FACTORY_DATA"; + private static final String THREAD_CALLBACKS_FIELD = "CGLIB$THREAD_CALLBACKS"; + private static final String STATIC_CALLBACKS_FIELD = "CGLIB$STATIC_CALLBACKS"; + private static final String SET_THREAD_CALLBACKS_NAME = "CGLIB$SET_THREAD_CALLBACKS"; + private static final String SET_STATIC_CALLBACKS_NAME = "CGLIB$SET_STATIC_CALLBACKS"; + private static final String CONSTRUCTED_FIELD = "CGLIB$CONSTRUCTED"; + /** + * {@link net.sf.cglib.core.AbstractClassGenerator.ClassLoaderData#generatedClasses} requires to keep cache key + * in a good shape (the keys should be up and running if the proxy class is alive), and one of the cache keys is + * {@link CallbackFilter}. That is why the generated class contains static field that keeps strong reference to + * the {@link #filter}. + *

This dance achieves two goals: ensures generated class is reusable and available through generatedClasses + * cache, and it enables to unload classloader and the related {@link CallbackFilter} in case user does not need + * that

+ */ + private static final String CALLBACK_FILTER_FIELD = "CGLIB$CALLBACK_FILTER"; + + private static final Type OBJECT_TYPE = + TypeUtils.parseType("Object"); + private static final Type FACTORY = + TypeUtils.parseType("net.sf.cglib.proxy.Factory"); + private static final Type ILLEGAL_STATE_EXCEPTION = + TypeUtils.parseType("IllegalStateException"); + private static final Type ILLEGAL_ARGUMENT_EXCEPTION = + TypeUtils.parseType("IllegalArgumentException"); + private static final Type THREAD_LOCAL = + TypeUtils.parseType("ThreadLocal"); + private static final Type CALLBACK = + TypeUtils.parseType("net.sf.cglib.proxy.Callback"); + private static final Type CALLBACK_ARRAY = + Type.getType(Callback[].class); + private static final Signature CSTRUCT_NULL = + TypeUtils.parseConstructor(""); + private static final Signature SET_THREAD_CALLBACKS = + new Signature(SET_THREAD_CALLBACKS_NAME, Type.VOID_TYPE, new Type[]{ CALLBACK_ARRAY }); + private static final Signature SET_STATIC_CALLBACKS = + new Signature(SET_STATIC_CALLBACKS_NAME, Type.VOID_TYPE, new Type[]{ CALLBACK_ARRAY }); + private static final Signature NEW_INSTANCE = + new Signature("newInstance", Constants.TYPE_OBJECT, new Type[]{ CALLBACK_ARRAY }); + private static final Signature MULTIARG_NEW_INSTANCE = + new Signature("newInstance", Constants.TYPE_OBJECT, new Type[]{ + Constants.TYPE_CLASS_ARRAY, + Constants.TYPE_OBJECT_ARRAY, + CALLBACK_ARRAY, + }); + private static final Signature SINGLE_NEW_INSTANCE = + new Signature("newInstance", Constants.TYPE_OBJECT, new Type[]{ CALLBACK }); + private static final Signature SET_CALLBACK = + new Signature("setCallback", Type.VOID_TYPE, new Type[]{ Type.INT_TYPE, CALLBACK }); + private static final Signature GET_CALLBACK = + new Signature("getCallback", CALLBACK, new Type[]{ Type.INT_TYPE }); + private static final Signature SET_CALLBACKS = + new Signature("setCallbacks", Type.VOID_TYPE, new Type[]{ CALLBACK_ARRAY }); + private static final Signature GET_CALLBACKS = + new Signature("getCallbacks", CALLBACK_ARRAY, new Type[0]); + private static final Signature THREAD_LOCAL_GET = + TypeUtils.parseSignature("Object get()"); + private static final Signature THREAD_LOCAL_SET = + TypeUtils.parseSignature("void set(Object)"); + private static final Signature BIND_CALLBACKS = + TypeUtils.parseSignature("void CGLIB$BIND_CALLBACKS(Object)"); + + private EnhancerFactoryData currentData; + private Object currentKey; + + /** Internal interface, only public due to ClassLoader issues. */ + public interface EnhancerKey { + public Object newInstance(String type, + String[] interfaces, + WeakCacheKey filter, + Type[] callbackTypes, + boolean useFactory, + boolean interceptDuringConstruction, + Long serialVersionUID); + } + + private Class[] interfaces; + private CallbackFilter filter; + private Callback[] callbacks; + private Type[] callbackTypes; + private boolean validateCallbackTypes; + private boolean classOnly; + private Class superclass; + private Class[] argumentTypes; + private Object[] arguments; + private boolean useFactory = true; + private Long serialVersionUID; + private boolean interceptDuringConstruction = true; + + /** + * Create a new Enhancer. A new Enhancer + * object should be used for each generated object, and should not + * be shared across threads. To create additional instances of a + * generated class, use the Factory interface. + * @see Factory + */ + public Enhancer() { + super(SOURCE); + } + + /** + * Set the class which the generated class will extend. As a convenience, + * if the supplied superclass is actually an interface, setInterfaces + * will be called with the appropriate argument instead. + * A non-interface argument must not be declared as final, and must have an + * accessible constructor. + * @param superclass class to extend or interface to implement + * @see #setInterfaces(Class[]) + */ + public void setSuperclass(Class superclass) { + if (superclass != null && superclass.isInterface()) { + setInterfaces(new Class[]{ superclass }); + } else if (superclass != null && superclass.equals(Object.class)) { + // affects choice of ClassLoader + this.superclass = null; + } else { + this.superclass = superclass; + } + } + + /** + * Set the interfaces to implement. The Factory interface will + * always be implemented regardless of what is specified here. + * @param interfaces array of interfaces to implement, or null + * @see Factory + */ + public void setInterfaces(Class[] interfaces) { + this.interfaces = interfaces; + } + + /** + * Set the {@link CallbackFilter} used to map the generated class' methods + * to a particular callback index. + * New object instances will always use the same mapping, but may use different + * actual callback objects. + * @param filter the callback filter to use when generating a new class + * @see #setCallbacks + */ + public void setCallbackFilter(CallbackFilter filter) { + this.filter = filter; + } + + + /** + * Set the single {@link Callback} to use. + * Ignored if you use {@link #createClass}. + * @param callback the callback to use for all methods + * @see #setCallbacks + */ + public void setCallback(final Callback callback) { + setCallbacks(new Callback[]{ callback }); + } + + /** + * Set the array of callbacks to use. + * Ignored if you use {@link #createClass}. + * You must use a {@link CallbackFilter} to specify the index into this + * array for each method in the proxied class. + * @param callbacks the callback array + * @see #setCallbackFilter + * @see #setCallback + */ + public void setCallbacks(Callback[] callbacks) { + if (callbacks != null && callbacks.length == 0) { + throw new IllegalArgumentException("Array cannot be empty"); + } + this.callbacks = callbacks; + } + + /** + * Set whether the enhanced object instances should implement + * the {@link Factory} interface. + * This was added for tools that need for proxies to be more + * indistinguishable from their targets. Also, in some cases it may + * be necessary to disable the Factory interface to + * prevent code from changing the underlying callbacks. + * @param useFactory whether to implement Factory; default is true + */ + public void setUseFactory(boolean useFactory) { + this.useFactory = useFactory; + } + + /** + * Set whether methods called from within the proxy's constructer + * will be intercepted. The default value is true. Unintercepted methods + * will call the method of the proxy's base class, if it exists. + * @param interceptDuringConstruction whether to intercept methods called from the constructor + */ + public void setInterceptDuringConstruction(boolean interceptDuringConstruction) { + this.interceptDuringConstruction = interceptDuringConstruction; + } + + /** + * Set the single type of {@link Callback} to use. + * This may be used instead of {@link #setCallback} when calling + * {@link #createClass}, since it may not be possible to have + * an array of actual callback instances. + * @param callbackType the type of callback to use for all methods + * @see #setCallbackTypes + */ + public void setCallbackType(Class callbackType) { + setCallbackTypes(new Class[]{ callbackType }); + } + + /** + * Set the array of callback types to use. + * This may be used instead of {@link #setCallbacks} when calling + * {@link #createClass}, since it may not be possible to have + * an array of actual callback instances. + * You must use a {@link CallbackFilter} to specify the index into this + * array for each method in the proxied class. + * @param callbackTypes the array of callback types + */ + public void setCallbackTypes(Class[] callbackTypes) { + if (callbackTypes != null && callbackTypes.length == 0) { + throw new IllegalArgumentException("Array cannot be empty"); + } + this.callbackTypes = CallbackInfo.determineTypes(callbackTypes); + } + + /** + * Generate a new class if necessary and uses the specified + * callbacks (if any) to create a new object instance. + * Uses the no-arg constructor of the superclass. + * @return a new instance + */ + public Object create() { + classOnly = false; + argumentTypes = null; + return createHelper(); + } + + /** + * Generate a new class if necessary and uses the specified + * callbacks (if any) to create a new object instance. + * Uses the constructor of the superclass matching the argumentTypes + * parameter, with the given arguments. + * @param argumentTypes constructor signature + * @param arguments compatible wrapped arguments to pass to constructor + * @return a new instance + */ + public Object create(Class[] argumentTypes, Object[] arguments) { + classOnly = false; + if (argumentTypes == null || arguments == null || argumentTypes.length != arguments.length) { + throw new IllegalArgumentException("Arguments must be non-null and of equal length"); + } + this.argumentTypes = argumentTypes; + this.arguments = arguments; + return createHelper(); + } + + /** + * Generate a new class if necessary and return it without creating a new instance. + * This ignores any callbacks that have been set. + * To create a new instance you will have to use reflection, and methods + * called during the constructor will not be intercepted. To avoid this problem, + * use the multi-arg create method. + * @see #create(Class[], Object[]) + */ + public Class createClass() { + classOnly = true; + return (Class)createHelper(); + } + + /** + * Insert a static serialVersionUID field into the generated class. + * @param sUID the field value, or null to avoid generating field. + */ + public void setSerialVersionUID(Long sUID) { + serialVersionUID = sUID; + } + + private void preValidate() { + if (callbackTypes == null) { + callbackTypes = CallbackInfo.determineTypes(callbacks, false); + validateCallbackTypes = true; + } + if (filter == null) { + if (callbackTypes.length > 1) { + throw new IllegalStateException("Multiple callback types possible but no filter specified"); + } + filter = ALL_ZERO; + } + } + + private void validate() { + if (classOnly ^ (callbacks == null)) { + if (classOnly) { + throw new IllegalStateException("createClass does not accept callbacks"); + } else { + throw new IllegalStateException("Callbacks are required"); + } + } + if (classOnly && (callbackTypes == null)) { + throw new IllegalStateException("Callback types are required"); + } + if (validateCallbackTypes) { + callbackTypes = null; + } + if (callbacks != null && callbackTypes != null) { + if (callbacks.length != callbackTypes.length) { + throw new IllegalStateException("Lengths of callback and callback types array must be the same"); + } + Type[] check = CallbackInfo.determineTypes(callbacks); + for (int i = 0; i < check.length; i++) { + if (!check[i].equals(callbackTypes[i])) { + throw new IllegalStateException("Callback " + check[i] + " is not assignable to " + callbackTypes[i]); + } + } + } else if (callbacks != null) { + callbackTypes = CallbackInfo.determineTypes(callbacks); + } + if (interfaces != null) { + for (int i = 0; i < interfaces.length; i++) { + if (interfaces[i] == null) { + throw new IllegalStateException("Interfaces cannot be null"); + } + if (!interfaces[i].isInterface()) { + throw new IllegalStateException(interfaces[i] + " is not an interface"); + } + } + } + } + + /** + * The idea of the class is to cache relevant java.lang.reflect instances so + * proxy-class can be instantiated faster that when using {@link ReflectUtils#newInstance(Class, Class[], Object[])} + * and {@link Enhancer#setThreadCallbacks(Class, Callback[])} + */ + static class EnhancerFactoryData { + public final Class generatedClass; + private final Method setThreadCallbacks; + private final Class[] primaryConstructorArgTypes; + private final Constructor primaryConstructor; + + public EnhancerFactoryData(Class generatedClass, Class[] primaryConstructorArgTypes, boolean classOnly) { + this.generatedClass = generatedClass; + try { + setThreadCallbacks = getCallbacksSetter(generatedClass, SET_THREAD_CALLBACKS_NAME); + if (classOnly) { + this.primaryConstructorArgTypes = null; + this.primaryConstructor = null; + } else { + this.primaryConstructorArgTypes = primaryConstructorArgTypes; + this.primaryConstructor = ReflectUtils.getConstructor(generatedClass, primaryConstructorArgTypes); + } + } catch (NoSuchMethodException e) { + throw new CodeGenerationException(e); + } + } + + /** + * Creates proxy instance for given argument types, and assigns the callbacks. + * Ideally, for each proxy class, just one set of argument types should be used, + * otherwise it would have to spend time on constructor lookup. + * Technically, it is a re-implementation of {@link Enhancer#createUsingReflection(Class)}, + * with "cache {@link #setThreadCallbacks} and {@link #primaryConstructor}" + * + * @see #createUsingReflection(Class) + * @param argumentTypes constructor argument types + * @param arguments constructor arguments + * @param callbacks callbacks to set for the new instance + * @return newly created proxy + */ + public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) { + setThreadCallbacks(callbacks); + try { + // Explicit reference equality is added here just in case Arrays.equals does not have one + if (primaryConstructorArgTypes == argumentTypes || + Arrays.equals(primaryConstructorArgTypes, argumentTypes)) { + // If we have relevant Constructor instance at hand, just call it + // This skips "get constructors" machinery + return ReflectUtils.newInstance(primaryConstructor, arguments); + } + // Take a slow path if observing unexpected argument types + return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments); + } finally { + // clear thread callbacks to allow them to be gc'd + setThreadCallbacks(null); + } + + } + + private void setThreadCallbacks(Callback[] callbacks) { + try { + setThreadCallbacks.invoke(generatedClass, (Object) callbacks); + } catch (IllegalAccessException e) { + throw new CodeGenerationException(e); + } catch (InvocationTargetException e) { + throw new CodeGenerationException(e.getTargetException()); + } + } + } + + private Object createHelper() { + preValidate(); + Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null, + ReflectUtils.getNames(interfaces), + filter == ALL_ZERO ? null : new WeakCacheKey(filter), + callbackTypes, + useFactory, + interceptDuringConstruction, + serialVersionUID); + this.currentKey = key; + Object result = super.create(key); + return result; + } + + @Override + protected Class generate(ClassLoaderData data) { + validate(); + if (superclass != null) { + setNamePrefix(superclass.getName()); + } else if (interfaces != null) { + setNamePrefix(interfaces[ReflectUtils.findPackageProtected(interfaces)].getName()); + } + return super.generate(data); + } + + protected ClassLoader getDefaultClassLoader() { + if (superclass != null) { + return superclass.getClassLoader(); + } else if (interfaces != null) { + return interfaces[0].getClassLoader(); + } else { + return null; + } + } + + protected ProtectionDomain getProtectionDomain() { + if (superclass != null) { + return ReflectUtils.getProtectionDomain(superclass); + } else if (interfaces != null) { + return ReflectUtils.getProtectionDomain(interfaces[0]); + } else { + return null; + } + } + + private Signature rename(Signature sig, int index) { + return new Signature("CGLIB$" + sig.getName() + "$" + index, + sig.getDescriptor()); + } + + /** + * Finds all of the methods that will be extended by an + * Enhancer-generated class using the specified superclass and + * interfaces. This can be useful in building a list of Callback + * objects. The methods are added to the end of the given list. Due + * to the subclassing nature of the classes generated by Enhancer, + * the methods are guaranteed to be non-static, non-final, and + * non-private. Each method signature will only occur once, even if + * it occurs in multiple classes. + * @param superclass the class that will be extended, or null + * @param interfaces the list of interfaces that will be implemented, or null + * @param methods the list into which to copy the applicable methods + */ + public static void getMethods(Class superclass, Class[] interfaces, List methods) + { + getMethods(superclass, interfaces, methods, null, null); + } + + private static void getMethods(Class superclass, Class[] interfaces, List methods, List interfaceMethods, Set forcePublic) + { + ReflectUtils.addAllMethods(superclass, methods); + List target = (interfaceMethods != null) ? interfaceMethods : methods; + if (interfaces != null) { + for (int i = 0; i < interfaces.length; i++) { + if (interfaces[i] != Factory.class) { + ReflectUtils.addAllMethods(interfaces[i], target); + } + } + } + if (interfaceMethods != null) { + if (forcePublic != null) { + forcePublic.addAll(MethodWrapper.createSet(interfaceMethods)); + } + methods.addAll(interfaceMethods); + } + CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_STATIC)); + CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true)); + CollectionUtils.filter(methods, new DuplicatesPredicate()); + CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_FINAL)); + } + + public void generateClass(ClassVisitor v) throws Exception { + Class sc = (superclass == null) ? Object.class : superclass; + + if (TypeUtils.isFinal(sc.getModifiers())) + throw new IllegalArgumentException("Cannot subclass final class " + sc.getName()); + List constructors = new ArrayList(Arrays.asList(sc.getDeclaredConstructors())); + filterConstructors(sc, constructors); + + // Order is very important: must add superclass, then + // its superclass chain, then each interface and + // its superinterfaces. + List actualMethods = new ArrayList(); + List interfaceMethods = new ArrayList(); + final Set forcePublic = new HashSet(); + getMethods(sc, interfaces, actualMethods, interfaceMethods, forcePublic); + + List methods = CollectionUtils.transform(actualMethods, new Transformer() { + public Object transform(Object value) { + Method method = (Method)value; + int modifiers = Constants.ACC_FINAL + | (method.getModifiers() + & ~Constants.ACC_ABSTRACT + & ~Constants.ACC_NATIVE + & ~Constants.ACC_SYNCHRONIZED); + if (forcePublic.contains(MethodWrapper.create(method))) { + modifiers = (modifiers & ~Constants.ACC_PROTECTED) | Constants.ACC_PUBLIC; + } + return ReflectUtils.getMethodInfo(method, modifiers); + } + }); + + ClassEmitter e = new ClassEmitter(v); + if (currentData == null) { + e.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + getClassName(), + Type.getType(sc), + (useFactory ? + TypeUtils.add(TypeUtils.getTypes(interfaces), FACTORY) : + TypeUtils.getTypes(interfaces)), + Constants.SOURCE_FILE); + } else { + e.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + getClassName(), + null, + new Type[]{FACTORY}, + Constants.SOURCE_FILE); + } + List constructorInfo = CollectionUtils.transform(constructors, MethodInfoTransformer.getInstance()); + + e.declare_field(Constants.ACC_PRIVATE, BOUND_FIELD, Type.BOOLEAN_TYPE, null); + e.declare_field(Constants.ACC_PUBLIC | Constants.ACC_STATIC, FACTORY_DATA_FIELD, OBJECT_TYPE, null); + if (!interceptDuringConstruction) { + e.declare_field(Constants.ACC_PRIVATE, CONSTRUCTED_FIELD, Type.BOOLEAN_TYPE, null); + } + e.declare_field(Constants.PRIVATE_FINAL_STATIC, THREAD_CALLBACKS_FIELD, THREAD_LOCAL, null); + e.declare_field(Constants.PRIVATE_FINAL_STATIC, STATIC_CALLBACKS_FIELD, CALLBACK_ARRAY, null); + if (serialVersionUID != null) { + e.declare_field(Constants.PRIVATE_FINAL_STATIC, Constants.SUID_FIELD_NAME, Type.LONG_TYPE, serialVersionUID); + } + + for (int i = 0; i < callbackTypes.length; i++) { + e.declare_field(Constants.ACC_PRIVATE, getCallbackField(i), callbackTypes[i], null); + } + // This is declared private to avoid "public field" pollution + e.declare_field(Constants.ACC_PRIVATE | Constants.ACC_STATIC, CALLBACK_FILTER_FIELD, OBJECT_TYPE, null); + + if (currentData == null) { + emitMethods(e, methods, actualMethods); + emitConstructors(e, constructorInfo); + } else { + emitDefaultConstructor(e); + } + emitSetThreadCallbacks(e); + emitSetStaticCallbacks(e); + emitBindCallbacks(e); + + if (useFactory || currentData != null) { + int[] keys = getCallbackKeys(); + emitNewInstanceCallbacks(e); + emitNewInstanceCallback(e); + emitNewInstanceMultiarg(e, constructorInfo); + emitGetCallback(e, keys); + emitSetCallback(e, keys); + emitGetCallbacks(e); + emitSetCallbacks(e); + } + + e.end_class(); + } + + /** + * Filter the list of constructors from the superclass. The + * constructors which remain will be included in the generated + * class. The default implementation is to filter out all private + * constructors, but subclasses may extend Enhancer to override this + * behavior. + * @param sc the superclass + * @param constructors the list of all declared constructors from the superclass + * @throws IllegalArgumentException if there are no non-private constructors + */ + protected void filterConstructors(Class sc, List constructors) { + CollectionUtils.filter(constructors, new VisibilityPredicate(sc, true)); + if (constructors.size() == 0) + throw new IllegalArgumentException("No visible constructors in " + sc); + } + + /** + * This method should not be called in regular flow. + * Technically speaking {@link #wrapCachedClass(Class)} uses {@link EnhancerFactoryData} as a cache value, + * and the latter enables faster instantiation than plain old reflection lookup and invoke. + * This method is left intact for backward compatibility reasons: just in case it was ever used. + * + * @param type class to instantiate + * @return newly created proxy instance + * @throws Exception if something goes wrong + */ + protected Object firstInstance(Class type) throws Exception { + if (classOnly) { + return type; + } else { + return createUsingReflection(type); + } + } + + protected Object nextInstance(Object instance) { + EnhancerFactoryData data = (EnhancerFactoryData) instance; + + if (classOnly) { + return data.generatedClass; + } + + Class[] argumentTypes = this.argumentTypes; + Object[] arguments = this.arguments; + if (argumentTypes == null) { + argumentTypes = Constants.EMPTY_CLASS_ARRAY; + arguments = null; + } + return data.newInstance(argumentTypes, arguments, callbacks); + } + + @Override + protected Object wrapCachedClass(Class klass) { + Class[] argumentTypes = this.argumentTypes; + if (argumentTypes == null) { + argumentTypes = Constants.EMPTY_CLASS_ARRAY; + } + EnhancerFactoryData factoryData = new EnhancerFactoryData(klass, argumentTypes, classOnly); + Field factoryDataField = null; + try { + // The subsequent dance is performed just once for each class, + // so it does not matter much how fast it goes + factoryDataField = klass.getField(FACTORY_DATA_FIELD); + factoryDataField.set(null, factoryData); + Field callbackFilterField = klass.getDeclaredField(CALLBACK_FILTER_FIELD); + callbackFilterField.setAccessible(true); + callbackFilterField.set(null, this.filter); + } catch (NoSuchFieldException e) { + throw new CodeGenerationException(e); + } catch (IllegalAccessException e) { + throw new CodeGenerationException(e); + } + return new WeakReference(factoryData); + } + + @Override + protected Object unwrapCachedValue(Object cached) { + if (currentKey instanceof EnhancerKey) { + EnhancerFactoryData data = ((WeakReference) cached).get(); + return data; + } + return super.unwrapCachedValue(cached); + } + + /** + * Call this method to register the {@link Callback} array to use before + * creating a new instance of the generated class via reflection. If you are using + * an instance of Enhancer or the {@link Factory} interface to create + * new instances, this method is unnecessary. Its primary use is for when you want to + * cache and reuse a generated class yourself, and the generated class does + * not implement the {@link Factory} interface. + *

+ * Note that this method only registers the callbacks on the current thread. + * If you want to register callbacks for instances created by multiple threads, + * use {@link #registerStaticCallbacks}. + *

+ * The registered callbacks are overwritten and subsequently cleared + * when calling any of the create methods (such as + * {@link #create}), or any {@link Factory} newInstance method. + * Otherwise they are not cleared, and you should be careful to set them + * back to null after creating new instances via reflection if + * memory leakage is a concern. + * @param generatedClass a class previously created by {@link Enhancer} + * @param callbacks the array of callbacks to use when instances of the generated + * class are created + * @see #setUseFactory + */ + public static void registerCallbacks(Class generatedClass, Callback[] callbacks) { + setThreadCallbacks(generatedClass, callbacks); + } + + /** + * Similar to {@link #registerCallbacks}, but suitable for use + * when multiple threads will be creating instances of the generated class. + * The thread-level callbacks will always override the static callbacks. + * Static callbacks are never cleared. + * @param generatedClass a class previously created by {@link Enhancer} + * @param callbacks the array of callbacks to use when instances of the generated + * class are created + */ + public static void registerStaticCallbacks(Class generatedClass, Callback[] callbacks) { + setCallbacksHelper(generatedClass, callbacks, SET_STATIC_CALLBACKS_NAME); + } + + /** + * Determine if a class was generated using Enhancer. + * @param type any class + * @return whether the class was generated using Enhancer + */ + public static boolean isEnhanced(Class type) { + try { + getCallbacksSetter(type, SET_THREAD_CALLBACKS_NAME); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + private static void setThreadCallbacks(Class type, Callback[] callbacks) { + setCallbacksHelper(type, callbacks, SET_THREAD_CALLBACKS_NAME); + } + + private static void setCallbacksHelper(Class type, Callback[] callbacks, String methodName) { + // TODO: optimize + try { + Method setter = getCallbacksSetter(type, methodName); + setter.invoke(null, new Object[]{ callbacks }); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException(type + " is not an enhanced class"); + } catch (IllegalAccessException e) { + throw new CodeGenerationException(e); + } catch (InvocationTargetException e) { + throw new CodeGenerationException(e); + } + } + + private static Method getCallbacksSetter(Class type, String methodName) throws NoSuchMethodException { + return type.getDeclaredMethod(methodName, new Class[]{ Callback[].class }); + } + + /** + * Instantiates a proxy instance and assigns callback values. + * Implementation detail: java.lang.reflect instances are not cached, so this method should not + * be used on a hot path. + * This method is used when {@link #setUseCache(boolean)} is set to {@code false}. + * + * @param type class to instantiate + * @return newly created instance + */ + private Object createUsingReflection(Class type) { + setThreadCallbacks(type, callbacks); + try{ + + if (argumentTypes != null) { + + return ReflectUtils.newInstance(type, argumentTypes, arguments); + + } else { + + return ReflectUtils.newInstance(type); + + } + }finally{ + // clear thread callbacks to allow them to be gc'd + setThreadCallbacks(type, null); + } + } + + /** + * Helper method to create an intercepted object. + * For finer control over the generated instance, use a new instance of Enhancer + * instead of this static method. + * @param type class to extend or interface to implement + * @param callback the callback to use for all methods + */ + public static Object create(Class type, Callback callback) { + Enhancer e = new Enhancer(); + e.setSuperclass(type); + e.setCallback(callback); + return e.create(); + } + + /** + * Helper method to create an intercepted object. + * For finer control over the generated instance, use a new instance of Enhancer + * instead of this static method. + * @param superclass class to extend or interface to implement + * @param interfaces array of interfaces to implement, or null + * @param callback the callback to use for all methods + */ + public static Object create(Class superclass, Class interfaces[], Callback callback) { + Enhancer e = new Enhancer(); + e.setSuperclass(superclass); + e.setInterfaces(interfaces); + e.setCallback(callback); + return e.create(); + } + + /** + * Helper method to create an intercepted object. + * For finer control over the generated instance, use a new instance of Enhancer + * instead of this static method. + * @param superclass class to extend or interface to implement + * @param interfaces array of interfaces to implement, or null + * @param filter the callback filter to use when generating a new class + * @param callbacks callback implementations to use for the enhanced object + */ + public static Object create(Class superclass, Class[] interfaces, CallbackFilter filter, Callback[] callbacks) { + Enhancer e = new Enhancer(); + e.setSuperclass(superclass); + e.setInterfaces(interfaces); + e.setCallbackFilter(filter); + e.setCallbacks(callbacks); + return e.create(); + } + + private void emitDefaultConstructor(ClassEmitter ce) { + Constructor declaredConstructor; + try { + declaredConstructor = Object.class.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new IllegalStateException("Object should have default constructor ", e); + } + MethodInfo constructor = (MethodInfo) MethodInfoTransformer.getInstance().transform(declaredConstructor); + CodeEmitter e = EmitUtils.begin_method(ce, constructor, Constants.ACC_PUBLIC); + e.load_this(); + e.dup(); + Signature sig = constructor.getSignature(); + e.super_invoke_constructor(sig); + e.return_value(); + e.end_method(); + } + + private void emitConstructors(ClassEmitter ce, List constructors) { + boolean seenNull = false; + for (Iterator it = constructors.iterator(); it.hasNext();) { + MethodInfo constructor = (MethodInfo)it.next(); + if (currentData != null && !"()V".equals(constructor.getSignature().getDescriptor())) { + continue; + } + CodeEmitter e = EmitUtils.begin_method(ce, constructor, Constants.ACC_PUBLIC); + e.load_this(); + e.dup(); + e.load_args(); + Signature sig = constructor.getSignature(); + seenNull = seenNull || sig.getDescriptor().equals("()V"); + e.super_invoke_constructor(sig); + if (currentData == null) { + e.invoke_static_this(BIND_CALLBACKS); + if (!interceptDuringConstruction) { + e.load_this(); + e.push(1); + e.putfield(CONSTRUCTED_FIELD); + } + } + e.return_value(); + e.end_method(); + } + if (!classOnly && !seenNull && arguments == null) + throw new IllegalArgumentException("Superclass has no null constructors but no arguments were given"); + } + + private int[] getCallbackKeys() { + int[] keys = new int[callbackTypes.length]; + for (int i = 0; i < callbackTypes.length; i++) { + keys[i] = i; + } + return keys; + } + + private void emitGetCallback(ClassEmitter ce, int[] keys) { + final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, GET_CALLBACK, null); + e.load_this(); + e.invoke_static_this(BIND_CALLBACKS); + e.load_this(); + e.load_arg(0); + e.process_switch(keys, new ProcessSwitchCallback() { + public void processCase(int key, Label end) { + e.getfield(getCallbackField(key)); + e.goTo(end); + } + public void processDefault() { + e.pop(); // stack height + e.aconst_null(); + } + }); + e.return_value(); + e.end_method(); + } + + private void emitSetCallback(ClassEmitter ce, int[] keys) { + final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, SET_CALLBACK, null); + e.load_arg(0); + e.process_switch(keys, new ProcessSwitchCallback() { + public void processCase(int key, Label end) { + e.load_this(); + e.load_arg(1); + e.checkcast(callbackTypes[key]); + e.putfield(getCallbackField(key)); + e.goTo(end); + } + public void processDefault() { + // TODO: error? + } + }); + e.return_value(); + e.end_method(); + } + + private void emitSetCallbacks(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, SET_CALLBACKS, null); + e.load_this(); + e.load_arg(0); + for (int i = 0; i < callbackTypes.length; i++) { + e.dup2(); + e.aaload(i); + e.checkcast(callbackTypes[i]); + e.putfield(getCallbackField(i)); + } + e.return_value(); + e.end_method(); + } + + private void emitGetCallbacks(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, GET_CALLBACKS, null); + e.load_this(); + e.invoke_static_this(BIND_CALLBACKS); + e.load_this(); + e.push(callbackTypes.length); + e.newarray(CALLBACK); + for (int i = 0; i < callbackTypes.length; i++) { + e.dup(); + e.push(i); + e.load_this(); + e.getfield(getCallbackField(i)); + e.aastore(); + } + e.return_value(); + e.end_method(); + } + + private void emitNewInstanceCallbacks(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, NEW_INSTANCE, null); + Type thisType = getThisType(e); + e.load_arg(0); + e.invoke_static(thisType, SET_THREAD_CALLBACKS); + emitCommonNewInstance(e); + } + + private Type getThisType(CodeEmitter e) { + if (currentData == null) { + return e.getClassEmitter().getClassType(); + } else { + return Type.getType(currentData.generatedClass); + } + } + + private void emitCommonNewInstance(CodeEmitter e) { + Type thisType = getThisType(e); + e.new_instance(thisType); + e.dup(); + e.invoke_constructor(thisType); + e.aconst_null(); + e.invoke_static(thisType, SET_THREAD_CALLBACKS); + e.return_value(); + e.end_method(); + } + + private void emitNewInstanceCallback(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, SINGLE_NEW_INSTANCE, null); + switch (callbackTypes.length) { + case 0: + // TODO: make sure Callback is null + break; + case 1: + // for now just make a new array; TODO: optimize + e.push(1); + e.newarray(CALLBACK); + e.dup(); + e.push(0); + e.load_arg(0); + e.aastore(); + e.invoke_static(getThisType(e), SET_THREAD_CALLBACKS); + break; + default: + e.throw_exception(ILLEGAL_STATE_EXCEPTION, "More than one callback object required"); + } + emitCommonNewInstance(e); + } + + private void emitNewInstanceMultiarg(ClassEmitter ce, List constructors) { + final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, MULTIARG_NEW_INSTANCE, null); + final Type thisType = getThisType(e); + e.load_arg(2); + e.invoke_static(thisType, SET_THREAD_CALLBACKS); + e.new_instance(thisType); + e.dup(); + e.load_arg(0); + EmitUtils.constructor_switch(e, constructors, new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + MethodInfo constructor = (MethodInfo)key; + Type types[] = constructor.getSignature().getArgumentTypes(); + for (int i = 0; i < types.length; i++) { + e.load_arg(1); + e.push(i); + e.aaload(); + e.unbox(types[i]); + } + e.invoke_constructor(thisType, constructor.getSignature()); + e.goTo(end); + } + public void processDefault() { + e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Constructor not found"); + } + }); + e.aconst_null(); + e.invoke_static(thisType, SET_THREAD_CALLBACKS); + e.return_value(); + e.end_method(); + } + + private void emitMethods(final ClassEmitter ce, List methods, List actualMethods) { + CallbackGenerator[] generators = CallbackInfo.getGenerators(callbackTypes); + + Map groups = new HashMap(); + final Map indexes = new HashMap(); + final Map originalModifiers = new HashMap(); + final Map positions = CollectionUtils.getIndexMap(methods); + final Map declToBridge = new HashMap(); + + Iterator it1 = methods.iterator(); + Iterator it2 = (actualMethods != null) ? actualMethods.iterator() : null; + + while (it1.hasNext()) { + MethodInfo method = (MethodInfo)it1.next(); + Method actualMethod = (it2 != null) ? (Method)it2.next() : null; + int index = filter.accept(actualMethod); + if (index >= callbackTypes.length) { + throw new IllegalArgumentException("Callback filter returned an index that is too large: " + index); + } + originalModifiers.put(method, new Integer((actualMethod != null) ? actualMethod.getModifiers() : method.getModifiers())); + indexes.put(method, new Integer(index)); + List group = (List)groups.get(generators[index]); + if (group == null) { + groups.put(generators[index], group = new ArrayList(methods.size())); + } + group.add(method); + + // Optimization: build up a map of Class -> bridge methods in class + // so that we can look up all the bridge methods in one pass for a class. + if (TypeUtils.isBridge(actualMethod.getModifiers())) { + Set bridges = (Set)declToBridge.get(actualMethod.getDeclaringClass()); + if (bridges == null) { + bridges = new HashSet(); + declToBridge.put(actualMethod.getDeclaringClass(), bridges); + } + bridges.add(method.getSignature()); + } + } + + final Map bridgeToTarget = new BridgeMethodResolver(declToBridge).resolveAll(); + + Set seenGen = new HashSet(); + CodeEmitter se = ce.getStaticHook(); + se.new_instance(THREAD_LOCAL); + se.dup(); + se.invoke_constructor(THREAD_LOCAL, CSTRUCT_NULL); + se.putfield(THREAD_CALLBACKS_FIELD); + + final Object[] state = new Object[1]; + CallbackGenerator.Context context = new CallbackGenerator.Context() { + public ClassLoader getClassLoader() { + return Enhancer.this.getClassLoader(); + } + public int getOriginalModifiers(MethodInfo method) { + return ((Integer)originalModifiers.get(method)).intValue(); + } + public int getIndex(MethodInfo method) { + return ((Integer)indexes.get(method)).intValue(); + } + public void emitCallback(CodeEmitter e, int index) { + emitCurrentCallback(e, index); + } + public Signature getImplSignature(MethodInfo method) { + return rename(method.getSignature(), ((Integer)positions.get(method)).intValue()); + } + public void emitInvoke(CodeEmitter e, MethodInfo method) { + // If this is a bridge and we know the target was called from invokespecial, + // then we need to invoke_virtual w/ the bridge target instead of doing + // a super, because super may itself be using super, which would bypass + // any proxies on the target. + Signature bridgeTarget = (Signature)bridgeToTarget.get(method.getSignature()); + if (bridgeTarget != null) { + // TODO: this assumes that the target has wider or the same type + // parameters than the current. + // In reality this should always be true because otherwise we wouldn't + // have had a bridge doing an invokespecial. + // If it isn't true, we would need to checkcast each argument + // against the target's argument types + e.invoke_virtual_this(bridgeTarget); + + Type retType = method.getSignature().getReturnType(); + // Not necessary to cast if the target & bridge have + // the same return type. + // (This conveniently includes void and primitive types, + // which would fail if casted. It's not possible to + // covariant from boxed to unbox (or vice versa), so no having + // to box/unbox for bridges). + // TODO: It also isn't necessary to checkcast if the return is + // assignable from the target. (This would happen if a subclass + // used covariant returns to narrow the return type within a bridge + // method.) + if (!retType.equals(bridgeTarget.getReturnType())) { + e.checkcast(retType); + } + } else { + e.super_invoke(method.getSignature()); + } + } + public CodeEmitter beginMethod(ClassEmitter ce, MethodInfo method) { + CodeEmitter e = EmitUtils.begin_method(ce, method); + if (!interceptDuringConstruction && + !TypeUtils.isAbstract(method.getModifiers())) { + Label constructed = e.make_label(); + e.load_this(); + e.getfield(CONSTRUCTED_FIELD); + e.if_jump(e.NE, constructed); + e.load_this(); + e.load_args(); + e.super_invoke(); + e.return_value(); + e.mark(constructed); + } + return e; + } + }; + for (int i = 0; i < callbackTypes.length; i++) { + CallbackGenerator gen = generators[i]; + if (!seenGen.contains(gen)) { + seenGen.add(gen); + final List fmethods = (List)groups.get(gen); + if (fmethods != null) { + try { + gen.generate(ce, context, fmethods); + gen.generateStatic(se, context, fmethods); + } catch (RuntimeException x) { + throw x; + } catch (Exception x) { + throw new CodeGenerationException(x); + } + } + } + } + se.return_value(); + se.end_method(); + } + + private void emitSetThreadCallbacks(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC | Constants.ACC_STATIC, + SET_THREAD_CALLBACKS, + null); + e.getfield(THREAD_CALLBACKS_FIELD); + e.load_arg(0); + e.invoke_virtual(THREAD_LOCAL, THREAD_LOCAL_SET); + e.return_value(); + e.end_method(); + } + + private void emitSetStaticCallbacks(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC | Constants.ACC_STATIC, + SET_STATIC_CALLBACKS, + null); + e.load_arg(0); + e.putfield(STATIC_CALLBACKS_FIELD); + e.return_value(); + e.end_method(); + } + + private void emitCurrentCallback(CodeEmitter e, int index) { + e.load_this(); + e.getfield(getCallbackField(index)); + e.dup(); + Label end = e.make_label(); + e.ifnonnull(end); + e.pop(); // stack height + e.load_this(); + e.invoke_static_this(BIND_CALLBACKS); + e.load_this(); + e.getfield(getCallbackField(index)); + e.mark(end); + } + + private void emitBindCallbacks(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.PRIVATE_FINAL_STATIC, + BIND_CALLBACKS, + null); + Local me = e.make_local(); + e.load_arg(0); + e.checkcast_this(); + e.store_local(me); + + Label end = e.make_label(); + e.load_local(me); + e.getfield(BOUND_FIELD); + e.if_jump(e.NE, end); + e.load_local(me); + e.push(1); + e.putfield(BOUND_FIELD); + + e.getfield(THREAD_CALLBACKS_FIELD); + e.invoke_virtual(THREAD_LOCAL, THREAD_LOCAL_GET); + e.dup(); + Label found_callback = e.make_label(); + e.ifnonnull(found_callback); + e.pop(); + + e.getfield(STATIC_CALLBACKS_FIELD); + e.dup(); + e.ifnonnull(found_callback); + e.pop(); + e.goTo(end); + + e.mark(found_callback); + e.checkcast(CALLBACK_ARRAY); + e.load_local(me); + e.swap(); + for (int i = callbackTypes.length - 1; i >= 0; i--) { + if (i != 0) { + e.dup2(); + } + e.aaload(i); + e.checkcast(callbackTypes[i]); + e.putfield(getCallbackField(i)); + } + + e.mark(end); + e.return_value(); + e.end_method(); + } + + private static String getCallbackField(int index) { + return "CGLIB$CALLBACK_" + index; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/Factory.java b/blade-aop/src/main/java/net/sf/cglib/proxy/Factory.java new file mode 100644 index 000000000..437f6ae13 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/Factory.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002,2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.cglib.proxy; + +/** + * All enhanced instances returned by the {@link Enhancer} class implement this interface. + * Using this interface for new instances is faster than going through the Enhancer + * interface or using reflection. In addition, to intercept methods called during + * object construction you must use these methods instead of reflection. + * @author Juozas Baliuka baliuka@mwm.lt + * @version $Id: Factory.java,v 1.13 2004/06/24 21:15:20 herbyderby Exp $ + */ +public interface Factory { + /** + * Creates new instance of the same type, using the no-arg constructor. + * The class of this object must have been created using a single Callback type. + * If multiple callbacks are required an exception will be thrown. + * @param callback the new interceptor to use + * @return new instance of the same type + */ + Object newInstance(Callback callback); + + /** + * Creates new instance of the same type, using the no-arg constructor. + * @param callbacks the new callbacks(s) to use + * @return new instance of the same type + */ + Object newInstance(Callback[] callbacks); + + /** + * Creates a new instance of the same type, using the constructor + * matching the given signature. + * @param types the constructor argument types + * @param args the constructor arguments + * @param callbacks the new interceptor(s) to use + * @return new instance of the same type + */ + Object newInstance(Class[] types, Object[] args, Callback[] callbacks); + + /** + * Return the Callback implementation at the specified index. + * @param index the callback index + * @return the callback implementation + */ + Callback getCallback(int index); + + /** + * Set the callback for this object for the given type. + * @param index the callback index to replace + * @param callback the new callback + */ + void setCallback(int index, Callback callback); + + /** + * Replace all of the callbacks for this object at once. + * @param callbacks the new callbacks(s) to use + */ + void setCallbacks(Callback[] callbacks); + + /** + * Get the current set of callbacks for ths object. + * @return a new array instance + */ + Callback[] getCallbacks(); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/FixedValue.java b/blade-aop/src/main/java/net/sf/cglib/proxy/FixedValue.java new file mode 100644 index 000000000..06afdba8b --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/FixedValue.java @@ -0,0 +1,35 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +/** + * {@link Enhancer} callback that simply returns the value to return + * from the proxied method. No information about what method + * is being called is available to the callback, and the type of + * the returned object must be compatible with the return type of + * the proxied method. This makes this callback primarily useful + * for forcing a particular method (through the use of a {@link CallbackFilter} + * to return a fixed value with little overhead. + */ +public interface FixedValue extends Callback { + /** + * Return the object which the original method invocation should + * return. This method is called for every method invocation. + * @return an object matching the type of the return value for every + * method this callback is mapped to + */ + Object loadObject() throws Exception; +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/FixedValueGenerator.java b/blade-aop/src/main/java/net/sf/cglib/proxy/FixedValueGenerator.java new file mode 100644 index 000000000..1cd6e92e2 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/FixedValueGenerator.java @@ -0,0 +1,42 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.Type; + +class FixedValueGenerator implements CallbackGenerator { + public static final FixedValueGenerator INSTANCE = new FixedValueGenerator(); + private static final Type FIXED_VALUE = + TypeUtils.parseType("net.sf.cglib.proxy.FixedValue"); + private static final Signature LOAD_OBJECT = + TypeUtils.parseSignature("Object loadObject()"); + + public void generate(ClassEmitter ce, Context context, List methods) { + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + CodeEmitter e = context.beginMethod(ce, method); + context.emitCallback(e, context.getIndex(method)); + e.invoke_interface(FIXED_VALUE, LOAD_OBJECT); + e.unbox_or_zero(e.getReturnType()); + e.return_value(); + e.end_method(); + } + } + + public void generateStatic(CodeEmitter e, Context context, List methods) { } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/InterfaceMaker.java b/blade-aop/src/main/java/net/sf/cglib/proxy/InterfaceMaker.java new file mode 100644 index 000000000..f4f8b71f2 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/InterfaceMaker.java @@ -0,0 +1,118 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.lang.reflect.*; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Type; + +/** + * Generates new interfaces at runtime. + * By passing a generated interface to the Enhancer's list of interfaces to + * implement, you can make your enhanced classes handle an arbitrary set + * of method signatures. + * @author Chris Nokleberg + * @version $Id: InterfaceMaker.java,v 1.4 2006/03/05 02:43:19 herbyderby Exp $ + */ +public class InterfaceMaker extends AbstractClassGenerator +{ + private static final Source SOURCE = new Source(InterfaceMaker.class.getName()); + private Map signatures = new HashMap(); + + /** + * Create a new InterfaceMaker. A new InterfaceMaker + * object should be used for each generated interface, and should not + * be shared across threads. + */ + public InterfaceMaker() { + super(SOURCE); + } + + /** + * Add a method signature to the interface. + * @param sig the method signature to add to the interface + * @param exceptions an array of exception types to declare for the method + */ + public void add(Signature sig, Type[] exceptions) { + signatures.put(sig, exceptions); + } + + /** + * Add a method signature to the interface. The method modifiers are ignored, + * since interface methods are by definition abstract and public. + * @param method the method to add to the interface + */ + public void add(Method method) { + add(ReflectUtils.getSignature(method), + ReflectUtils.getExceptionTypes(method)); + } + + /** + * Add all the public methods in the specified class. + * Methods from superclasses are included, except for methods declared in the base + * Object class (e.g. getClass, equals, hashCode). + * @param class the class containing the methods to add to the interface + */ + public void add(Class clazz) { + Method[] methods = clazz.getMethods(); + for (int i = 0; i < methods.length; i++) { + Method m = methods[i]; + if (!m.getDeclaringClass().getName().equals("java.lang.Object")) { + add(m); + } + } + } + + /** + * Create an interface using the current set of method signatures. + */ + public Class create() { + setUseCache(false); + return (Class)super.create(this); + } + + protected ClassLoader getDefaultClassLoader() { + return null; + } + + protected Object firstInstance(Class type) { + return type; + } + + protected Object nextInstance(Object instance) { + throw new IllegalStateException("InterfaceMaker does not cache"); + } + + public void generateClass(ClassVisitor v) throws Exception { + ClassEmitter ce = new ClassEmitter(v); + ce.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC | Constants.ACC_INTERFACE, + getClassName(), + null, + null, + Constants.SOURCE_FILE); + for (Iterator it = signatures.keySet().iterator(); it.hasNext();) { + Signature sig = (Signature)it.next(); + Type[] exceptions = (Type[])signatures.get(sig); + ce.begin_method(Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT, + sig, + exceptions).end_method(); + } + ce.end_class(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/InvocationHandler.java b/blade-aop/src/main/java/net/sf/cglib/proxy/InvocationHandler.java new file mode 100644 index 000000000..4307a6d19 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/InvocationHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.lang.reflect.Method; + +/** + * {@link java.lang.reflect.InvocationHandler} replacement (unavailable under JDK 1.2). + * This callback type is primarily for use by the {@link Proxy} class but + * may be used with {@link Enhancer} as well. + * @author Neeme Praks neeme@apache.org + * @version $Id: InvocationHandler.java,v 1.3 2004/06/24 21:15:20 herbyderby Exp $ + */ +public interface InvocationHandler +extends Callback +{ + /** + * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object) + */ + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; + +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/InvocationHandlerGenerator.java b/blade-aop/src/main/java/net/sf/cglib/proxy/InvocationHandlerGenerator.java new file mode 100644 index 000000000..dcb028fcd --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/InvocationHandlerGenerator.java @@ -0,0 +1,64 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import net.sf.cglib.core.*; +import java.util.*; +import org.objectweb.asm.Type; + +class InvocationHandlerGenerator +implements CallbackGenerator +{ + public static final InvocationHandlerGenerator INSTANCE = new InvocationHandlerGenerator(); + + private static final Type INVOCATION_HANDLER = + TypeUtils.parseType("net.sf.cglib.proxy.InvocationHandler"); + private static final Type UNDECLARED_THROWABLE_EXCEPTION = + TypeUtils.parseType("net.sf.cglib.proxy.UndeclaredThrowableException"); + private static final Type METHOD = + TypeUtils.parseType("java.lang.reflect.Method"); + private static final Signature INVOKE = + TypeUtils.parseSignature("Object invoke(Object, java.lang.reflect.Method, Object[])"); + + public void generate(ClassEmitter ce, Context context, List methods) { + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + Signature impl = context.getImplSignature(method); + ce.declare_field(Constants.PRIVATE_FINAL_STATIC, impl.getName(), METHOD, null); + + CodeEmitter e = context.beginMethod(ce, method); + Block handler = e.begin_block(); + context.emitCallback(e, context.getIndex(method)); + e.load_this(); + e.getfield(impl.getName()); + e.create_arg_array(); + e.invoke_interface(INVOCATION_HANDLER, INVOKE); + e.unbox(method.getSignature().getReturnType()); + e.return_value(); + handler.end(); + EmitUtils.wrap_undeclared_throwable(e, handler, method.getExceptionTypes(), UNDECLARED_THROWABLE_EXCEPTION); + e.end_method(); + } + } + + public void generateStatic(CodeEmitter e, Context context, List methods) { + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + EmitUtils.load_method(e, method); + e.putfield(context.getImplSignature(method).getName()); + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/LazyLoader.java b/blade-aop/src/main/java/net/sf/cglib/proxy/LazyLoader.java new file mode 100644 index 000000000..874c2a396 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/LazyLoader.java @@ -0,0 +1,30 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +/** + * Lazy-loading {@link Enhancer} callback. + */ +public interface LazyLoader extends Callback { + /** + * Return the object which the original method invocation should be + * dispatched. Called as soon as the first lazily-loaded method in + * the enhanced instance is invoked. The same object is then used + * for every future method call to the proxy instance. + * @return an object that can invoke the method + */ + Object loadObject() throws Exception; +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/LazyLoaderGenerator.java b/blade-aop/src/main/java/net/sf/cglib/proxy/LazyLoaderGenerator.java new file mode 100644 index 000000000..b7f92c1ca --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/LazyLoaderGenerator.java @@ -0,0 +1,88 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; + +class LazyLoaderGenerator implements CallbackGenerator { + public static final LazyLoaderGenerator INSTANCE = new LazyLoaderGenerator(); + + private static final Signature LOAD_OBJECT = + TypeUtils.parseSignature("Object loadObject()"); + private static final Type LAZY_LOADER = + TypeUtils.parseType("net.sf.cglib.proxy.LazyLoader"); + + public void generate(ClassEmitter ce, Context context, List methods) { + Set indexes = new HashSet(); + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + if (TypeUtils.isProtected(method.getModifiers())) { + // ignore protected methods + } else { + int index = context.getIndex(method); + indexes.add(new Integer(index)); + CodeEmitter e = context.beginMethod(ce, method); + e.load_this(); + e.dup(); + e.invoke_virtual_this(loadMethod(index)); + e.checkcast(method.getClassInfo().getType()); + e.load_args(); + e.invoke(method); + e.return_value(); + e.end_method(); + } + } + + for (Iterator it = indexes.iterator(); it.hasNext();) { + int index = ((Integer)it.next()).intValue(); + + String delegate = "CGLIB$LAZY_LOADER_" + index; + ce.declare_field(Constants.ACC_PRIVATE, delegate, Constants.TYPE_OBJECT, null); + + CodeEmitter e = ce.begin_method(Constants.ACC_PRIVATE | + Constants.ACC_SYNCHRONIZED | + Constants.ACC_FINAL, + loadMethod(index), + null); + e.load_this(); + e.getfield(delegate); + e.dup(); + Label end = e.make_label(); + e.ifnonnull(end); + e.pop(); + e.load_this(); + context.emitCallback(e, index); + e.invoke_interface(LAZY_LOADER, LOAD_OBJECT); + e.dup_x1(); + e.putfield(delegate); + e.mark(end); + e.return_value(); + e.end_method(); + + } + } + + private Signature loadMethod(int index) { + return new Signature("CGLIB$LOAD_PRIVATE_" + index, + Constants.TYPE_OBJECT, + Constants.TYPES_EMPTY); + } + + public void generateStatic(CodeEmitter e, Context context, List methods) { } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/MethodInterceptor.java b/blade-aop/src/main/java/net/sf/cglib/proxy/MethodInterceptor.java new file mode 100644 index 000000000..c0a24429d --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/MethodInterceptor.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002,2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +/** + * General-purpose {@link Enhancer} callback which provides for "around advice". + * @author Juozas Baliuka baliuka@mwm.lt + * @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $ + */ +public interface MethodInterceptor +extends Callback +{ + /** + * All generated proxied methods call this method instead of the original method. + * The original method may either be invoked by normal reflection using the Method object, + * or by using the MethodProxy (faster). + * @param obj "this", the enhanced object + * @param method intercepted Method + * @param args argument array; primitive types are wrapped + * @param proxy used to invoke super (non-intercepted method); may be called + * as many times as needed + * @throws Throwable any exception may be thrown; if so, super method will not be invoked + * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value. + * @see MethodProxy + */ + public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, + MethodProxy proxy) throws Throwable; + +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/MethodInterceptorGenerator.java b/blade-aop/src/main/java/net/sf/cglib/proxy/MethodInterceptorGenerator.java new file mode 100644 index 000000000..b0e00a9da --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/MethodInterceptorGenerator.java @@ -0,0 +1,239 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.lang.reflect.Method; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; + +class MethodInterceptorGenerator +implements CallbackGenerator +{ + public static final MethodInterceptorGenerator INSTANCE = new MethodInterceptorGenerator(); + + static final String EMPTY_ARGS_NAME = "CGLIB$emptyArgs"; + static final String FIND_PROXY_NAME = "CGLIB$findMethodProxy"; + static final Class[] FIND_PROXY_TYPES = { Signature.class }; + + private static final Type ABSTRACT_METHOD_ERROR = + TypeUtils.parseType("AbstractMethodError"); + private static final Type METHOD = + TypeUtils.parseType("java.lang.reflect.Method"); + private static final Type REFLECT_UTILS = + TypeUtils.parseType("net.sf.cglib.core.ReflectUtils"); + private static final Type METHOD_PROXY = + TypeUtils.parseType("net.sf.cglib.proxy.MethodProxy"); + private static final Type METHOD_INTERCEPTOR = + TypeUtils.parseType("net.sf.cglib.proxy.MethodInterceptor"); + private static final Signature GET_DECLARED_METHODS = + TypeUtils.parseSignature("java.lang.reflect.Method[] getDeclaredMethods()"); + private static final Signature GET_DECLARING_CLASS = + TypeUtils.parseSignature("Class getDeclaringClass()"); + private static final Signature FIND_METHODS = + TypeUtils.parseSignature("java.lang.reflect.Method[] findMethods(String[], java.lang.reflect.Method[])"); + private static final Signature MAKE_PROXY = + new Signature("create", METHOD_PROXY, new Type[]{ + Constants.TYPE_CLASS, + Constants.TYPE_CLASS, + Constants.TYPE_STRING, + Constants.TYPE_STRING, + Constants.TYPE_STRING + }); + private static final Signature INTERCEPT = + new Signature("intercept", Constants.TYPE_OBJECT, new Type[]{ + Constants.TYPE_OBJECT, + METHOD, + Constants.TYPE_OBJECT_ARRAY, + METHOD_PROXY + }); + private static final Signature FIND_PROXY = + new Signature(FIND_PROXY_NAME, METHOD_PROXY, new Type[]{ Constants.TYPE_SIGNATURE }); + private static final Signature TO_STRING = + TypeUtils.parseSignature("String toString()"); + private static final Transformer METHOD_TO_CLASS = new Transformer(){ + public Object transform(Object value) { + return ((MethodInfo)value).getClassInfo(); + } + }; + private static final Signature CSTRUCT_SIGNATURE = + TypeUtils.parseConstructor("String, String"); + + private String getMethodField(Signature impl) { + return impl.getName() + "$Method"; + } + private String getMethodProxyField(Signature impl) { + return impl.getName() + "$Proxy"; + } + + public void generate(ClassEmitter ce, Context context, List methods) { + Map sigMap = new HashMap(); + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + Signature sig = method.getSignature(); + Signature impl = context.getImplSignature(method); + + String methodField = getMethodField(impl); + String methodProxyField = getMethodProxyField(impl); + + sigMap.put(sig.toString(), methodProxyField); + ce.declare_field(Constants.PRIVATE_FINAL_STATIC, methodField, METHOD, null); + ce.declare_field(Constants.PRIVATE_FINAL_STATIC, methodProxyField, METHOD_PROXY, null); + ce.declare_field(Constants.PRIVATE_FINAL_STATIC, EMPTY_ARGS_NAME, Constants.TYPE_OBJECT_ARRAY, null); + CodeEmitter e; + + // access method + e = ce.begin_method(Constants.ACC_FINAL, + impl, + method.getExceptionTypes()); + superHelper(e, method, context); + e.return_value(); + e.end_method(); + + // around method + e = context.beginMethod(ce, method); + Label nullInterceptor = e.make_label(); + context.emitCallback(e, context.getIndex(method)); + e.dup(); + e.ifnull(nullInterceptor); + + e.load_this(); + e.getfield(methodField); + + if (sig.getArgumentTypes().length == 0) { + e.getfield(EMPTY_ARGS_NAME); + } else { + e.create_arg_array(); + } + + e.getfield(methodProxyField); + e.invoke_interface(METHOD_INTERCEPTOR, INTERCEPT); + e.unbox_or_zero(sig.getReturnType()); + e.return_value(); + + e.mark(nullInterceptor); + superHelper(e, method, context); + e.return_value(); + e.end_method(); + } + generateFindProxy(ce, sigMap); + } + + private static void superHelper(CodeEmitter e, MethodInfo method, Context context) + { + if (TypeUtils.isAbstract(method.getModifiers())) { + e.throw_exception(ABSTRACT_METHOD_ERROR, method.toString() + " is abstract" ); + } else { + e.load_this(); + e.load_args(); + context.emitInvoke(e, method); + } + } + + public void generateStatic(CodeEmitter e, Context context, List methods) throws Exception { + /* generates: + static { + Class thisClass = Class.forName("NameOfThisClass"); + Class cls = Class.forName("java.lang.Object"); + String[] sigs = new String[]{ "toString", "()Ljava/lang/String;", ... }; + Method[] methods = cls.getDeclaredMethods(); + methods = ReflectUtils.findMethods(sigs, methods); + METHOD_0 = methods[0]; + CGLIB$ACCESS_0 = MethodProxy.create(cls, thisClass, "()Ljava/lang/String;", "toString", "CGLIB$ACCESS_0"); + ... + } + */ + + e.push(0); + e.newarray(); + e.putfield(EMPTY_ARGS_NAME); + + Local thisclass = e.make_local(); + Local declaringclass = e.make_local(); + EmitUtils.load_class_this(e); + e.store_local(thisclass); + + Map methodsByClass = CollectionUtils.bucket(methods, METHOD_TO_CLASS); + for (Iterator i = methodsByClass.keySet().iterator(); i.hasNext();) { + ClassInfo classInfo = (ClassInfo)i.next(); + + List classMethods = (List)methodsByClass.get(classInfo); + e.push(2 * classMethods.size()); + e.newarray(Constants.TYPE_STRING); + for (int index = 0; index < classMethods.size(); index++) { + MethodInfo method = (MethodInfo)classMethods.get(index); + Signature sig = method.getSignature(); + e.dup(); + e.push(2 * index); + e.push(sig.getName()); + e.aastore(); + e.dup(); + e.push(2 * index + 1); + e.push(sig.getDescriptor()); + e.aastore(); + } + + EmitUtils.load_class(e, classInfo.getType()); + e.dup(); + e.store_local(declaringclass); + e.invoke_virtual(Constants.TYPE_CLASS, GET_DECLARED_METHODS); + e.invoke_static(REFLECT_UTILS, FIND_METHODS); + + for (int index = 0; index < classMethods.size(); index++) { + MethodInfo method = (MethodInfo)classMethods.get(index); + Signature sig = method.getSignature(); + Signature impl = context.getImplSignature(method); + e.dup(); + e.push(index); + e.array_load(METHOD); + e.putfield(getMethodField(impl)); + + e.load_local(declaringclass); + e.load_local(thisclass); + e.push(sig.getDescriptor()); + e.push(sig.getName()); + e.push(impl.getName()); + e.invoke_static(METHOD_PROXY, MAKE_PROXY); + e.putfield(getMethodProxyField(impl)); + } + e.pop(); + } + } + + public void generateFindProxy(ClassEmitter ce, final Map sigMap) { + final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC | Constants.ACC_STATIC, + FIND_PROXY, + null); + e.load_arg(0); + e.invoke_virtual(Constants.TYPE_OBJECT, TO_STRING); + ObjectSwitchCallback callback = new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + e.getfield((String)sigMap.get(key)); + e.return_value(); + } + public void processDefault() { + e.aconst_null(); + e.return_value(); + } + }; + EmitUtils.string_switch(e, + (String[])sigMap.keySet().toArray(new String[0]), + Constants.SWITCH_STYLE_HASH, + callback); + e.end_method(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/MethodProxy.java b/blade-aop/src/main/java/net/sf/cglib/proxy/MethodProxy.java new file mode 100644 index 000000000..c8bc95f9a --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/MethodProxy.java @@ -0,0 +1,233 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import net.sf.cglib.core.AbstractClassGenerator; +import net.sf.cglib.core.CodeGenerationException; +import net.sf.cglib.core.GeneratorStrategy; +import net.sf.cglib.core.NamingPolicy; +import net.sf.cglib.core.Signature; +import net.sf.cglib.reflect.FastClass; + +/** + * Classes generated by {@link Enhancer} pass this object to the + * registered {@link MethodInterceptor} objects when an intercepted method is invoked. It can + * be used to either invoke the original method, or call the same method on a different + * object of the same type. + * @version $Id: MethodProxy.java,v 1.16 2009/01/11 20:09:48 herbyderby Exp $ + */ +public class MethodProxy { + private Signature sig1; + private Signature sig2; + private CreateInfo createInfo; + + private final Object initLock = new Object(); + private volatile FastClassInfo fastClassInfo; + + /** + * For internal use by {@link Enhancer} only; see the {@link net.sf.cglib.reflect.FastMethod} class + * for similar functionality. + */ + public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) { + MethodProxy proxy = new MethodProxy(); + proxy.sig1 = new Signature(name1, desc); + proxy.sig2 = new Signature(name2, desc); + proxy.createInfo = new CreateInfo(c1, c2); + return proxy; + } + + private void init() + { + /* + * Using a volatile invariant allows us to initialize the FastClass and + * method index pairs atomically. + * + * Double-checked locking is safe with volatile in Java 5. Before 1.5 this + * code could allow fastClassInfo to be instantiated more than once, which + * appears to be benign. + */ + if (fastClassInfo == null) + { + synchronized (initLock) + { + if (fastClassInfo == null) + { + CreateInfo ci = createInfo; + + FastClassInfo fci = new FastClassInfo(); + fci.f1 = helper(ci, ci.c1); + fci.f2 = helper(ci, ci.c2); + fci.i1 = fci.f1.getIndex(sig1); + fci.i2 = fci.f2.getIndex(sig2); + fastClassInfo = fci; + createInfo = null; + } + } + } + } + + private static class FastClassInfo + { + FastClass f1; + FastClass f2; + int i1; + int i2; + } + + private static class CreateInfo + { + Class c1; + Class c2; + NamingPolicy namingPolicy; + GeneratorStrategy strategy; + boolean attemptLoad; + + public CreateInfo(Class c1, Class c2) + { + this.c1 = c1; + this.c2 = c2; + AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent(); + if (fromEnhancer != null) { + namingPolicy = fromEnhancer.getNamingPolicy(); + strategy = fromEnhancer.getStrategy(); + attemptLoad = fromEnhancer.getAttemptLoad(); + } + } + } + + private static FastClass helper(CreateInfo ci, Class type) { + FastClass.Generator g = new FastClass.Generator(); + g.setType(type); + g.setClassLoader(ci.c2.getClassLoader()); + g.setNamingPolicy(ci.namingPolicy); + g.setStrategy(ci.strategy); + g.setAttemptLoad(ci.attemptLoad); + return g.create(); + } + + private MethodProxy() { + } + + /** + * Return the signature of the proxied method. + */ + public Signature getSignature() { + return sig1; + } + + /** + * Return the name of the synthetic method created by CGLIB which is + * used by {@link #invokeSuper} to invoke the superclass + * (non-intercepted) method implementation. The parameter types are + * the same as the proxied method. + */ + public String getSuperName() { + return sig2.getName(); + } + + /** + * Return the {@link net.sf.cglib.reflect.FastClass} method index + * for the method used by {@link #invokeSuper}. This index uniquely + * identifies the method within the generated proxy, and therefore + * can be useful to reference external metadata. + * @see #getSuperName + */ + public int getSuperIndex() { + init(); + return fastClassInfo.i2; + } + + // For testing + FastClass getFastClass() { + init(); + return fastClassInfo.f1; + } + + // For testing + FastClass getSuperFastClass() { + init(); + return fastClassInfo.f2; + } + + /** + * Return the MethodProxy used when intercepting the method + * matching the given signature. + * @param type the class generated by Enhancer + * @param sig the signature to match + * @return the MethodProxy instance, or null if no applicable matching method is found + * @throws IllegalArgumentException if the Class was not created by Enhancer or does not use a MethodInterceptor + */ + public static MethodProxy find(Class type, Signature sig) { + try { + Method m = type.getDeclaredMethod(MethodInterceptorGenerator.FIND_PROXY_NAME, + MethodInterceptorGenerator.FIND_PROXY_TYPES); + return (MethodProxy)m.invoke(null, new Object[]{ sig }); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("Class " + type + " does not use a MethodInterceptor"); + } catch (IllegalAccessException e) { + throw new CodeGenerationException(e); + } catch (InvocationTargetException e) { + throw new CodeGenerationException(e); + } + } + + /** + * Invoke the original method, on a different object of the same type. + * @param obj the compatible object; recursion will result if you use the object passed as the first + * argument to the MethodInterceptor (usually not what you want) + * @param args the arguments passed to the intercepted method; you may substitute a different + * argument array as long as the types are compatible + * @see MethodInterceptor#intercept + * @throws Throwable the bare exceptions thrown by the called method are passed through + * without wrapping in an InvocationTargetException + */ + public Object invoke(Object obj, Object[] args) throws Throwable { + try { + init(); + FastClassInfo fci = fastClassInfo; + return fci.f1.invoke(fci.i1, obj, args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } catch (IllegalArgumentException e) { + if (fastClassInfo.i1 < 0) + throw new IllegalArgumentException("Protected method: " + sig1); + throw e; + } + } + + /** + * Invoke the original (super) method on the specified object. + * @param obj the enhanced object, must be the object passed as the first + * argument to the MethodInterceptor + * @param args the arguments passed to the intercepted method; you may substitute a different + * argument array as long as the types are compatible + * @see MethodInterceptor#intercept + * @throws Throwable the bare exceptions thrown by the called method are passed through + * without wrapping in an InvocationTargetException + */ + public Object invokeSuper(Object obj, Object[] args) throws Throwable { + try { + init(); + FastClassInfo fci = fastClassInfo; + return fci.f2.invoke(fci.i2, obj, args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/Mixin.java b/blade-aop/src/main/java/net/sf/cglib/proxy/Mixin.java new file mode 100644 index 000000000..821bbc47d --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/Mixin.java @@ -0,0 +1,242 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.security.ProtectionDomain; +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; + + + +/** + * Mixin allows + * multiple objects to be combined into a single larger object. The + * methods in the generated object simply call the original methods in the + * underlying "delegate" objects. + * @author Chris Nokleberg + * @version $Id: Mixin.java,v 1.7 2005/09/27 11:42:27 baliuka Exp $ + */ +abstract public class Mixin { + private static final MixinKey KEY_FACTORY = + (MixinKey)KeyFactory.create(MixinKey.class, KeyFactory.CLASS_BY_NAME); + private static final Map ROUTE_CACHE = Collections.synchronizedMap(new HashMap()); + + public static final int STYLE_INTERFACES = 0; + public static final int STYLE_BEANS = 1; + public static final int STYLE_EVERYTHING = 2; + + interface MixinKey { + public Object newInstance(int style, String[] classes, int[] route); + } + + abstract public Mixin newInstance(Object[] delegates); + + /** + * Helper method to create an interface mixin. For finer control over the + * generated instance, use a new instance of Mixin + * instead of this static method. + * TODO + */ + public static Mixin create(Object[] delegates) { + Generator gen = new Generator(); + gen.setDelegates(delegates); + return gen.create(); + } + + /** + * Helper method to create an interface mixin. For finer control over the + * generated instance, use a new instance of Mixin + * instead of this static method. + * TODO + */ + public static Mixin create(Class[] interfaces, Object[] delegates) { + Generator gen = new Generator(); + gen.setClasses(interfaces); + gen.setDelegates(delegates); + return gen.create(); + } + + + public static Mixin createBean(Object[] beans) { + + return createBean(null, beans); + + } + /** + * Helper method to create a bean mixin. For finer control over the + * generated instance, use a new instance of Mixin + * instead of this static method. + * TODO + */ + public static Mixin createBean(ClassLoader loader,Object[] beans) { + Generator gen = new Generator(); + gen.setStyle(STYLE_BEANS); + gen.setDelegates(beans); + gen.setClassLoader(loader); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(Mixin.class.getName()); + + private Class[] classes; + private Object[] delegates; + private int style = STYLE_INTERFACES; + + private int[] route; + + public Generator() { + super(SOURCE); + } + + protected ClassLoader getDefaultClassLoader() { + return classes[0].getClassLoader(); // is this right? + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(classes[0]); + } + + public void setStyle(int style) { + switch (style) { + case STYLE_INTERFACES: + case STYLE_BEANS: + case STYLE_EVERYTHING: + this.style = style; + break; + default: + throw new IllegalArgumentException("Unknown mixin style: " + style); + } + } + + public void setClasses(Class[] classes) { + this.classes = classes; + } + + public void setDelegates(Object[] delegates) { + this.delegates = delegates; + } + + public Mixin create() { + if (classes == null && delegates == null) { + throw new IllegalStateException("Either classes or delegates must be set"); + } + switch (style) { + case STYLE_INTERFACES: + if (classes == null) { + Route r = route(delegates); + classes = r.classes; + route = r.route; + } + break; + case STYLE_BEANS: + // fall-through + case STYLE_EVERYTHING: + if (classes == null) { + classes = ReflectUtils.getClasses(delegates); + } else { + if (delegates != null) { + Class[] temp = ReflectUtils.getClasses(delegates); + if (classes.length != temp.length) { + throw new IllegalStateException("Specified classes are incompatible with delegates"); + } + for (int i = 0; i < classes.length; i++) { + if (!classes[i].isAssignableFrom(temp[i])) { + throw new IllegalStateException("Specified class " + classes[i] + " is incompatible with delegate class " + temp[i] + " (index " + i + ")"); + } + } + } + } + } + setNamePrefix(classes[ReflectUtils.findPackageProtected(classes)].getName()); + + return (Mixin)super.create(KEY_FACTORY.newInstance(style, ReflectUtils.getNames( classes ), route)); + } + + public void generateClass(ClassVisitor v) { + switch (style) { + case STYLE_INTERFACES: + new MixinEmitter(v, getClassName(), classes, route); + break; + case STYLE_BEANS: + new MixinBeanEmitter(v, getClassName(), classes); + break; + case STYLE_EVERYTHING: + new MixinEverythingEmitter(v, getClassName(), classes); + break; + } + } + + protected Object firstInstance(Class type) { + return ((Mixin)ReflectUtils.newInstance(type)).newInstance(delegates); + } + + protected Object nextInstance(Object instance) { + return ((Mixin)instance).newInstance(delegates); + } + } + + public static Class[] getClasses(Object[] delegates) { + return (Class[])route(delegates).classes.clone(); + } + +// public static int[] getRoute(Object[] delegates) { +// return (int[])route(delegates).route.clone(); +// } + + private static Route route(Object[] delegates) { + Object key = ClassesKey.create(delegates); + Route route = (Route)ROUTE_CACHE.get(key); + if (route == null) { + ROUTE_CACHE.put(key, route = new Route(delegates)); + } + return route; + } + + private static class Route + { + private Class[] classes; + private int[] route; + + Route(Object[] delegates) { + Map map = new HashMap(); + ArrayList collect = new ArrayList(); + for (int i = 0; i < delegates.length; i++) { + Class delegate = delegates[i].getClass(); + collect.clear(); + ReflectUtils.addAllInterfaces(delegate, collect); + for (Iterator it = collect.iterator(); it.hasNext();) { + Class iface = (Class)it.next(); + if (!map.containsKey(iface)) { + map.put(iface, new Integer(i)); + } + } + } + classes = new Class[map.size()]; + route = new int[map.size()]; + int index = 0; + for (Iterator it = map.keySet().iterator(); it.hasNext();) { + Class key = (Class)it.next(); + classes[index] = key; + route[index] = ((Integer)map.get(key)).intValue(); + index++; + } + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/MixinBeanEmitter.java b/blade-aop/src/main/java/net/sf/cglib/proxy/MixinBeanEmitter.java new file mode 100644 index 000000000..c48cc9209 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/MixinBeanEmitter.java @@ -0,0 +1,38 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.lang.reflect.Method; +import net.sf.cglib.core.ReflectUtils; +import org.objectweb.asm.ClassVisitor; + +/** + * @author Chris Nokleberg + * @version $Id: MixinBeanEmitter.java,v 1.2 2004/06/24 21:15:20 herbyderby Exp $ + */ +class MixinBeanEmitter extends MixinEmitter { + public MixinBeanEmitter(ClassVisitor v, String className, Class[] classes) { + super(v, className, classes, null); + } + + protected Class[] getInterfaces(Class[] classes) { + return null; + } + + protected Method[] getMethods(Class type) { + return ReflectUtils.getPropertyMethods(ReflectUtils.getBeanProperties(type), true, true); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/MixinEmitter.java b/blade-aop/src/main/java/net/sf/cglib/proxy/MixinEmitter.java new file mode 100644 index 000000000..b5cfc6d97 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/MixinEmitter.java @@ -0,0 +1,93 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.lang.reflect.Method; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Type; + +/** + * @author Chris Nokleberg + * @version $Id: MixinEmitter.java,v 1.9 2006/08/27 21:04:37 herbyderby Exp $ + */ +class MixinEmitter extends ClassEmitter { + private static final String FIELD_NAME = "CGLIB$DELEGATES"; + private static final Signature CSTRUCT_OBJECT_ARRAY = + TypeUtils.parseConstructor("Object[]"); + private static final Type MIXIN = + TypeUtils.parseType("net.sf.cglib.proxy.Mixin"); + private static final Signature NEW_INSTANCE = + new Signature("newInstance", MIXIN, new Type[]{ Constants.TYPE_OBJECT_ARRAY }); + + public MixinEmitter(ClassVisitor v, String className, Class[] classes, int[] route) { + super(v); + + begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + className, + MIXIN, + TypeUtils.getTypes(getInterfaces(classes)), + Constants.SOURCE_FILE); + EmitUtils.null_constructor(this); + EmitUtils.factory_method(this, NEW_INSTANCE); + + declare_field(Constants.ACC_PRIVATE, FIELD_NAME, Constants.TYPE_OBJECT_ARRAY, null); + + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT_ARRAY, null); + e.load_this(); + e.super_invoke_constructor(); + e.load_this(); + e.load_arg(0); + e.putfield(FIELD_NAME); + e.return_value(); + e.end_method(); + + Set unique = new HashSet(); + for (int i = 0; i < classes.length; i++) { + Method[] methods = getMethods(classes[i]); + for (int j = 0; j < methods.length; j++) { + if (unique.add(MethodWrapper.create(methods[j]))) { + MethodInfo method = ReflectUtils.getMethodInfo(methods[j]); + int modifiers = Constants.ACC_PUBLIC; + if ((method.getModifiers() & Constants.ACC_VARARGS) == Constants.ACC_VARARGS) { + modifiers |= Constants.ACC_VARARGS; + } + e = EmitUtils.begin_method(this, method, modifiers); + e.load_this(); + e.getfield(FIELD_NAME); + e.aaload((route != null) ? route[i] : i); + e.checkcast(method.getClassInfo().getType()); + e.load_args(); + e.invoke(method); + e.return_value(); + e.end_method(); + } + } + } + + end_class(); + } + + protected Class[] getInterfaces(Class[] classes) { + return classes; + } + + protected Method[] getMethods(Class type) { + return type.getMethods(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/MixinEverythingEmitter.java b/blade-aop/src/main/java/net/sf/cglib/proxy/MixinEverythingEmitter.java new file mode 100644 index 000000000..ffb45700b --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/MixinEverythingEmitter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import net.sf.cglib.core.CollectionUtils; +import net.sf.cglib.core.ReflectUtils; +import net.sf.cglib.core.RejectModifierPredicate; +import org.objectweb.asm.ClassVisitor; + +/** + * @author Chris Nokleberg + * @version $Id: MixinEverythingEmitter.java,v 1.3 2004/06/24 21:15:19 herbyderby Exp $ + */ +class MixinEverythingEmitter extends MixinEmitter { + + public MixinEverythingEmitter(ClassVisitor v, String className, Class[] classes) { + super(v, className, classes, null); + } + + protected Class[] getInterfaces(Class[] classes) { + List list = new ArrayList(); + for (int i = 0; i < classes.length; i++) { + ReflectUtils.addAllInterfaces(classes[i], list); + } + return (Class[])list.toArray(new Class[list.size()]); + } + + protected Method[] getMethods(Class type) { + List methods = new ArrayList(Arrays.asList(type.getMethods())); + CollectionUtils.filter(methods, new RejectModifierPredicate(Modifier.FINAL | Modifier.STATIC)); + return (Method[])methods.toArray(new Method[methods.size()]); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/NoOp.java b/blade-aop/src/main/java/net/sf/cglib/proxy/NoOp.java new file mode 100644 index 000000000..227617590 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/NoOp.java @@ -0,0 +1,28 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +/** + * Methods using this {@link Enhancer} callback will delegate directly to the + * default (super) implementation in the base class. + */ +public interface NoOp extends Callback +{ + /** + * A thread-safe singleton instance of the NoOp callback. + */ + public static final NoOp INSTANCE = new NoOp() { }; +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/NoOpGenerator.java b/blade-aop/src/main/java/net/sf/cglib/proxy/NoOpGenerator.java new file mode 100644 index 000000000..84920be6b --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/NoOpGenerator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.util.Iterator; +import java.util.List; +import net.sf.cglib.core.*; + +class NoOpGenerator +implements CallbackGenerator +{ + public static final NoOpGenerator INSTANCE = new NoOpGenerator(); + + public void generate(ClassEmitter ce, Context context, List methods) { + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + if (TypeUtils.isBridge(method.getModifiers()) || ( + TypeUtils.isProtected(context.getOriginalModifiers(method)) && + TypeUtils.isPublic(method.getModifiers()))) { + CodeEmitter e = EmitUtils.begin_method(ce, method); + e.load_this(); + e.load_args(); + context.emitInvoke(e, method); + e.return_value(); + e.end_method(); + } + } + } + + public void generateStatic(CodeEmitter e, Context context, List methods) { } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/Proxy.java b/blade-aop/src/main/java/net/sf/cglib/proxy/Proxy.java new file mode 100644 index 000000000..fe5f4f93e --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/Proxy.java @@ -0,0 +1,101 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.lang.reflect.Member; +import net.sf.cglib.core.CodeGenerationException; + +/** + * This class is meant to be used as replacement for + * java.lang.reflect.Proxy under JDK 1.2. There are some known + * subtle differences: + *
    + *
  • The exceptions returned by invoking getExceptionTypes + * on the Method passed to the invoke method + * are the exact set that can be thrown without resulting in an + * UndeclaredThrowableException being thrown. + *
  • {@link UndeclaredThrowableException} is used instead + * of java.lang.reflect.UndeclaredThrowableException. + *
+ *

+ * @version $Id: Proxy.java,v 1.6 2004/06/24 21:15:19 herbyderby Exp $ + */ +public class Proxy implements Serializable { + protected InvocationHandler h; + + private static final CallbackFilter BAD_OBJECT_METHOD_FILTER = new CallbackFilter() { + public int accept(Method method) { + if (method.getDeclaringClass().getName().equals("java.lang.Object")) { + String name = method.getName(); + if (!(name.equals("hashCode") || + name.equals("equals") || + name.equals("toString"))) { + return 1; + } + } + return 0; + } + }; + + protected Proxy(InvocationHandler h) { + Enhancer.registerCallbacks(getClass(), new Callback[]{ h, null }); + this.h = h; + } + + // private for security of isProxyClass + private static class ProxyImpl extends Proxy { + protected ProxyImpl(InvocationHandler h) { + super(h); + } + } + + public static InvocationHandler getInvocationHandler(Object proxy) { + if (!(proxy instanceof ProxyImpl)) { + throw new IllegalArgumentException("Object is not a proxy"); + } + return ((Proxy)proxy).h; + } + + public static Class getProxyClass(ClassLoader loader, Class[] interfaces) { + Enhancer e = new Enhancer(); + e.setSuperclass(ProxyImpl.class); + e.setInterfaces(interfaces); + e.setCallbackTypes(new Class[]{ + InvocationHandler.class, + NoOp.class, + }); + e.setCallbackFilter(BAD_OBJECT_METHOD_FILTER); + e.setUseFactory(false); + return e.createClass(); + } + + public static boolean isProxyClass(Class cl) { + return cl.getSuperclass().equals(ProxyImpl.class); + } + + public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) { + try { + Class clazz = getProxyClass(loader, interfaces); + return clazz.getConstructor(new Class[]{ InvocationHandler.class }).newInstance(new Object[]{ h }); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new CodeGenerationException(e); + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/ProxyRefDispatcher.java b/blade-aop/src/main/java/net/sf/cglib/proxy/ProxyRefDispatcher.java new file mode 100644 index 000000000..701088590 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/ProxyRefDispatcher.java @@ -0,0 +1,31 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.proxy; + +/** + * Dispatching {@link Enhancer} callback. This is the same as the + * {@link Dispatcher} except for the addition of an argument + * which references the proxy object. + */ +public interface ProxyRefDispatcher extends Callback { + /** + * Return the object which the original method invocation should + * be dispatched. This method is called for every method invocation. + * @param proxy a reference to the proxy (generated) object + * @return an object that can invoke the method + */ + Object loadObject(Object proxy) throws Exception; +} diff --git a/blade-aop/src/main/java/net/sf/cglib/proxy/UndeclaredThrowableException.java b/blade-aop/src/main/java/net/sf/cglib/proxy/UndeclaredThrowableException.java new file mode 100644 index 000000000..09f8db5a1 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/proxy/UndeclaredThrowableException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002,2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.cglib.proxy; + +import net.sf.cglib.core.CodeGenerationException; + +/** + * Used by {@link Proxy} as a replacement for java.lang.reflect.UndeclaredThrowableException. + * @author Juozas Baliuka + */ +public class UndeclaredThrowableException extends CodeGenerationException { + /** + * Creates a new instance of UndeclaredThrowableException without detail message. + */ + public UndeclaredThrowableException(Throwable t) { + super(t); + } + + public Throwable getUndeclaredThrowable() { + return getCause(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/reflect/ConstructorDelegate.java b/blade-aop/src/main/java/net/sf/cglib/reflect/ConstructorDelegate.java new file mode 100644 index 000000000..ae302c6a7 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/reflect/ConstructorDelegate.java @@ -0,0 +1,123 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.reflect; + +import java.lang.reflect.*; +import java.security.ProtectionDomain; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Type; + +/** + * @author Chris Nokleberg + * @version $Id: ConstructorDelegate.java,v 1.20 2006/03/05 02:43:19 herbyderby Exp $ + */ +abstract public class ConstructorDelegate { + private static final ConstructorKey KEY_FACTORY = + (ConstructorKey)KeyFactory.create(ConstructorKey.class, KeyFactory.CLASS_BY_NAME); + + interface ConstructorKey { + public Object newInstance(String declaring, String iface); + } + + protected ConstructorDelegate() { + } + + public static ConstructorDelegate create(Class targetClass, Class iface) { + Generator gen = new Generator(); + gen.setTargetClass(targetClass); + gen.setInterface(iface); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(ConstructorDelegate.class.getName()); + private static final Type CONSTRUCTOR_DELEGATE = + TypeUtils.parseType("net.sf.cglib.reflect.ConstructorDelegate"); + + private Class iface; + private Class targetClass; + + public Generator() { + super(SOURCE); + } + + public void setInterface(Class iface) { + this.iface = iface; + } + + public void setTargetClass(Class targetClass) { + this.targetClass = targetClass; + } + + public ConstructorDelegate create() { + setNamePrefix(targetClass.getName()); + Object key = KEY_FACTORY.newInstance(iface.getName(), targetClass.getName()); + return (ConstructorDelegate)super.create(key); + } + + protected ClassLoader getDefaultClassLoader() { + return targetClass.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(targetClass); + } + + public void generateClass(ClassVisitor v) { + setNamePrefix(targetClass.getName()); + + final Method newInstance = ReflectUtils.findNewInstance(iface); + if (!newInstance.getReturnType().isAssignableFrom(targetClass)) { + throw new IllegalArgumentException("incompatible return type"); + } + final Constructor constructor; + try { + constructor = targetClass.getDeclaredConstructor(newInstance.getParameterTypes()); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("interface does not match any known constructor"); + } + + ClassEmitter ce = new ClassEmitter(v); + ce.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + getClassName(), + CONSTRUCTOR_DELEGATE, + new Type[]{ Type.getType(iface) }, + Constants.SOURCE_FILE); + Type declaring = Type.getType(constructor.getDeclaringClass()); + EmitUtils.null_constructor(ce); + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, + ReflectUtils.getSignature(newInstance), + ReflectUtils.getExceptionTypes(newInstance)); + e.new_instance(declaring); + e.dup(); + e.load_args(); + e.invoke_constructor(declaring, ReflectUtils.getSignature(constructor)); + e.return_value(); + e.end_method(); + ce.end_class(); + } + + protected Object firstInstance(Class type) { + return ReflectUtils.newInstance(type); + } + + protected Object nextInstance(Object instance) { + return instance; + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/reflect/FastClass.java b/blade-aop/src/main/java/net/sf/cglib/reflect/FastClass.java new file mode 100644 index 000000000..ce9026ad2 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/reflect/FastClass.java @@ -0,0 +1,207 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.reflect; + +import net.sf.cglib.core.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Type; + +abstract public class FastClass +{ + private Class type; + + protected FastClass() { + throw new Error("Using the FastClass empty constructor--please report to the cglib-devel mailing list"); + } + + protected FastClass(Class type) { + this.type = type; + } + + public static FastClass create(Class type) { + + return create(type.getClassLoader(),type); + + } + public static FastClass create(ClassLoader loader, Class type) { + Generator gen = new Generator(); + gen.setType(type); + gen.setClassLoader(loader); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator + { + private static final Source SOURCE = new Source(FastClass.class.getName()); + private Class type; + + public Generator() { + super(SOURCE); + } + + public void setType(Class type) { + this.type = type; + } + + public FastClass create() { + setNamePrefix(type.getName()); + return (FastClass)super.create(type.getName()); + } + + protected ClassLoader getDefaultClassLoader() { + return type.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(type); + } + + public void generateClass(ClassVisitor v) throws Exception { + new FastClassEmitter(v, getClassName(), type); + } + + protected Object firstInstance(Class type) { + return ReflectUtils.newInstance(type, + new Class[]{ Class.class }, + new Object[]{ this.type }); + } + + protected Object nextInstance(Object instance) { + return instance; + } + } + + public Object invoke(String name, Class[] parameterTypes, Object obj, Object[] args) throws InvocationTargetException { + return invoke(getIndex(name, parameterTypes), obj, args); + } + + public Object newInstance() throws InvocationTargetException { + return newInstance(getIndex(Constants.EMPTY_CLASS_ARRAY), null); + } + + public Object newInstance(Class[] parameterTypes, Object[] args) throws InvocationTargetException { + return newInstance(getIndex(parameterTypes), args); + } + + public FastMethod getMethod(Method method) { + return new FastMethod(this, method); + } + + public FastConstructor getConstructor(Constructor constructor) { + return new FastConstructor(this, constructor); + } + + public FastMethod getMethod(String name, Class[] parameterTypes) { + try { + return getMethod(type.getMethod(name, parameterTypes)); + } catch (NoSuchMethodException e) { + throw new NoSuchMethodError(e.getMessage()); + } + } + + public FastConstructor getConstructor(Class[] parameterTypes) { + try { + return getConstructor(type.getConstructor(parameterTypes)); + } catch (NoSuchMethodException e) { + throw new NoSuchMethodError(e.getMessage()); + } + } + + public String getName() { + return type.getName(); + } + + public Class getJavaClass() { + return type; + } + + public String toString() { + return type.toString(); + } + + public int hashCode() { + return type.hashCode(); + } + + public boolean equals(Object o) { + if (o == null || !(o instanceof FastClass)) { + return false; + } + return type.equals(((FastClass)o).type); + } + + /** + * Return the index of the matching method. The index may be used + * later to invoke the method with less overhead. If more than one + * method matches (i.e. they differ by return type only), one is + * chosen arbitrarily. + * @see #invoke(int, Object, Object[]) + * @param name the method name + * @param parameterTypes the parameter array + * @return the index, or -1 if none is found. + */ + abstract public int getIndex(String name, Class[] parameterTypes); + + /** + * Return the index of the matching constructor. The index may be used + * later to create a new instance with less overhead. + * @see #newInstance(int, Object[]) + * @param parameterTypes the parameter array + * @return the constructor index, or -1 if none is found. + */ + abstract public int getIndex(Class[] parameterTypes); + + /** + * Invoke the method with the specified index. + * @see getIndex(name, Class[]) + * @param index the method index + * @param obj the object the underlying method is invoked from + * @param args the arguments used for the method call + * @throws java.lang.reflect.InvocationTargetException if the underlying method throws an exception + */ + abstract public Object invoke(int index, Object obj, Object[] args) throws InvocationTargetException; + + /** + * Create a new instance using the specified constructor index and arguments. + * @see getIndex(Class[]) + * @param index the constructor index + * @param args the arguments passed to the constructor + * @throws java.lang.reflect.InvocationTargetException if the constructor throws an exception + */ + abstract public Object newInstance(int index, Object[] args) throws InvocationTargetException; + + abstract public int getIndex(Signature sig); + + /** + * Returns the maximum method index for this class. + */ + abstract public int getMaxIndex(); + + protected static String getSignatureWithoutReturnType(String name, Class[] parameterTypes) { + StringBuffer sb = new StringBuffer(); + sb.append(name); + sb.append('('); + for (int i = 0; i < parameterTypes.length; i++) { + sb.append(Type.getDescriptor(parameterTypes[i])); + } + sb.append(')'); + return sb.toString(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/reflect/FastClassEmitter.java b/blade-aop/src/main/java/net/sf/cglib/reflect/FastClassEmitter.java new file mode 100644 index 000000000..61990e28f --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/reflect/FastClassEmitter.java @@ -0,0 +1,226 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.reflect; + +import java.lang.reflect.*; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; + +class FastClassEmitter extends ClassEmitter { + private static final Signature CSTRUCT_CLASS = + TypeUtils.parseConstructor("Class"); + private static final Signature METHOD_GET_INDEX = + TypeUtils.parseSignature("int getIndex(String, Class[])"); + private static final Signature SIGNATURE_GET_INDEX = + new Signature("getIndex", Type.INT_TYPE, new Type[]{ Constants.TYPE_SIGNATURE }); + private static final Signature TO_STRING = + TypeUtils.parseSignature("String toString()"); + private static final Signature CONSTRUCTOR_GET_INDEX = + TypeUtils.parseSignature("int getIndex(Class[])"); + private static final Signature INVOKE = + TypeUtils.parseSignature("Object invoke(int, Object, Object[])"); + private static final Signature NEW_INSTANCE = + TypeUtils.parseSignature("Object newInstance(int, Object[])"); + private static final Signature GET_MAX_INDEX = + TypeUtils.parseSignature("int getMaxIndex()"); + private static final Signature GET_SIGNATURE_WITHOUT_RETURN_TYPE = + TypeUtils.parseSignature("String getSignatureWithoutReturnType(String, Class[])"); + private static final Type FAST_CLASS = + TypeUtils.parseType("net.sf.cglib.reflect.FastClass"); + private static final Type ILLEGAL_ARGUMENT_EXCEPTION = + TypeUtils.parseType("IllegalArgumentException"); + private static final Type INVOCATION_TARGET_EXCEPTION = + TypeUtils.parseType("java.lang.reflect.InvocationTargetException"); + private static final Type[] INVOCATION_TARGET_EXCEPTION_ARRAY = { INVOCATION_TARGET_EXCEPTION }; + + public FastClassEmitter(ClassVisitor v, String className, Class type) { + super(v); + + Type base = Type.getType(type); + begin_class(Constants.V1_2, Constants.ACC_PUBLIC, className, FAST_CLASS, null, Constants.SOURCE_FILE); + + // constructor + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, CSTRUCT_CLASS, null); + e.load_this(); + e.load_args(); + e.super_invoke_constructor(CSTRUCT_CLASS); + e.return_value(); + e.end_method(); + + VisibilityPredicate vp = new VisibilityPredicate(type, false); + List methods = ReflectUtils.addAllMethods(type, new ArrayList()); + CollectionUtils.filter(methods, vp); + CollectionUtils.filter(methods, new DuplicatesPredicate()); + List constructors = new ArrayList(Arrays.asList(type.getDeclaredConstructors())); + CollectionUtils.filter(constructors, vp); + + // getIndex(String) + emitIndexBySignature(methods); + + // getIndex(String, Class[]) + emitIndexByClassArray(methods); + + // getIndex(Class[]) + e = begin_method(Constants.ACC_PUBLIC, CONSTRUCTOR_GET_INDEX, null); + e.load_args(); + List info = CollectionUtils.transform(constructors, MethodInfoTransformer.getInstance()); + EmitUtils.constructor_switch(e, info, new GetIndexCallback(e, info)); + e.end_method(); + + // invoke(int, Object, Object[]) + e = begin_method(Constants.ACC_PUBLIC, INVOKE, INVOCATION_TARGET_EXCEPTION_ARRAY); + e.load_arg(1); + e.checkcast(base); + e.load_arg(0); + invokeSwitchHelper(e, methods, 2, base); + e.end_method(); + + // newInstance(int, Object[]) + e = begin_method(Constants.ACC_PUBLIC, NEW_INSTANCE, INVOCATION_TARGET_EXCEPTION_ARRAY); + e.new_instance(base); + e.dup(); + e.load_arg(0); + invokeSwitchHelper(e, constructors, 1, base); + e.end_method(); + + // getMaxIndex() + e = begin_method(Constants.ACC_PUBLIC, GET_MAX_INDEX, null); + e.push(methods.size() - 1); + e.return_value(); + e.end_method(); + + end_class(); + } + + // TODO: support constructor indices ("") + private void emitIndexBySignature(List methods) { + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, SIGNATURE_GET_INDEX, null); + List signatures = CollectionUtils.transform(methods, new Transformer() { + public Object transform(Object obj) { + return ReflectUtils.getSignature((Method)obj).toString(); + } + }); + e.load_arg(0); + e.invoke_virtual(Constants.TYPE_OBJECT, TO_STRING); + signatureSwitchHelper(e, signatures); + e.end_method(); + } + + private static final int TOO_MANY_METHODS = 100; // TODO + private void emitIndexByClassArray(List methods) { + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, METHOD_GET_INDEX, null); + if (methods.size() > TOO_MANY_METHODS) { + // hack for big classes + List signatures = CollectionUtils.transform(methods, new Transformer() { + public Object transform(Object obj) { + String s = ReflectUtils.getSignature((Method)obj).toString(); + return s.substring(0, s.lastIndexOf(')') + 1); + } + }); + e.load_args(); + e.invoke_static(FAST_CLASS, GET_SIGNATURE_WITHOUT_RETURN_TYPE); + signatureSwitchHelper(e, signatures); + } else { + e.load_args(); + List info = CollectionUtils.transform(methods, MethodInfoTransformer.getInstance()); + EmitUtils.method_switch(e, info, new GetIndexCallback(e, info)); + } + e.end_method(); + } + + private void signatureSwitchHelper(final CodeEmitter e, final List signatures) { + ObjectSwitchCallback callback = new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + // TODO: remove linear indexOf + e.push(signatures.indexOf(key)); + e.return_value(); + } + public void processDefault() { + e.push(-1); + e.return_value(); + } + }; + EmitUtils.string_switch(e, + (String[])signatures.toArray(new String[signatures.size()]), + Constants.SWITCH_STYLE_HASH, + callback); + } + + private static void invokeSwitchHelper(final CodeEmitter e, List members, final int arg, final Type base) { + final List info = CollectionUtils.transform(members, MethodInfoTransformer.getInstance()); + final Label illegalArg = e.make_label(); + Block block = e.begin_block(); + e.process_switch(getIntRange(info.size()), new ProcessSwitchCallback() { + public void processCase(int key, Label end) { + MethodInfo method = (MethodInfo)info.get(key); + Type[] types = method.getSignature().getArgumentTypes(); + for (int i = 0; i < types.length; i++) { + e.load_arg(arg); + e.aaload(i); + e.unbox(types[i]); + } + // TODO: change method lookup process so MethodInfo will already reference base + // instead of superclass when superclass method is inaccessible + e.invoke(method, base); + if (!TypeUtils.isConstructor(method)) { + e.box(method.getSignature().getReturnType()); + } + e.return_value(); + } + public void processDefault() { + e.goTo(illegalArg); + } + }); + block.end(); + EmitUtils.wrap_throwable(block, INVOCATION_TARGET_EXCEPTION); + e.mark(illegalArg); + e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Cannot find matching method/constructor"); + } + + private static class GetIndexCallback implements ObjectSwitchCallback { + private CodeEmitter e; + private Map indexes = new HashMap(); + + public GetIndexCallback(CodeEmitter e, List methods) { + this.e = e; + int index = 0; + for (Iterator it = methods.iterator(); it.hasNext();) { + indexes.put(it.next(), new Integer(index++)); + } + } + + public void processCase(Object key, Label end) { + e.push(((Integer)indexes.get(key)).intValue()); + e.return_value(); + } + + public void processDefault() { + e.push(-1); + e.return_value(); + } + } + + private static int[] getIntRange(int length) { + int[] range = new int[length]; + for (int i = 0; i < length; i++) { + range[i] = i; + } + return range; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/reflect/FastConstructor.java b/blade-aop/src/main/java/net/sf/cglib/reflect/FastConstructor.java new file mode 100644 index 000000000..7b25697c2 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/reflect/FastConstructor.java @@ -0,0 +1,46 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.reflect; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +public class FastConstructor extends FastMember +{ + FastConstructor(FastClass fc, Constructor constructor) { + super(fc, constructor, fc.getIndex(constructor.getParameterTypes())); + } + + public Class[] getParameterTypes() { + return ((Constructor)member).getParameterTypes(); + } + + public Class[] getExceptionTypes() { + return ((Constructor)member).getExceptionTypes(); + } + + public Object newInstance() throws InvocationTargetException { + return fc.newInstance(index, null); + } + + public Object newInstance(Object[] args) throws InvocationTargetException { + return fc.newInstance(index, args); + } + + public Constructor getJavaConstructor() { + return (Constructor)member; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/reflect/FastMember.java b/blade-aop/src/main/java/net/sf/cglib/reflect/FastMember.java new file mode 100644 index 000000000..08dc5cc7f --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/reflect/FastMember.java @@ -0,0 +1,65 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.reflect; + +import java.lang.reflect.Member; + +abstract public class FastMember +{ + protected FastClass fc; + protected Member member; + protected int index; + + protected FastMember(FastClass fc, Member member, int index) { + this.fc = fc; + this.member = member; + this.index = index; + } + + abstract public Class[] getParameterTypes(); + abstract public Class[] getExceptionTypes(); + + public int getIndex() { + return index; + } + + public String getName() { + return member.getName(); + } + + public Class getDeclaringClass() { + return fc.getJavaClass(); + } + + public int getModifiers() { + return member.getModifiers(); + } + + public String toString() { + return member.toString(); + } + + public int hashCode() { + return member.hashCode(); + } + + public boolean equals(Object o) { + if (o == null || !(o instanceof FastMember)) { + return false; + } + return member.equals(((FastMember)o).member); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/reflect/FastMethod.java b/blade-aop/src/main/java/net/sf/cglib/reflect/FastMethod.java new file mode 100644 index 000000000..0c027be3a --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/reflect/FastMethod.java @@ -0,0 +1,63 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.reflect; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import net.sf.cglib.core.Signature; + +import org.objectweb.asm.Type; + +public class FastMethod extends FastMember +{ + FastMethod(FastClass fc, Method method) { + super(fc, method, helper(fc, method)); + } + + private static int helper(FastClass fc, Method method) { + int index = fc.getIndex(new Signature(method.getName(), Type.getMethodDescriptor(method))); + if (index < 0) { + Class[] types = method.getParameterTypes(); + System.err.println("hash=" + method.getName().hashCode() + " size=" + types.length); + for (int i = 0; i < types.length; i++) { + System.err.println(" types[" + i + "]=" + types[i].getName()); + } + throw new IllegalArgumentException("Cannot find method " + method); + } + return index; + } + + public Class getReturnType() { + return ((Method)member).getReturnType(); + } + + public Class[] getParameterTypes() { + return ((Method)member).getParameterTypes(); + } + + public Class[] getExceptionTypes() { + return ((Method)member).getExceptionTypes(); + } + + public Object invoke(Object obj, Object[] args) throws InvocationTargetException { + return fc.invoke(index, obj, args); + } + + public Method getJavaMethod() { + return (Method)member; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/reflect/MethodDelegate.java b/blade-aop/src/main/java/net/sf/cglib/reflect/MethodDelegate.java new file mode 100644 index 000000000..54da1d355 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/reflect/MethodDelegate.java @@ -0,0 +1,268 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.reflect; + +import java.lang.reflect.*; +import java.security.ProtectionDomain; +import net.sf.cglib.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Type; + +// TODO: don't require exact match for return type + +/** + * DOCUMENTATION FROM APACHE AVALON DELEGATE CLASS + * + *

+ * Delegates are a typesafe pointer to another method. Since Java does not + * have language support for such a construct, this utility will construct + * a proxy that forwards method calls to any method with the same signature. + * This utility is inspired in part by the C# delegate mechanism. We + * implemented it in a Java-centric manner. + *

+ * + *

Delegate

+ *

+ * Any interface with one method can become the interface for a delegate. + * Consider the example below: + *

+ * + *
+ *   public interface MainDelegate {
+ *       int main(String[] args);
+ *   }
+ * 
+ * + *

+ * The interface above is an example of an interface that can become a + * delegate. It has only one method, and the interface is public. In + * order to create a delegate for that method, all we have to do is + * call MethodDelegate.create(this, "alternateMain", MainDelegate.class). + * The following program will show how to use it: + *

+ * + *
+ *   public class Main {
+ *       public static int main( String[] args ) {
+ *           Main newMain = new Main();
+ *           MainDelegate start = (MainDelegate)
+ *               MethodDelegate.create(newMain, "alternateMain", MainDelegate.class);
+ *           return start.main( args );
+ *       }
+ *
+ *       public int alternateMain( String[] args ) {
+ *           for (int i = 0; i < args.length; i++) {
+ *               System.out.println( args[i] );
+ *           }
+ *           return args.length;
+ *       }
+ *   }
+ * 
+ * + *

+ * By themselves, delegates don't do much. Their true power lies in the fact that + * they can be treated like objects, and passed to other methods. In fact that is + * one of the key building blocks of building Intelligent Agents which in tern are + * the foundation of artificial intelligence. In the above program, we could have + * easily created the delegate to match the static main method by + * substituting the delegate creation call with this: + * MethodDelegate.createStatic(getClass(), "main", MainDelegate.class). + *

+ *

+ * Another key use for Delegates is to register event listeners. It is much easier + * to have all the code for your events separated out into methods instead of individual + * classes. One of the ways Java gets around that is to create anonymous classes. + * They are particularly troublesome because many Debuggers do not know what to do + * with them. Anonymous classes tend to duplicate alot of code as well. We can + * use any interface with one declared method to forward events to any method that + * matches the signature (although the method name can be different). + *

+ * + *

Equality

+ * The criteria that we use to test if two delegates are equal are: + *
    + *
  • + * They both refer to the same instance. That is, the instance + * parameter passed to the newDelegate method was the same for both. The + * instances are compared with the identity equality operator, ==. + *
  • + *
  • They refer to the same method as resolved by Method.equals.
  • + *
+ * + * @version $Id: MethodDelegate.java,v 1.25 2006/03/05 02:43:19 herbyderby Exp $ + */ +abstract public class MethodDelegate { + private static final MethodDelegateKey KEY_FACTORY = + (MethodDelegateKey)KeyFactory.create(MethodDelegateKey.class, KeyFactory.CLASS_BY_NAME); + + protected Object target; + protected String eqMethod; + + interface MethodDelegateKey { + Object newInstance(Class delegateClass, String methodName, Class iface); + } + + public static MethodDelegate createStatic(Class targetClass, String methodName, Class iface) { + Generator gen = new Generator(); + gen.setTargetClass(targetClass); + gen.setMethodName(methodName); + gen.setInterface(iface); + return gen.create(); + } + + public static MethodDelegate create(Object target, String methodName, Class iface) { + Generator gen = new Generator(); + gen.setTarget(target); + gen.setMethodName(methodName); + gen.setInterface(iface); + return gen.create(); + } + + public boolean equals(Object obj) { + MethodDelegate other = (MethodDelegate)obj; + return (other != null && target == other.target) && eqMethod.equals(other.eqMethod); + } + + public int hashCode() { + return target.hashCode() ^ eqMethod.hashCode(); + } + + public Object getTarget() { + return target; + } + + abstract public MethodDelegate newInstance(Object target); + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(MethodDelegate.class.getName()); + private static final Type METHOD_DELEGATE = + TypeUtils.parseType("net.sf.cglib.reflect.MethodDelegate"); + private static final Signature NEW_INSTANCE = + new Signature("newInstance", METHOD_DELEGATE, new Type[]{ Constants.TYPE_OBJECT }); + + private Object target; + private Class targetClass; + private String methodName; + private Class iface; + + public Generator() { + super(SOURCE); + } + + public void setTarget(Object target) { + this.target = target; + this.targetClass = target.getClass(); + } + + public void setTargetClass(Class targetClass) { + this.targetClass = targetClass; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public void setInterface(Class iface) { + this.iface = iface; + } + + protected ClassLoader getDefaultClassLoader() { + return targetClass.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(targetClass); + } + + public MethodDelegate create() { + setNamePrefix(targetClass.getName()); + Object key = KEY_FACTORY.newInstance(targetClass, methodName, iface); + return (MethodDelegate)super.create(key); + } + + protected Object firstInstance(Class type) { + return ((MethodDelegate)ReflectUtils.newInstance(type)).newInstance(target); + } + + protected Object nextInstance(Object instance) { + return ((MethodDelegate)instance).newInstance(target); + } + + public void generateClass(ClassVisitor v) throws NoSuchMethodException { + Method proxy = ReflectUtils.findInterfaceMethod(iface); + final Method method = targetClass.getMethod(methodName, proxy.getParameterTypes()); + if (!proxy.getReturnType().isAssignableFrom(method.getReturnType())) { + throw new IllegalArgumentException("incompatible return types"); + } + + MethodInfo methodInfo = ReflectUtils.getMethodInfo(method); + + boolean isStatic = TypeUtils.isStatic(methodInfo.getModifiers()); + if ((target == null) ^ isStatic) { + throw new IllegalArgumentException("Static method " + (isStatic ? "not " : "") + "expected"); + } + + ClassEmitter ce = new ClassEmitter(v); + CodeEmitter e; + ce.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + getClassName(), + METHOD_DELEGATE, + new Type[]{ Type.getType(iface) }, + Constants.SOURCE_FILE); + ce.declare_field(Constants.PRIVATE_FINAL_STATIC, "eqMethod", Constants.TYPE_STRING, null); + EmitUtils.null_constructor(ce); + + // generate proxied method + MethodInfo proxied = ReflectUtils.getMethodInfo(iface.getDeclaredMethods()[0]); + int modifiers = Constants.ACC_PUBLIC; + if ((proxied.getModifiers() & Constants.ACC_VARARGS) == Constants.ACC_VARARGS) { + modifiers |= Constants.ACC_VARARGS; + } + e = EmitUtils.begin_method(ce, proxied, modifiers); + e.load_this(); + e.super_getfield("target", Constants.TYPE_OBJECT); + e.checkcast(methodInfo.getClassInfo().getType()); + e.load_args(); + e.invoke(methodInfo); + e.return_value(); + e.end_method(); + + // newInstance + e = ce.begin_method(Constants.ACC_PUBLIC, NEW_INSTANCE, null); + e.new_instance_this(); + e.dup(); + e.dup2(); + e.invoke_constructor_this(); + e.getfield("eqMethod"); + e.super_putfield("eqMethod", Constants.TYPE_STRING); + e.load_arg(0); + e.super_putfield("target", Constants.TYPE_OBJECT); + e.return_value(); + e.end_method(); + + // static initializer + e = ce.begin_static(); + e.push(methodInfo.getSignature().toString()); + e.putfield("eqMethod"); + e.return_value(); + e.end_method(); + + ce.end_class(); + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/reflect/MulticastDelegate.java b/blade-aop/src/main/java/net/sf/cglib/reflect/MulticastDelegate.java new file mode 100644 index 000000000..fe5e7b8d2 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/reflect/MulticastDelegate.java @@ -0,0 +1,179 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.reflect; + +import java.lang.reflect.*; +import java.security.ProtectionDomain; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +abstract public class MulticastDelegate implements Cloneable { + protected Object[] targets = {}; + + protected MulticastDelegate() { + } + + public List getTargets() { + return new ArrayList(Arrays.asList(targets)); + } + + abstract public MulticastDelegate add(Object target); + + protected MulticastDelegate addHelper(Object target) { + MulticastDelegate copy = newInstance(); + copy.targets = new Object[targets.length + 1]; + System.arraycopy(targets, 0, copy.targets, 0, targets.length); + copy.targets[targets.length] = target; + return copy; + } + + public MulticastDelegate remove(Object target) { + for (int i = targets.length - 1; i >= 0; i--) { + if (targets[i].equals(target)) { + MulticastDelegate copy = newInstance(); + copy.targets = new Object[targets.length - 1]; + System.arraycopy(targets, 0, copy.targets, 0, i); + System.arraycopy(targets, i + 1, copy.targets, i, targets.length - i - 1); + return copy; + } + } + return this; + } + + abstract public MulticastDelegate newInstance(); + + public static MulticastDelegate create(Class iface) { + Generator gen = new Generator(); + gen.setInterface(iface); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(MulticastDelegate.class.getName()); + private static final Type MULTICAST_DELEGATE = + TypeUtils.parseType("net.sf.cglib.reflect.MulticastDelegate"); + private static final Signature NEW_INSTANCE = + new Signature("newInstance", MULTICAST_DELEGATE, new Type[0]); + private static final Signature ADD_DELEGATE = + new Signature("add", MULTICAST_DELEGATE, new Type[]{ Constants.TYPE_OBJECT }); + private static final Signature ADD_HELPER = + new Signature("addHelper", MULTICAST_DELEGATE, new Type[]{ Constants.TYPE_OBJECT }); + + private Class iface; + + public Generator() { + super(SOURCE); + } + + protected ClassLoader getDefaultClassLoader() { + return iface.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(iface); + } + + public void setInterface(Class iface) { + this.iface = iface; + } + + public MulticastDelegate create() { + setNamePrefix(MulticastDelegate.class.getName()); + return (MulticastDelegate)super.create(iface.getName()); + } + + public void generateClass(ClassVisitor cv) { + final MethodInfo method = ReflectUtils.getMethodInfo(ReflectUtils.findInterfaceMethod(iface)); + + ClassEmitter ce = new ClassEmitter(cv); + ce.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + getClassName(), + MULTICAST_DELEGATE, + new Type[]{ Type.getType(iface) }, + Constants.SOURCE_FILE); + EmitUtils.null_constructor(ce); + + // generate proxied method + emitProxy(ce, method); + + // newInstance + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, NEW_INSTANCE, null); + e.new_instance_this(); + e.dup(); + e.invoke_constructor_this(); + e.return_value(); + e.end_method(); + + // add + e = ce.begin_method(Constants.ACC_PUBLIC, ADD_DELEGATE, null); + e.load_this(); + e.load_arg(0); + e.checkcast(Type.getType(iface)); + e.invoke_virtual_this(ADD_HELPER); + e.return_value(); + e.end_method(); + + ce.end_class(); + } + + private void emitProxy(ClassEmitter ce, final MethodInfo method) { + int modifiers = Constants.ACC_PUBLIC; + if ((method.getModifiers() & Constants.ACC_VARARGS) == Constants.ACC_VARARGS) { + modifiers |= Constants.ACC_VARARGS; + } + final CodeEmitter e = EmitUtils.begin_method(ce, method, modifiers); + Type returnType = method.getSignature().getReturnType(); + final boolean returns = returnType != Type.VOID_TYPE; + Local result = null; + if (returns) { + result = e.make_local(returnType); + e.zero_or_null(returnType); + e.store_local(result); + } + e.load_this(); + e.super_getfield("targets", Constants.TYPE_OBJECT_ARRAY); + final Local result2 = result; + EmitUtils.process_array(e, Constants.TYPE_OBJECT_ARRAY, new ProcessArrayCallback() { + public void processElement(Type type) { + e.checkcast(Type.getType(iface)); + e.load_args(); + e.invoke(method); + if (returns) { + e.store_local(result2); + } + } + }); + if (returns) { + e.load_local(result); + } + e.return_value(); + e.end_method(); + } + + protected Object firstInstance(Class type) { + // make a new instance in case first object is used with a long list of targets + return ((MulticastDelegate)ReflectUtils.newInstance(type)).newInstance(); + } + + protected Object nextInstance(Object instance) { + return ((MulticastDelegate)instance).newInstance(); + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/AbstractClassFilterTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/AbstractClassFilterTransformer.java new file mode 100644 index 000000000..53742fb6f --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/AbstractClassFilterTransformer.java @@ -0,0 +1,85 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import org.objectweb.asm.*; + +abstract public class AbstractClassFilterTransformer extends AbstractClassTransformer { + private ClassTransformer pass; + private ClassVisitor target; + + public void setTarget(ClassVisitor target) { + super.setTarget(target); + pass.setTarget(target); + } + + protected AbstractClassFilterTransformer(ClassTransformer pass) { + this.pass = pass; + } + + abstract protected boolean accept(int version, int access, String name, String signature, String superName, String[] interfaces); + + public void visit(int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + target = accept(version, access, name, signature, superName, interfaces) ? pass : cv; + target.visit(version, access, name, signature, superName, interfaces); + } + + public void visitSource(String source, String debug) { + target.visitSource(source, debug); + } + + public void visitOuterClass(String owner, String name, String desc) { + target.visitOuterClass(owner, name, desc); + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return target.visitAnnotation(desc, visible); + } + + public void visitAttribute(Attribute attr) { + target.visitAttribute(attr); + } + + public void visitInnerClass(String name, String outerName, String innerName, int access) { + target.visitInnerClass(name, outerName, innerName, access); + } + + public FieldVisitor visitField(int access, + String name, + String desc, + String signature, + Object value) { + return target.visitField(access, name, desc, signature, value); + } + + public MethodVisitor visitMethod(int access, + String name, + String desc, + String signature, + String[] exceptions) { + return target.visitMethod(access, name, desc, signature, exceptions); + } + + public void visitEnd() { + target.visitEnd(); + target = null; // just to be safe + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/AbstractClassLoader.java b/blade-aop/src/main/java/net/sf/cglib/transform/AbstractClassLoader.java new file mode 100644 index 000000000..8f25e4fd6 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/AbstractClassLoader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import net.sf.cglib.core.CodeGenerationException; +import net.sf.cglib.core.ClassGenerator; +import net.sf.cglib.core.DebuggingClassWriter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Attribute; + +import java.io.IOException; + +abstract public class AbstractClassLoader extends ClassLoader { + private ClassFilter filter; + private ClassLoader classPath; + private static java.security.ProtectionDomain DOMAIN ; + + static{ + + DOMAIN = (java.security.ProtectionDomain) + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + return AbstractClassLoader.class.getProtectionDomain(); + } + }); + } + + protected AbstractClassLoader(ClassLoader parent, ClassLoader classPath, ClassFilter filter) { + super(parent); + this.filter = filter; + this.classPath = classPath; + } + + public Class loadClass(String name) throws ClassNotFoundException { + + Class loaded = findLoadedClass(name); + + if( loaded != null ){ + if( loaded.getClassLoader() == this ){ + return loaded; + }//else reload with this class loader + } + + if (!filter.accept(name)) { + return super.loadClass(name); + } + ClassReader r; + try { + + java.io.InputStream is = classPath.getResourceAsStream( + name.replace('.','/') + ".class" + ); + + if (is == null) { + + throw new ClassNotFoundException(name); + + } + try { + + r = new ClassReader(is); + + } finally { + + is.close(); + + } + } catch (IOException e) { + throw new ClassNotFoundException(name + ":" + e.getMessage()); + } + + try { + DebuggingClassWriter w = + new DebuggingClassWriter(ClassWriter.COMPUTE_FRAMES); + getGenerator(r).generateClass(w); + byte[] b = w.toByteArray(); + Class c = super.defineClass(name, b, 0, b.length, DOMAIN); + postProcess(c); + return c; + } catch (RuntimeException e) { + throw e; + } catch (Error e) { + throw e; + } catch (Exception e) { + throw new CodeGenerationException(e); + } + } + + protected ClassGenerator getGenerator(ClassReader r) { + return new ClassReaderGenerator(r, attributes(), getFlags()); + } + + protected int getFlags() { + return 0; + } + + protected Attribute[] attributes() { + return null; + } + + protected void postProcess(Class c) { + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/AbstractClassTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/AbstractClassTransformer.java new file mode 100644 index 000000000..67102054e --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/AbstractClassTransformer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; + +abstract public class AbstractClassTransformer extends ClassTransformer { + protected AbstractClassTransformer() { + super(Opcodes.ASM5); + } + + public void setTarget(ClassVisitor target) { + cv = target; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/AnnotationVisitorTee.java b/blade-aop/src/main/java/net/sf/cglib/transform/AnnotationVisitorTee.java new file mode 100644 index 000000000..5ca4a22a1 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/AnnotationVisitorTee.java @@ -0,0 +1,61 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Opcodes; + +public class AnnotationVisitorTee extends AnnotationVisitor { + private AnnotationVisitor av1, av2; + + public static AnnotationVisitor getInstance(AnnotationVisitor av1, AnnotationVisitor av2) { + if (av1 == null) + return av2; + if (av2 == null) + return av1; + return new AnnotationVisitorTee(av1, av2); + } + + public AnnotationVisitorTee(AnnotationVisitor av1, AnnotationVisitor av2) { + super(Opcodes.ASM5); + this.av1 = av1; + this.av2 = av2; + } + + public void visit(String name, Object value) { + av2.visit(name, value); + av2.visit(name, value); + } + + public void visitEnum(String name, String desc, String value) { + av1.visitEnum(name, desc, value); + av2.visitEnum(name, desc, value); + } + + public AnnotationVisitor visitAnnotation(String name, String desc) { + return getInstance(av1.visitAnnotation(name, desc), + av2.visitAnnotation(name, desc)); + } + + public AnnotationVisitor visitArray(String name) { + return getInstance(av1.visitArray(name), av2.visitArray(name)); + } + + public void visitEnd() { + av1.visitEnd(); + av2.visitEnd(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/ClassEmitterTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/ClassEmitterTransformer.java new file mode 100644 index 000000000..8d0adb599 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/ClassEmitterTransformer.java @@ -0,0 +1,21 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import net.sf.cglib.core.ClassEmitter; + +abstract public class ClassEmitterTransformer extends ClassEmitter { +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/ClassFilter.java b/blade-aop/src/main/java/net/sf/cglib/transform/ClassFilter.java new file mode 100644 index 000000000..10bc52285 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/ClassFilter.java @@ -0,0 +1,27 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.cglib.transform; + +/** + * + * @author baliuka + */ +public interface ClassFilter { + + boolean accept(String className); + +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/ClassFilterTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/ClassFilterTransformer.java new file mode 100644 index 000000000..7f9e7bef3 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/ClassFilterTransformer.java @@ -0,0 +1,31 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import org.objectweb.asm.*; + +public class ClassFilterTransformer extends AbstractClassFilterTransformer { + private ClassFilter filter; + + public ClassFilterTransformer(ClassFilter filter, ClassTransformer pass) { + super(pass); + this.filter = filter; + } + + protected boolean accept(int version, int access, String name, String signature, String superName, String[] interfaces) { + return filter.accept(name.replace('/', '.')); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/ClassReaderGenerator.java b/blade-aop/src/main/java/net/sf/cglib/transform/ClassReaderGenerator.java new file mode 100644 index 000000000..9274f843d --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/ClassReaderGenerator.java @@ -0,0 +1,41 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import net.sf.cglib.core.ClassGenerator; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; + +public class ClassReaderGenerator implements ClassGenerator { + private final ClassReader r; + private final Attribute[] attrs; + private final int flags; + + public ClassReaderGenerator(ClassReader r, int flags) { + this(r, null, flags); + } + + public ClassReaderGenerator(ClassReader r, Attribute[] attrs, int flags) { + this.r = r; + this.attrs = (attrs != null) ? attrs : new Attribute[0]; + this.flags = flags; + } + + public void generateClass(ClassVisitor v) { + r.accept(v, attrs, flags); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/ClassTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/ClassTransformer.java new file mode 100644 index 000000000..853020c04 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/ClassTransformer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; + +public abstract class ClassTransformer extends ClassVisitor { + public ClassTransformer() { + super(Opcodes.ASM5); + } + public ClassTransformer(int opcode) { + super(opcode); + } + public abstract void setTarget(ClassVisitor target); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/ClassTransformerChain.java b/blade-aop/src/main/java/net/sf/cglib/transform/ClassTransformerChain.java new file mode 100644 index 000000000..acce06ff6 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/ClassTransformerChain.java @@ -0,0 +1,56 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import org.objectweb.asm.*; + +public class ClassTransformerChain extends AbstractClassTransformer { + private ClassTransformer[] chain; + + public ClassTransformerChain(ClassTransformer[] chain) { + this.chain = (ClassTransformer[])chain.clone(); + } + + public void setTarget(ClassVisitor v) { + super.setTarget(chain[0]); + ClassVisitor next = v; + for (int i = chain.length - 1; i >= 0; i--) { + chain[i].setTarget(next); + next = chain[i]; + } + } + + public MethodVisitor visitMethod(int access, + String name, + String desc, + String signature, + String[] exceptions) { + return cv.visitMethod(access, name, desc, signature, exceptions); + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("ClassTransformerChain{"); + for (int i = 0; i < chain.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(chain[i].toString()); + } + sb.append("}"); + return sb.toString(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/ClassTransformerFactory.java b/blade-aop/src/main/java/net/sf/cglib/transform/ClassTransformerFactory.java new file mode 100644 index 000000000..cceac9261 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/ClassTransformerFactory.java @@ -0,0 +1,20 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +public interface ClassTransformerFactory { + ClassTransformer newInstance(); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/ClassTransformerTee.java b/blade-aop/src/main/java/net/sf/cglib/transform/ClassTransformerTee.java new file mode 100644 index 000000000..367c3f7dc --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/ClassTransformerTee.java @@ -0,0 +1,32 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; + +public class ClassTransformerTee extends ClassTransformer { + private ClassVisitor branch; + + public ClassTransformerTee(ClassVisitor branch) { + super(Opcodes.ASM5); + this.branch = branch; + } + + public void setTarget(ClassVisitor target) { + cv = new ClassVisitorTee(branch, target); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/ClassVisitorTee.java b/blade-aop/src/main/java/net/sf/cglib/transform/ClassVisitorTee.java new file mode 100644 index 000000000..b4cb967fb --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/ClassVisitorTee.java @@ -0,0 +1,103 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import org.objectweb.asm.*; + +public class ClassVisitorTee extends ClassVisitor { + private ClassVisitor cv1, cv2; + + public ClassVisitorTee(ClassVisitor cv1, ClassVisitor cv2) { + super(Opcodes.ASM5); + this.cv1 = cv1; + this.cv2 = cv2; + } + + public void visit(int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + cv1.visit(version, access, name, signature, superName, interfaces); + cv2.visit(version, access, name, signature, superName, interfaces); + } + + public void visitEnd() { + cv1.visitEnd(); + cv2.visitEnd(); + cv1 = cv2 = null; + } + + public void visitInnerClass(String name, String outerName, String innerName, int access) { + cv1.visitInnerClass(name, outerName, innerName, access); + cv2.visitInnerClass(name, outerName, innerName, access); + } + + public FieldVisitor visitField(int access, + String name, + String desc, + String signature, + Object value) { + FieldVisitor fv1 = cv1.visitField(access, name, desc, signature, value); + FieldVisitor fv2 = cv2.visitField(access, name, desc, signature, value); + if (fv1 == null) + return fv2; + if (fv2 == null) + return fv1; + return new FieldVisitorTee(fv1, fv2); + } + + + public MethodVisitor visitMethod(int access, + String name, + String desc, + String signature, + String[] exceptions) { + MethodVisitor mv1 = cv1.visitMethod(access, name, desc, signature, exceptions); + MethodVisitor mv2 = cv2.visitMethod(access, name, desc, signature, exceptions); + if (mv1 == null) + return mv2; + if (mv2 == null) + return mv1; + return new MethodVisitorTee(mv1, mv2); + } + + public void visitSource(String source, String debug) { + cv1.visitSource(source, debug); + cv2.visitSource(source, debug); + } + + public void visitOuterClass(String owner, String name, String desc) { + cv1.visitOuterClass(owner, name, desc); + cv2.visitOuterClass(owner, name, desc); + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(cv1.visitAnnotation(desc, visible), + cv2.visitAnnotation(desc, visible)); + } + + public void visitAttribute(Attribute attrs) { + cv1.visitAttribute(attrs); + cv2.visitAttribute(attrs); + } + + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(cv1.visitTypeAnnotation(typeRef, typePath, desc, visible), + cv2.visitTypeAnnotation(typeRef, typePath, desc, visible)); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/FieldVisitorTee.java b/blade-aop/src/main/java/net/sf/cglib/transform/FieldVisitorTee.java new file mode 100644 index 000000000..1ade1dcd3 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/FieldVisitorTee.java @@ -0,0 +1,53 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.TypePath; + +public class FieldVisitorTee extends FieldVisitor { + private FieldVisitor fv1, fv2; + + public FieldVisitorTee(FieldVisitor fv1, FieldVisitor fv2) { + super(Opcodes.ASM5); + this.fv1 = fv1; + this.fv2 = fv2; + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(fv1.visitAnnotation(desc, visible), + fv2.visitAnnotation(desc, visible)); + } + + public void visitAttribute(Attribute attr) { + fv1.visitAttribute(attr); + fv2.visitAttribute(attr); + } + + public void visitEnd() { + fv1.visitEnd(); + fv2.visitEnd(); + } + + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(fv1.visitTypeAnnotation(typeRef, typePath, desc, visible), + fv2.visitTypeAnnotation(typeRef, typePath, desc, visible)); + } +} + diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/MethodFilter.java b/blade-aop/src/main/java/net/sf/cglib/transform/MethodFilter.java new file mode 100644 index 000000000..2772f8165 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/MethodFilter.java @@ -0,0 +1,23 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import org.objectweb.asm.Attribute; + +public interface MethodFilter { + // TODO: pass class name too? + boolean accept(int access, String name, String desc, String signature, String[] exceptions); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/MethodFilterTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/MethodFilterTransformer.java new file mode 100644 index 000000000..1220cb0a8 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/MethodFilterTransformer.java @@ -0,0 +1,43 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import org.objectweb.asm.*; + +public class MethodFilterTransformer extends AbstractClassTransformer { + private MethodFilter filter; + private ClassTransformer pass; + private ClassVisitor direct; + + public MethodFilterTransformer(MethodFilter filter, ClassTransformer pass) { + this.filter = filter; + this.pass = pass; + super.setTarget(pass); + } + + public MethodVisitor visitMethod(int access, + String name, + String desc, + String signature, + String[] exceptions) { + return (filter.accept(access, name, desc, signature, exceptions) ? pass : direct).visitMethod(access, name, desc, signature, exceptions); + } + + public void setTarget(ClassVisitor target) { + pass.setTarget(target); + direct = target; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/MethodVisitorTee.java b/blade-aop/src/main/java/net/sf/cglib/transform/MethodVisitorTee.java new file mode 100644 index 000000000..17eeca062 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/MethodVisitorTee.java @@ -0,0 +1,187 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import org.objectweb.asm.*; + +public class MethodVisitorTee extends MethodVisitor { + private final MethodVisitor mv1; + private final MethodVisitor mv2; + + public MethodVisitorTee(MethodVisitor mv1, MethodVisitor mv2) { + super(Opcodes.ASM5); + this.mv1 = mv1; + this.mv2 = mv2; + } + + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + mv1.visitFrame(type, nLocal, local, nStack, stack); + mv2.visitFrame(type, nLocal, local, nStack, stack); + } + + public AnnotationVisitor visitAnnotationDefault() { + return AnnotationVisitorTee.getInstance(mv1.visitAnnotationDefault(), + mv2.visitAnnotationDefault()); + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(mv1.visitAnnotation(desc, visible), + mv2.visitAnnotation(desc, visible)); + } + + public AnnotationVisitor visitParameterAnnotation(int parameter, + String desc, + boolean visible) { + return AnnotationVisitorTee.getInstance(mv1.visitParameterAnnotation(parameter, desc, visible), + mv2.visitParameterAnnotation(parameter, desc, visible)); + } + + public void visitAttribute(Attribute attr) { + mv1.visitAttribute(attr); + mv2.visitAttribute(attr); + } + + public void visitCode() { + mv1.visitCode(); + mv2.visitCode(); + } + + public void visitInsn(int opcode) { + mv1.visitInsn(opcode); + mv2.visitInsn(opcode); + } + + public void visitIntInsn(int opcode, int operand) { + mv1.visitIntInsn(opcode, operand); + mv2.visitIntInsn(opcode, operand); + } + + public void visitVarInsn(int opcode, int var) { + mv1.visitVarInsn(opcode, var); + mv2.visitVarInsn(opcode, var); + } + + public void visitTypeInsn(int opcode, String desc) { + mv1.visitTypeInsn(opcode, desc); + mv2.visitTypeInsn(opcode, desc); + } + + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + mv1.visitFieldInsn(opcode, owner, name, desc); + mv2.visitFieldInsn(opcode, owner, name, desc); + } + + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + mv1.visitMethodInsn(opcode, owner, name, desc); + mv2.visitMethodInsn(opcode, owner, name, desc); + } + + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + mv1.visitMethodInsn(opcode, owner, name, desc, itf); + mv2.visitMethodInsn(opcode, owner, name, desc, itf); + } + + public void visitJumpInsn(int opcode, Label label) { + mv1.visitJumpInsn(opcode, label); + mv2.visitJumpInsn(opcode, label); + } + + public void visitLabel(Label label) { + mv1.visitLabel(label); + mv2.visitLabel(label); + } + + public void visitLdcInsn(Object cst) { + mv1.visitLdcInsn(cst); + mv2.visitLdcInsn(cst); + } + + public void visitIincInsn(int var, int increment) { + mv1.visitIincInsn(var, increment); + mv2.visitIincInsn(var, increment); + } + + public void visitTableSwitchInsn(int min, int max, Label dflt, Label labels[]) { + mv1.visitTableSwitchInsn(min, max, dflt, labels); + mv2.visitTableSwitchInsn(min, max, dflt, labels); + } + + public void visitLookupSwitchInsn(Label dflt, int keys[], Label labels[]) { + mv1.visitLookupSwitchInsn(dflt, keys, labels); + mv2.visitLookupSwitchInsn(dflt, keys, labels); + } + + public void visitMultiANewArrayInsn(String desc, int dims) { + mv1.visitMultiANewArrayInsn(desc, dims); + mv2.visitMultiANewArrayInsn(desc, dims); + } + + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + mv1.visitTryCatchBlock(start, end, handler, type); + mv2.visitTryCatchBlock(start, end, handler, type); + } + + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + mv1.visitLocalVariable(name, desc, signature, start, end, index); + mv2.visitLocalVariable(name, desc, signature, start, end, index); + } + + public void visitLineNumber(int line, Label start) { + mv1.visitLineNumber(line, start); + mv2.visitLineNumber(line, start); + } + + public void visitMaxs(int maxStack, int maxLocals) { + mv1.visitMaxs(maxStack, maxLocals); + mv2.visitMaxs(maxStack, maxLocals); + } + + public void visitEnd() { + mv1.visitEnd(); + mv2.visitEnd(); + } + + public void visitParameter(String name, int access) { + mv1.visitParameter(name, access); + mv2.visitParameter(name, access); + } + + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(mv1.visitTypeAnnotation(typeRef, typePath, desc, visible), + mv2.visitTypeAnnotation(typeRef, typePath, desc, visible)); + } + + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + mv1.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + mv2.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + + public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(mv1.visitInsnAnnotation(typeRef, typePath, desc, visible), + mv2.visitInsnAnnotation(typeRef, typePath, desc, visible)); + } + + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(mv1.visitTryCatchAnnotation(typeRef, typePath, desc, visible), + mv2.visitTryCatchAnnotation(typeRef, typePath, desc, visible)); + } + + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(mv1.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, desc, visible), + mv2.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, desc, visible)); + } +} + diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/TransformingClassGenerator.java b/blade-aop/src/main/java/net/sf/cglib/transform/TransformingClassGenerator.java new file mode 100644 index 000000000..386155fee --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/TransformingClassGenerator.java @@ -0,0 +1,35 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import net.sf.cglib.core.ClassGenerator; +import net.sf.cglib.core.Transformer; +import org.objectweb.asm.ClassVisitor; + +public class TransformingClassGenerator implements ClassGenerator { + private ClassGenerator gen; + private ClassTransformer t; + + public TransformingClassGenerator(ClassGenerator gen, ClassTransformer t) { + this.gen = gen; + this.t = t; + } + + public void generateClass(ClassVisitor v) throws Exception { + t.setTarget(v); + gen.generateClass(t); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/TransformingClassLoader.java b/blade-aop/src/main/java/net/sf/cglib/transform/TransformingClassLoader.java new file mode 100644 index 000000000..505f621e2 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/TransformingClassLoader.java @@ -0,0 +1,34 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform; + +import java.util.*; +import net.sf.cglib.core.ClassGenerator; +import org.objectweb.asm.*; + +public class TransformingClassLoader extends AbstractClassLoader { + private ClassTransformerFactory t; + + public TransformingClassLoader(ClassLoader parent, ClassFilter filter, ClassTransformerFactory t) { + super(parent, parent, filter); + this.t = t; + } + + protected ClassGenerator getGenerator(ClassReader r) { + ClassTransformer t2 = (ClassTransformer)t.newInstance(); + return new TransformingClassGenerator(super.getGenerator(r), t2); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/AbstractInterceptFieldCallback.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/AbstractInterceptFieldCallback.java new file mode 100644 index 000000000..302bd3567 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/AbstractInterceptFieldCallback.java @@ -0,0 +1,42 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +/** + * @author Chris Nokleberg + */ +public class AbstractInterceptFieldCallback implements InterceptFieldCallback { + + public int writeInt(Object obj, String name, int oldValue, int newValue) { return newValue; } + public char writeChar(Object obj, String name, char oldValue, char newValue) { return newValue; } + public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { return newValue; } + public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { return newValue; } + public short writeShort(Object obj, String name, short oldValue, short newValue) { return newValue; } + public float writeFloat(Object obj, String name, float oldValue, float newValue) { return newValue; } + public double writeDouble(Object obj, String name, double oldValue, double newValue) { return newValue; } + public long writeLong(Object obj, String name, long oldValue, long newValue) { return newValue; } + public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { return newValue; } + + public int readInt(Object obj, String name, int oldValue) { return oldValue; } + public char readChar(Object obj, String name, char oldValue) { return oldValue; } + public byte readByte(Object obj, String name, byte oldValue) { return oldValue; } + public boolean readBoolean(Object obj, String name, boolean oldValue) { return oldValue; } + public short readShort(Object obj, String name, short oldValue) { return oldValue; } + public float readFloat(Object obj, String name, float oldValue) { return oldValue; } + public double readDouble(Object obj, String name, double oldValue) { return oldValue; } + public long readLong(Object obj, String name, long oldValue) { return oldValue; } + public Object readObject(Object obj, String name, Object oldValue) { return oldValue; } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/AccessFieldTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/AccessFieldTransformer.java new file mode 100644 index 000000000..9de468eb4 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/AccessFieldTransformer.java @@ -0,0 +1,64 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +import net.sf.cglib.transform.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Type; + +public class AccessFieldTransformer extends ClassEmitterTransformer { + private Callback callback; + + public AccessFieldTransformer(Callback callback) { + this.callback = callback; + } + + public interface Callback { + String getPropertyName(Type owner, String fieldName); + } + + public void declare_field(int access, final String name, Type type, Object value) { + super.declare_field(access, name, type, value); + + String property = TypeUtils.upperFirst(callback.getPropertyName(getClassType(), name)); + if (property != null) { + CodeEmitter e; + e = begin_method(Constants.ACC_PUBLIC, + new Signature("get" + property, + type, + Constants.TYPES_EMPTY), + null); + e.load_this(); + e.getfield(name); + e.return_value(); + e.end_method(); + + e = begin_method(Constants.ACC_PUBLIC, + new Signature("set" + property, + Type.VOID_TYPE, + new Type[]{ type }), + null); + e.load_this(); + e.load_arg(0); + e.putfield(name); + e.return_value(); + e.end_method(); + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/AddDelegateTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/AddDelegateTransformer.java new file mode 100644 index 000000000..a5423a7c4 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/AddDelegateTransformer.java @@ -0,0 +1,119 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +import net.sf.cglib.transform.*; +import java.lang.reflect.*; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Type; + +/** + * @author Juozas Baliuka + */ +public class AddDelegateTransformer extends ClassEmitterTransformer { + private static final String DELEGATE = "$CGLIB_DELEGATE"; + private static final Signature CSTRUCT_OBJECT = + TypeUtils.parseSignature("void (Object)"); + + private Class[] delegateIf; + private Class delegateImpl; + private Type delegateType; + + /** Creates a new instance of AddDelegateTransformer */ + public AddDelegateTransformer(Class delegateIf[], Class delegateImpl) { + try { + delegateImpl.getConstructor(new Class[]{ Object.class }); + this.delegateIf = delegateIf; + this.delegateImpl = delegateImpl; + delegateType = Type.getType(delegateImpl); + } catch (NoSuchMethodException e) { + throw new CodeGenerationException(e); + } + } + + public void begin_class(int version, int access, String className, Type superType, Type[] interfaces, String sourceFile) { + + if(!TypeUtils.isInterface(access)){ + + Type[] all = TypeUtils.add(interfaces, TypeUtils.getTypes(delegateIf)); + super.begin_class(version, access, className, superType, all, sourceFile); + + declare_field(Constants.ACC_PRIVATE | Constants.ACC_TRANSIENT, + DELEGATE, + delegateType, + null); + for (int i = 0; i < delegateIf.length; i++) { + Method[] methods = delegateIf[i].getMethods(); + for (int j = 0; j < methods.length; j++) { + if (Modifier.isAbstract(methods[j].getModifiers())) { + addDelegate(methods[j]); + } + } + } + }else{ + super.begin_class(version, access, className, superType, interfaces, sourceFile); + } + } + + public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) { + final CodeEmitter e = super.begin_method(access, sig, exceptions); + if (sig.getName().equals(Constants.CONSTRUCTOR_NAME)) { + return new CodeEmitter(e) { + private boolean transformInit = true; + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + if (transformInit && opcode == Constants.INVOKESPECIAL) { + load_this(); + new_instance(delegateType); + dup(); + load_this(); + invoke_constructor(delegateType, CSTRUCT_OBJECT); + putfield(DELEGATE); + transformInit = false; + } + } + }; + } + return e; + } + + private void addDelegate(Method m) { + Method delegate; + try { + delegate = delegateImpl.getMethod(m.getName(), m.getParameterTypes()); + if (!delegate.getReturnType().getName().equals(m.getReturnType().getName())){ + throw new IllegalArgumentException("Invalid delegate signature " + delegate); + } + } catch (NoSuchMethodException e) { + throw new CodeGenerationException(e); + } + + final Signature sig = ReflectUtils.getSignature(m); + Type[] exceptions = TypeUtils.getTypes(m.getExceptionTypes()); + CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, sig, exceptions); + e.load_this(); + e.getfield(DELEGATE); + e.load_args(); + e.invoke_virtual(delegateType, sig); + e.return_value(); + e.end_method(); + } +} + + + diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/AddInitTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/AddInitTransformer.java new file mode 100644 index 000000000..a17a9f79f --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/AddInitTransformer.java @@ -0,0 +1,63 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +import java.lang.reflect.Method; + +import net.sf.cglib.core.CodeEmitter; +import net.sf.cglib.core.Constants; +import net.sf.cglib.core.MethodInfo; +import net.sf.cglib.core.ReflectUtils; +import net.sf.cglib.core.Signature; +import net.sf.cglib.transform.ClassEmitterTransformer; + +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Type; + +/** + * @author Mark Hobson + */ +public class AddInitTransformer extends ClassEmitterTransformer { + private MethodInfo info; + + public AddInitTransformer(Method method) { + info = ReflectUtils.getMethodInfo(method); + + Type[] types = info.getSignature().getArgumentTypes(); + if (types.length != 1 || + !types[0].equals(Constants.TYPE_OBJECT) || + !info.getSignature().getReturnType().equals(Type.VOID_TYPE)) { + throw new IllegalArgumentException(method + " illegal signature"); + } + } + + public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) { + final CodeEmitter emitter = super.begin_method(access, sig, exceptions); + if (sig.getName().equals(Constants.CONSTRUCTOR_NAME)) { + return new CodeEmitter(emitter) { + public void visitInsn(int opcode) { + if (opcode == Constants.RETURN) { + load_this(); + invoke(info); + } + super.visitInsn(opcode); + } + }; + } + return emitter; + } +} + diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/AddPropertyTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/AddPropertyTransformer.java new file mode 100644 index 000000000..e13071acb --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/AddPropertyTransformer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +import net.sf.cglib.transform.*; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.Type; + +public class AddPropertyTransformer extends ClassEmitterTransformer { + private final String[] names; + private final Type[] types; + + public AddPropertyTransformer(Map props) { + int size = props.size(); + names = (String[])props.keySet().toArray(new String[size]); + types = new Type[size]; + for (int i = 0; i < size; i++) { + types[i] = (Type)props.get(names[i]); + } + } + + public AddPropertyTransformer(String[] names, Type[] types) { + this.names = names; + this.types = types; + } + + public void end_class() { + if (!TypeUtils.isAbstract(getAccess())) { + EmitUtils.add_properties(this, names, types); + } + super.end_class(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/AddStaticInitTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/AddStaticInitTransformer.java new file mode 100644 index 000000000..94a2eaebf --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/AddStaticInitTransformer.java @@ -0,0 +1,49 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +import java.lang.reflect.Method; +import net.sf.cglib.core.*; +import net.sf.cglib.transform.*; +import org.objectweb.asm.Type; + +/** + * @author Juozas Baliuka, Chris Nokleberg + */ +public class AddStaticInitTransformer extends ClassEmitterTransformer { + private MethodInfo info; + + public AddStaticInitTransformer(Method classInit) { + info = ReflectUtils.getMethodInfo(classInit); + if (!TypeUtils.isStatic(info.getModifiers())) { + throw new IllegalArgumentException(classInit + " is not static"); + } + Type[] types = info.getSignature().getArgumentTypes(); + if (types.length != 1 || + !types[0].equals(Constants.TYPE_CLASS) || + !info.getSignature().getReturnType().equals(Type.VOID_TYPE)) { + throw new IllegalArgumentException(classInit + " illegal signature"); + } + } + + protected void init() { + if (!TypeUtils.isInterface(getAccess())) { + CodeEmitter e = getStaticHook(); + EmitUtils.load_class_this(e); + e.invoke(info); + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/FieldProvider.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/FieldProvider.java new file mode 100644 index 000000000..d1eaace29 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/FieldProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +public interface FieldProvider { + + String[] getFieldNames(); + + Class[] getFieldTypes(); + + void setField(int index, Object value); + + Object getField(int index); + + + void setField(String name, Object value); + + Object getField(String name); + + +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/FieldProviderTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/FieldProviderTransformer.java new file mode 100644 index 000000000..90c9d1bd8 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/FieldProviderTransformer.java @@ -0,0 +1,208 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +import net.sf.cglib.transform.*; +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; + +public class FieldProviderTransformer extends ClassEmitterTransformer { + + private static final String FIELD_NAMES = "CGLIB$FIELD_NAMES"; + private static final String FIELD_TYPES = "CGLIB$FIELD_TYPES"; + + private static final Type FIELD_PROVIDER = + TypeUtils.parseType("net.sf.cglib.transform.impl.FieldProvider"); + private static final Type ILLEGAL_ARGUMENT_EXCEPTION = + TypeUtils.parseType("IllegalArgumentException"); + private static final Signature PROVIDER_GET = + TypeUtils.parseSignature("Object getField(String)"); + private static final Signature PROVIDER_SET = + TypeUtils.parseSignature("void setField(String, Object)"); + private static final Signature PROVIDER_SET_BY_INDEX = + TypeUtils.parseSignature("void setField(int, Object)"); + private static final Signature PROVIDER_GET_BY_INDEX = + TypeUtils.parseSignature("Object getField(int)"); + private static final Signature PROVIDER_GET_TYPES = + TypeUtils.parseSignature("Class[] getFieldTypes()"); + private static final Signature PROVIDER_GET_NAMES = + TypeUtils.parseSignature("String[] getFieldNames()"); + + private int access; + private Map fields; + + public void begin_class(int version, int access, String className, Type superType, Type[] interfaces, String sourceFile) { + if (!TypeUtils.isAbstract(access)) { + interfaces = TypeUtils.add(interfaces, FIELD_PROVIDER); + } + this.access = access; + fields = new HashMap(); + super.begin_class(version, access, className, superType, interfaces, sourceFile); + } + + public void declare_field(int access, String name, Type type, Object value) { + super.declare_field(access, name, type, value); + + if (!TypeUtils.isStatic(access)) { + fields.put(name, type); + } + } + + public void end_class() { + if (!TypeUtils.isInterface(access)) { + try { + generate(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new CodeGenerationException(e); + } + } + super.end_class(); + } + + private void generate() throws Exception { + final String[] names = (String[])fields.keySet().toArray(new String[fields.size()]); + + int indexes[] = new int[names.length]; + for (int i = 0; i < indexes.length; i++) { + indexes[i] = i; + } + + super.declare_field(Constants.PRIVATE_FINAL_STATIC, FIELD_NAMES, Constants.TYPE_STRING_ARRAY, null); + super.declare_field(Constants.PRIVATE_FINAL_STATIC, FIELD_TYPES, Constants.TYPE_CLASS_ARRAY, null); + + // use separate methods here because each process switch inner class needs a final CodeEmitter + initFieldProvider(names); + getNames(); + getTypes(); + getField(names); + setField(names); + setByIndex(names, indexes); + getByIndex(names, indexes); + } + + private void initFieldProvider(String[] names) { + CodeEmitter e = getStaticHook(); + EmitUtils.push_object(e, names); + e.putstatic(getClassType(), FIELD_NAMES, Constants.TYPE_STRING_ARRAY); + + e.push(names.length); + e.newarray(Constants.TYPE_CLASS); + e.dup(); + for(int i = 0; i < names.length; i++ ){ + e.dup(); + e.push(i); + Type type = (Type)fields.get(names[i]); + EmitUtils.load_class(e, type); + e.aastore(); + } + e.putstatic(getClassType(), FIELD_TYPES, Constants.TYPE_CLASS_ARRAY); + } + + private void getNames() { + CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_GET_NAMES, null); + e.getstatic(getClassType(), FIELD_NAMES, Constants.TYPE_STRING_ARRAY); + e.return_value(); + e.end_method(); + } + + private void getTypes() { + CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_GET_TYPES, null); + e.getstatic(getClassType(), FIELD_TYPES, Constants.TYPE_CLASS_ARRAY); + e.return_value(); + e.end_method(); + } + + private void setByIndex(final String[] names, final int[] indexes) throws Exception { + final CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_SET_BY_INDEX, null); + e.load_this(); + e.load_arg(1); + e.load_arg(0); + e.process_switch(indexes, new ProcessSwitchCallback() { + public void processCase(int key, Label end) throws Exception { + Type type = (Type)fields.get(names[key]); + e.unbox(type); + e.putfield(names[key]); + e.return_value(); + } + public void processDefault() throws Exception { + e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field index"); + } + }); + e.end_method(); + } + + private void getByIndex(final String[] names, final int[] indexes) throws Exception { + final CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_GET_BY_INDEX, null); + e.load_this(); + e.load_arg(0); + e.process_switch(indexes, new ProcessSwitchCallback() { + public void processCase(int key, Label end) throws Exception { + Type type = (Type)fields.get(names[key]); + e.getfield(names[key]); + e.box(type); + e.return_value(); + } + public void processDefault() throws Exception { + e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field index"); + } + }); + e.end_method(); + } + + // TODO: if this is used to enhance class files SWITCH_STYLE_TRIE should be used + // to avoid JVM hashcode implementation incompatibilities + private void getField(String[] names) throws Exception { + final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, PROVIDER_GET, null); + e.load_this(); + e.load_arg(0); + EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + Type type = (Type)fields.get(key); + e.getfield((String)key); + e.box(type); + e.return_value(); + } + public void processDefault() { + e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field name"); + } + }); + e.end_method(); + } + + private void setField(String[] names) throws Exception { + final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, PROVIDER_SET, null); + e.load_this(); + e.load_arg(1); + e.load_arg(0); + EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + Type type = (Type)fields.get(key); + e.unbox(type); + e.putfield((String)key); + e.return_value(); + } + public void processDefault() { + e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field name"); + } + }); + e.end_method(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/InterceptFieldCallback.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/InterceptFieldCallback.java new file mode 100644 index 000000000..b53461b54 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/InterceptFieldCallback.java @@ -0,0 +1,42 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +/** + * @author Juozas Baliuka + */ +public interface InterceptFieldCallback { + + int writeInt(Object obj, String name, int oldValue, int newValue); + char writeChar(Object obj, String name, char oldValue, char newValue); + byte writeByte(Object obj, String name, byte oldValue, byte newValue); + boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue); + short writeShort(Object obj, String name, short oldValue, short newValue); + float writeFloat(Object obj, String name, float oldValue, float newValue); + double writeDouble(Object obj, String name, double oldValue, double newValue); + long writeLong(Object obj, String name, long oldValue, long newValue); + Object writeObject(Object obj, String name, Object oldValue, Object newValue); + + int readInt(Object obj, String name, int oldValue); + char readChar(Object obj, String name, char oldValue); + byte readByte(Object obj, String name, byte oldValue); + boolean readBoolean(Object obj, String name, boolean oldValue); + short readShort(Object obj, String name, short oldValue); + float readFloat(Object obj, String name, float oldValue); + double readDouble(Object obj, String name, double oldValue); + long readLong(Object obj, String name, long oldValue); + Object readObject(Object obj, String name, Object oldValue); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/InterceptFieldEnabled.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/InterceptFieldEnabled.java new file mode 100644 index 000000000..a4119e278 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/InterceptFieldEnabled.java @@ -0,0 +1,21 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +public interface InterceptFieldEnabled { + void setInterceptFieldCallback(InterceptFieldCallback callback); + InterceptFieldCallback getInterceptFieldCallback(); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/InterceptFieldFilter.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/InterceptFieldFilter.java new file mode 100644 index 000000000..0259dba39 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/InterceptFieldFilter.java @@ -0,0 +1,23 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +import org.objectweb.asm.Type; + +public interface InterceptFieldFilter { + boolean acceptRead(Type owner, String name); + boolean acceptWrite(Type owner, String name); +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/InterceptFieldTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/InterceptFieldTransformer.java new file mode 100644 index 000000000..c61521010 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/InterceptFieldTransformer.java @@ -0,0 +1,210 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +import net.sf.cglib.transform.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; + +/** + * @author Juozas Baliuka, Chris Nokleberg + */ +public class InterceptFieldTransformer extends ClassEmitterTransformer { + private static final String CALLBACK_FIELD = "$CGLIB_READ_WRITE_CALLBACK"; + private static final Type CALLBACK = + TypeUtils.parseType("net.sf.cglib.transform.impl.InterceptFieldCallback"); + private static final Type ENABLED = + TypeUtils.parseType("net.sf.cglib.transform.impl.InterceptFieldEnabled"); + private static final Signature ENABLED_SET = + new Signature("setInterceptFieldCallback", Type.VOID_TYPE, new Type[]{ CALLBACK }); + private static final Signature ENABLED_GET = + new Signature("getInterceptFieldCallback", CALLBACK, new Type[0]); + + private InterceptFieldFilter filter; + + public InterceptFieldTransformer(InterceptFieldFilter filter) { + this.filter = filter; + } + + public void begin_class(int version, int access, String className, Type superType, Type[] interfaces, String sourceFile) { + if (!TypeUtils.isInterface(access)) { + super.begin_class(version, access, className, superType, TypeUtils.add(interfaces, ENABLED), sourceFile); + + super.declare_field(Constants.ACC_PRIVATE | Constants.ACC_TRANSIENT, + CALLBACK_FIELD, + CALLBACK, + null); + + CodeEmitter e; + e = super.begin_method(Constants.ACC_PUBLIC, ENABLED_GET, null); + e.load_this(); + e.getfield(CALLBACK_FIELD); + e.return_value(); + e.end_method(); + + e = super.begin_method(Constants.ACC_PUBLIC, ENABLED_SET, null); + e.load_this(); + e.load_arg(0); + e.putfield(CALLBACK_FIELD); + e.return_value(); + e.end_method(); + } else { + super.begin_class(version, access, className, superType, interfaces, sourceFile); + } + } + + public void declare_field(int access, String name, Type type, Object value) { + super.declare_field(access, name, type, value); + if (!TypeUtils.isStatic(access)) { + if (filter.acceptRead(getClassType(), name)) { + addReadMethod(name, type); + } + if (filter.acceptWrite(getClassType(), name)) { + addWriteMethod(name, type); + } + } + } + + private void addReadMethod(String name, Type type) { + CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, + readMethodSig(name, type.getDescriptor()), + null); + e.load_this(); + e.getfield(name); + e.load_this(); + e.invoke_interface(ENABLED,ENABLED_GET); + Label intercept = e.make_label(); + e.ifnonnull(intercept); + e.return_value(); + + e.mark(intercept); + Local result = e.make_local(type); + e.store_local(result); + e.load_this(); + e.invoke_interface(ENABLED,ENABLED_GET); + e.load_this(); + e.push(name); + e.load_local(result); + e.invoke_interface(CALLBACK, readCallbackSig(type)); + if (!TypeUtils.isPrimitive(type)) { + e.checkcast(type); + } + e.return_value(); + e.end_method(); + } + + private void addWriteMethod(String name, Type type) { + CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, + writeMethodSig(name, type.getDescriptor()), + null); + e.load_this(); + e.dup(); + e.invoke_interface(ENABLED,ENABLED_GET); + Label skip = e.make_label(); + e.ifnull(skip); + + e.load_this(); + e.invoke_interface(ENABLED,ENABLED_GET); + e.load_this(); + e.push(name); + e.load_this(); + e.getfield(name); + e.load_arg(0); + e.invoke_interface(CALLBACK, writeCallbackSig(type)); + if (!TypeUtils.isPrimitive(type)) { + e.checkcast(type); + } + Label go = e.make_label(); + e.goTo(go); + e.mark(skip); + e.load_arg(0); + e.mark(go); + e.putfield(name); + e.return_value(); + e.end_method(); + } + + public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) { + return new CodeEmitter(super.begin_method(access, sig, exceptions)) { + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + Type towner = TypeUtils.fromInternalName(owner); + switch (opcode) { + case Constants.GETFIELD: + if (filter.acceptRead(towner, name)) { + helper(towner, readMethodSig(name, desc)); + return; + } + break; + case Constants.PUTFIELD: + if (filter.acceptWrite(towner, name)) { + helper(towner, writeMethodSig(name, desc)); + return; + } + break; + } + super.visitFieldInsn(opcode, owner, name, desc); + } + + private void helper(Type owner, Signature sig) { + invoke_virtual(owner, sig); + } + }; + } + + private static Signature readMethodSig(String name, String desc) { + return new Signature("$cglib_read_" + name, "()" + desc); + } + + private static Signature writeMethodSig(String name, String desc) { + return new Signature("$cglib_write_" + name, "(" + desc + ")V"); + } + + private static Signature readCallbackSig(Type type) { + Type remap = remap(type); + return new Signature("read" + callbackName(remap), + remap, + new Type[]{ Constants.TYPE_OBJECT, + Constants.TYPE_STRING, + remap }); + } + + private static Signature writeCallbackSig(Type type) { + Type remap = remap(type); + return new Signature("write" + callbackName(remap), + remap, + new Type[]{ Constants.TYPE_OBJECT, + Constants.TYPE_STRING, + remap, + remap }); + } + + private static Type remap(Type type) { + switch (type.getSort()) { + case Type.OBJECT: + case Type.ARRAY: + return Constants.TYPE_OBJECT; + default: + return type; + } + } + + private static String callbackName(Type type) { + return (type == Constants.TYPE_OBJECT) ? + "Object" : + TypeUtils.upperFirst(TypeUtils.getClassName(type)); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/UndeclaredThrowableStrategy.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/UndeclaredThrowableStrategy.java new file mode 100644 index 000000000..aaf3789dd --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/UndeclaredThrowableStrategy.java @@ -0,0 +1,61 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +import net.sf.cglib.core.ClassGenerator; +import net.sf.cglib.core.DefaultGeneratorStrategy; +import net.sf.cglib.core.GeneratorStrategy; +import net.sf.cglib.core.TypeUtils; +import net.sf.cglib.transform.ClassTransformer; +import net.sf.cglib.transform.MethodFilter; +import net.sf.cglib.transform.MethodFilterTransformer; +import net.sf.cglib.transform.TransformingClassGenerator; + +/** + * A {@link GeneratorStrategy} suitable for use with {@link net.sf.cglib.Enhancer} which + * causes all undeclared exceptions thrown from within a proxied method to be wrapped + * in an alternative exception of your choice. + */ +public class UndeclaredThrowableStrategy extends DefaultGeneratorStrategy { + + + private Class wrapper; + + /** + * Create a new instance of this strategy. + * @param wrapper a class which extends either directly or + * indirectly from Throwable and which has at least one + * constructor that takes a single argument of type + * Throwable, for example + * java.lang.reflect.UndeclaredThrowableException.class + */ + public UndeclaredThrowableStrategy(Class wrapper) { + this.wrapper = wrapper; + } + + private static final MethodFilter TRANSFORM_FILTER = new MethodFilter() { + public boolean accept(int access, String name, String desc, String signature, String[] exceptions) { + return !TypeUtils.isPrivate(access) && name.indexOf('$') < 0; + } + }; + + protected ClassGenerator transform(ClassGenerator cg) throws Exception { + ClassTransformer tr = new UndeclaredThrowableTransformer(wrapper); + tr = new MethodFilterTransformer(TRANSFORM_FILTER, tr); + return new TransformingClassGenerator(cg, tr); + } +} + diff --git a/blade-aop/src/main/java/net/sf/cglib/transform/impl/UndeclaredThrowableTransformer.java b/blade-aop/src/main/java/net/sf/cglib/transform/impl/UndeclaredThrowableTransformer.java new file mode 100644 index 000000000..6369abae4 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/transform/impl/UndeclaredThrowableTransformer.java @@ -0,0 +1,60 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.transform.impl; + +import java.lang.reflect.Constructor; +import net.sf.cglib.core.*; +import net.sf.cglib.transform.*; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Type; +import org.objectweb.asm.ClassVisitor; + +public class UndeclaredThrowableTransformer extends ClassEmitterTransformer { + private Type wrapper; + + public UndeclaredThrowableTransformer(Class wrapper) { + this.wrapper = Type.getType(wrapper); + boolean found = false; + Constructor[] cstructs = wrapper.getConstructors(); + for (int i = 0; i < cstructs.length; i++) { + Class[] types = cstructs[i].getParameterTypes(); + if (types.length == 1 && types[0].equals(Throwable.class)) { + found = true; + break; + } + } + if (!found) + throw new IllegalArgumentException(wrapper + " does not have a single-arg constructor that takes a Throwable"); + } + + public CodeEmitter begin_method(int access, final Signature sig, final Type[] exceptions) { + CodeEmitter e = super.begin_method(access, sig, exceptions); + if (TypeUtils.isAbstract(access) || sig.equals(Constants.SIG_STATIC)) { + return e; + } + return new CodeEmitter(e) { + private Block handler; + /* init */ { + handler = begin_block(); + } + public void visitMaxs(int maxStack, int maxLocals) { + handler.end(); + EmitUtils.wrap_undeclared_throwable(this, handler, exceptions, wrapper); + super.visitMaxs(maxStack, maxLocals); + } + }; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/util/ParallelSorter.java b/blade-aop/src/main/java/net/sf/cglib/util/ParallelSorter.java new file mode 100644 index 000000000..04574a315 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/util/ParallelSorter.java @@ -0,0 +1,294 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.util; + +import java.lang.reflect.*; +import java.util.Comparator; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; + +/** + * For the efficient sorting of multiple arrays in parallel. + *

+ * Given two arrays of equal length and varying types, the standard + * technique for sorting them in parallel is to create a new temporary + * object for each row, store the objects in a temporary array, sort the + * array using a custom comparator, and the extract the original values + * back into their respective arrays. This is wasteful in both time and + * memory. + *

+ * This class generates bytecode customized to the particular set of + * arrays you need to sort, in such a way that both arrays are sorted + * in-place, simultaneously. + *

+ * Two sorting algorithms are provided. + * Quicksort is best when you only need to sort by a single column, as + * it requires very few comparisons and swaps. Mergesort is best used + * when sorting multiple columns, as it is a "stable" sort--that is, it + * does not affect the relative order of equal objects from previous sorts. + *

+ * The mergesort algorithm here is an "in-place" variant, which while + * slower, does not require a temporary array. + * + * @author Chris Nokleberg + */ +abstract public class ParallelSorter extends SorterTemplate { + protected Object[] a; + private Comparer comparer; + + protected ParallelSorter() { + } + + abstract public ParallelSorter newInstance(Object[] arrays); + + /** + * Create a new ParallelSorter object for a set of arrays. You may + * sort the arrays multiple times via the same ParallelSorter object. + * @param arrays An array of arrays to sort. The arrays may be a mix + * of primitive and non-primitive types, but should all be the same + * length. + * @param loader ClassLoader for generated class, uses "current" if null + */ + public static ParallelSorter create(Object[] arrays) { + Generator gen = new Generator(); + gen.setArrays(arrays); + return gen.create(); + } + + private int len() { + return ((Object[])a[0]).length; + } + + /** + * Sort the arrays using the quicksort algorithm. + * @param index array (column) to sort by + */ + public void quickSort(int index) { + quickSort(index, 0, len(), null); + } + + /** + * Sort the arrays using the quicksort algorithm. + * @param index array (column) to sort by + * @param lo starting array index (row), inclusive + * @param hi ending array index (row), exclusive + */ + public void quickSort(int index, int lo, int hi) { + quickSort(index, lo, hi, null); + } + + /** + * Sort the arrays using the quicksort algorithm. + * @param index array (column) to sort by + * @param cmp Comparator to use if the specified column is non-primitive + */ + public void quickSort(int index, Comparator cmp) { + quickSort(index, 0, len(), cmp); + } + + /** + * Sort the arrays using the quicksort algorithm. + * @param index array (column) to sort by + * @param lo starting array index (row), inclusive + * @param hi ending array index (row), exclusive + * @param cmp Comparator to use if the specified column is non-primitive + */ + public void quickSort(int index, int lo, int hi, Comparator cmp) { + chooseComparer(index, cmp); + super.quickSort(lo, hi - 1); + } + + /** + * @param index array (column) to sort by + */ + public void mergeSort(int index) { + mergeSort(index, 0, len(), null); + } + + /** + * Sort the arrays using an in-place merge sort. + * @param index array (column) to sort by + * @param lo starting array index (row), inclusive + * @param hi ending array index (row), exclusive + */ + public void mergeSort(int index, int lo, int hi) { + mergeSort(index, lo, hi, null); + } + + /** + * Sort the arrays using an in-place merge sort. + * @param index array (column) to sort by + * @param lo starting array index (row), inclusive + * @param hi ending array index (row), exclusive + */ + public void mergeSort(int index, Comparator cmp) { + mergeSort(index, 0, len(), cmp); + } + + /** + * Sort the arrays using an in-place merge sort. + * @param index array (column) to sort by + * @param lo starting array index (row), inclusive + * @param hi ending array index (row), exclusive + * @param cmp Comparator to use if the specified column is non-primitive + */ + public void mergeSort(int index, int lo, int hi, Comparator cmp) { + chooseComparer(index, cmp); + super.mergeSort(lo, hi - 1); + } + + private void chooseComparer(int index, Comparator cmp) { + Object array = a[index]; + Class type = array.getClass().getComponentType(); + if (type.equals(Integer.TYPE)) { + comparer = new IntComparer((int[])array); + } else if (type.equals(Long.TYPE)) { + comparer = new LongComparer((long[])array); + } else if (type.equals(Double.TYPE)) { + comparer = new DoubleComparer((double[])array); + } else if (type.equals(Float.TYPE)) { + comparer = new FloatComparer((float[])array); + } else if (type.equals(Short.TYPE)) { + comparer = new ShortComparer((short[])array); + } else if (type.equals(Byte.TYPE)) { + comparer = new ByteComparer((byte[])array); + } else if (cmp != null) { + comparer = new ComparatorComparer((Object[])array, cmp); + } else { + comparer = new ObjectComparer((Object[])array); + } + } + + protected int compare(int i, int j) { + return comparer.compare(i, j); + } + + interface Comparer { + int compare(int i, int j); + } + + static class ComparatorComparer implements Comparer { + private Object[] a; + private Comparator cmp; + + public ComparatorComparer(Object[] a, Comparator cmp) { + this.a = a; + this.cmp = cmp; + } + + public int compare(int i, int j) { + return cmp.compare(a[i], a[j]); + } + } + + static class ObjectComparer implements Comparer { + private Object[] a; + public ObjectComparer(Object[] a) { this.a = a; } + public int compare(int i, int j) { + return ((Comparable)a[i]).compareTo(a[j]); + } + } + + static class IntComparer implements Comparer { + private int[] a; + public IntComparer(int[] a) { this.a = a; } + public int compare(int i, int j) { return a[i] - a[j]; } + } + + static class LongComparer implements Comparer { + private long[] a; + public LongComparer(long[] a) { this.a = a; } + public int compare(int i, int j) { + long vi = a[i]; + long vj = a[j]; + return (vi == vj) ? 0 : (vi > vj) ? 1 : -1; + } + } + + static class FloatComparer implements Comparer { + private float[] a; + public FloatComparer(float[] a) { this.a = a; } + public int compare(int i, int j) { + float vi = a[i]; + float vj = a[j]; + return (vi == vj) ? 0 : (vi > vj) ? 1 : -1; + } + } + + static class DoubleComparer implements Comparer { + private double[] a; + public DoubleComparer(double[] a) { this.a = a; } + public int compare(int i, int j) { + double vi = a[i]; + double vj = a[j]; + return (vi == vj) ? 0 : (vi > vj) ? 1 : -1; + } + } + + static class ShortComparer implements Comparer { + private short[] a; + public ShortComparer(short[] a) { this.a = a; } + public int compare(int i, int j) { return a[i] - a[j]; } + } + + static class ByteComparer implements Comparer { + private byte[] a; + public ByteComparer(byte[] a) { this.a = a; } + public int compare(int i, int j) { return a[i] - a[j]; } + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(ParallelSorter.class.getName()); + + private Object[] arrays; + + public Generator() { + super(SOURCE); + } + + protected ClassLoader getDefaultClassLoader() { + return null; // TODO + } + + public void setArrays(Object[] arrays) { + this.arrays = arrays; + } + + public ParallelSorter create() { + return (ParallelSorter)super.create(ClassesKey.create(arrays)); + } + + public void generateClass(ClassVisitor v) throws Exception { + if (arrays.length == 0) { + throw new IllegalArgumentException("No arrays specified to sort"); + } + for (int i = 0; i < arrays.length; i++) { + if (!arrays[i].getClass().isArray()) { + throw new IllegalArgumentException(arrays[i].getClass() + " is not an array"); + } + } + new ParallelSorterEmitter(v, getClassName(), arrays); + } + + protected Object firstInstance(Class type) { + return ((ParallelSorter)ReflectUtils.newInstance(type)).newInstance(arrays); + } + + protected Object nextInstance(Object instance) { + return ((ParallelSorter)instance).newInstance(arrays); + } + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/util/ParallelSorterEmitter.java b/blade-aop/src/main/java/net/sf/cglib/util/ParallelSorterEmitter.java new file mode 100644 index 000000000..06351a9cf --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/util/ParallelSorterEmitter.java @@ -0,0 +1,100 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.util; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Type; + +class ParallelSorterEmitter extends ClassEmitter { + private static final Type PARALLEL_SORTER = + TypeUtils.parseType("net.sf.cglib.util.ParallelSorter"); + private static final Signature CSTRUCT_OBJECT_ARRAY = + TypeUtils.parseConstructor("Object[]"); + private static final Signature NEW_INSTANCE = + new Signature("newInstance", PARALLEL_SORTER, new Type[]{ Constants.TYPE_OBJECT_ARRAY }); + private static final Signature SWAP = + TypeUtils.parseSignature("void swap(int, int)"); + + public ParallelSorterEmitter(ClassVisitor v, String className, Object[] arrays) { + super(v); + begin_class(Constants.V1_2, Constants.ACC_PUBLIC, className, PARALLEL_SORTER, null, Constants.SOURCE_FILE); + EmitUtils.null_constructor(this); + EmitUtils.factory_method(this, NEW_INSTANCE); + generateConstructor(arrays); + generateSwap(arrays); + end_class(); + } + + private String getFieldName(int index) { + return "FIELD_" + index; + } + + private void generateConstructor(Object[] arrays) { + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT_ARRAY, null); + e.load_this(); + e.super_invoke_constructor(); + e.load_this(); + e.load_arg(0); + e.super_putfield("a", Constants.TYPE_OBJECT_ARRAY); + for (int i = 0; i < arrays.length; i++) { + Type type = Type.getType(arrays[i].getClass()); + declare_field(Constants.ACC_PRIVATE, getFieldName(i), type, null); + e.load_this(); + e.load_arg(0); + e.push(i); + e.aaload(); + e.checkcast(type); + e.putfield(getFieldName(i)); + } + e.return_value(); + e.end_method(); + } + + private void generateSwap(final Object[] arrays) { + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, SWAP, null); + for (int i = 0; i < arrays.length; i++) { + Type type = Type.getType(arrays[i].getClass()); + Type component = TypeUtils.getComponentType(type); + Local T = e.make_local(type); + + e.load_this(); + e.getfield(getFieldName(i)); + e.store_local(T); + + e.load_local(T); + e.load_arg(0); + + e.load_local(T); + e.load_arg(1); + e.array_load(component); + + e.load_local(T); + e.load_arg(1); + + e.load_local(T); + e.load_arg(0); + e.array_load(component); + + e.array_store(component); + e.array_store(component); + } + e.return_value(); + e.end_method(); + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/util/SorterTemplate.java b/blade-aop/src/main/java/net/sf/cglib/util/SorterTemplate.java new file mode 100644 index 000000000..b5c95f53c --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/util/SorterTemplate.java @@ -0,0 +1,173 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.util; + +import java.util.*; + +abstract class SorterTemplate { + private static final int MERGESORT_THRESHOLD = 12; + private static final int QUICKSORT_THRESHOLD = 7; + + abstract protected void swap(int i, int j); + abstract protected int compare(int i, int j); + + protected void quickSort(int lo, int hi) { + quickSortHelper(lo, hi); + insertionSort(lo, hi); + } + + private void quickSortHelper(int lo, int hi) { + for (;;) { + int diff = hi - lo; + if (diff <= QUICKSORT_THRESHOLD) { + break; + } + int i = (hi + lo) / 2; + if (compare(lo, i) > 0) { + swap(lo, i); + } + if (compare(lo, hi) > 0) { + swap(lo, hi); + } + if (compare(i, hi) > 0) { + swap(i, hi); + } + int j = hi - 1; + swap(i, j); + i = lo; + int v = j; + for (;;) { + while (compare(++i, v) < 0) { + /* nothing */; + } + while (compare(--j, v) > 0) { + /* nothing */; + } + if (j < i) { + break; + } + swap(i, j); + } + swap(i, hi - 1); + if (j - lo <= hi - i + 1) { + quickSortHelper(lo, j); + lo = i + 1; + } else { + quickSortHelper(i + 1, hi); + hi = j; + } + } + } + + private void insertionSort(int lo, int hi) { + for (int i = lo + 1 ; i <= hi; i++) { + for (int j = i; j > lo; j--) { + if (compare(j - 1, j) > 0) { + swap(j - 1, j); + } else { + break; + } + } + } + } + + protected void mergeSort(int lo, int hi) { + int diff = hi - lo; + if (diff <= MERGESORT_THRESHOLD) { + insertionSort(lo, hi); + return; + } + int mid = lo + diff / 2; + mergeSort(lo, mid); + mergeSort(mid, hi); + merge(lo, mid, hi, mid - lo, hi - mid); + } + + private void merge(int lo, int pivot, int hi, int len1, int len2) { + if (len1 == 0 || len2 == 0) { + return; + } + if (len1 + len2 == 2) { + if (compare(pivot, lo) < 0) { + swap(pivot, lo); + } + return; + } + int first_cut, second_cut; + int len11, len22; + if (len1 > len2) { + len11 = len1 / 2; + first_cut = lo + len11; + second_cut = lower(pivot, hi, first_cut); + len22 = second_cut - pivot; + } else { + len22 = len2 / 2; + second_cut = pivot + len22; + first_cut = upper(lo, pivot, second_cut); + len11 = first_cut - lo; + } + rotate(first_cut, pivot, second_cut); + int new_mid = first_cut + len22; + merge(lo, first_cut, new_mid, len11, len22); + merge(new_mid, second_cut, hi, len1 - len11, len2 - len22); + } + + private void rotate(int lo, int mid, int hi) { + int lot = lo; + int hit = mid - 1; + while (lot < hit) { + swap(lot++, hit--); + } + lot = mid; hit = hi - 1; + while (lot < hit) { + swap(lot++, hit--); + } + lot = lo; hit = hi - 1; + while (lot < hit) { + swap(lot++, hit--); + } + } + + private int lower(int lo, int hi, int val) { + int len = hi - lo; + while (len > 0) { + int half = len / 2; + int mid= lo + half; + if (compare(mid, val) < 0) { + lo = mid + 1; + len = len - half -1; + } else { + len = half; + } + } + return lo; + } + + private int upper(int lo, int hi, int val) { + int len = hi - lo; + while (len > 0) { + int half = len / 2; + int mid = lo + half; + if (compare(val, mid) < 0) { + len = half; + } else { + lo = mid + 1; + len = len - half -1; + } + } + return lo; + } +} diff --git a/blade-aop/src/main/java/net/sf/cglib/util/StringSwitcher.java b/blade-aop/src/main/java/net/sf/cglib/util/StringSwitcher.java new file mode 100644 index 000000000..df6248822 --- /dev/null +++ b/blade-aop/src/main/java/net/sf/cglib/util/StringSwitcher.java @@ -0,0 +1,154 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.cglib.util; + +import java.util.*; +import net.sf.cglib.core.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.Type; + +/** + * This class implements a simple String->int mapping for a fixed set of keys. + */ +abstract public class StringSwitcher { + private static final Type STRING_SWITCHER = + TypeUtils.parseType("net.sf.cglib.util.StringSwitcher"); + private static final Signature INT_VALUE = + TypeUtils.parseSignature("int intValue(String)"); + private static final StringSwitcherKey KEY_FACTORY = + (StringSwitcherKey)KeyFactory.create(StringSwitcherKey.class); + + interface StringSwitcherKey { + public Object newInstance(String[] strings, int[] ints, boolean fixedInput); + } + + /** + * Helper method to create a StringSwitcher. + * For finer control over the generated instance, use a new instance of StringSwitcher.Generator + * instead of this static method. + * @param strings the array of String keys; must be the same length as the value array + * @param ints the array of integer results; must be the same length as the key array + * @param fixedInput if false, an unknown key will be returned from {@link #intValue} as -1; if true, + * the result will be undefined, and the resulting code will be faster + */ + public static StringSwitcher create(String[] strings, int[] ints, boolean fixedInput) { + Generator gen = new Generator(); + gen.setStrings(strings); + gen.setInts(ints); + gen.setFixedInput(fixedInput); + return gen.create(); + } + + protected StringSwitcher() { + } + + /** + * Return the integer associated with the given key. + * @param s the key + * @return the associated integer value, or -1 if the key is unknown (unless + * fixedInput was specified when this StringSwitcher was created, + * in which case the return value for an unknown key is undefined) + */ + abstract public int intValue(String s); + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(StringSwitcher.class.getName()); + + private String[] strings; + private int[] ints; + private boolean fixedInput; + + public Generator() { + super(SOURCE); + } + + /** + * Set the array of recognized Strings. + * @param strings the array of String keys; must be the same length as the value array + * @see #setInts + */ + public void setStrings(String[] strings) { + this.strings = strings; + } + + /** + * Set the array of integer results. + * @param ints the array of integer results; must be the same length as the key array + * @see #setStrings + */ + public void setInts(int[] ints) { + this.ints = ints; + } + + /** + * Configure how unknown String keys will be handled. + * @param fixedInput if false, an unknown key will be returned from {@link #intValue} as -1; if true, + * the result will be undefined, and the resulting code will be faster + */ + public void setFixedInput(boolean fixedInput) { + this.fixedInput = fixedInput; + } + + protected ClassLoader getDefaultClassLoader() { + return getClass().getClassLoader(); + } + + /** + * Generate the StringSwitcher. + */ + public StringSwitcher create() { + setNamePrefix(StringSwitcher.class.getName()); + Object key = KEY_FACTORY.newInstance(strings, ints, fixedInput); + return (StringSwitcher)super.create(key); + } + + public void generateClass(ClassVisitor v) throws Exception { + ClassEmitter ce = new ClassEmitter(v); + ce.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + getClassName(), + STRING_SWITCHER, + null, + Constants.SOURCE_FILE); + EmitUtils.null_constructor(ce); + final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, INT_VALUE, null); + e.load_arg(0); + final List stringList = Arrays.asList(strings); + int style = fixedInput ? Constants.SWITCH_STYLE_HASHONLY : Constants.SWITCH_STYLE_HASH; + EmitUtils.string_switch(e, strings, style, new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + e.push(ints[stringList.indexOf(key)]); + e.return_value(); + } + public void processDefault() { + e.push(-1); + e.return_value(); + } + }); + e.end_method(); + ce.end_class(); + } + + protected Object firstInstance(Class type) { + return (StringSwitcher)ReflectUtils.newInstance(type); + } + + protected Object nextInstance(Object instance) { + return instance; + } + } +} diff --git a/blade-sql2o/pom.xml b/blade-auth/pom.xml similarity index 53% rename from blade-sql2o/pom.xml rename to blade-auth/pom.xml index 06a2c5b4f..fcf6e9319 100644 --- a/blade-sql2o/pom.xml +++ b/blade-auth/pom.xml @@ -5,33 +5,36 @@ 4.0.0 com.bladejava - blade-root + blade 1.0 - - blade-sql2o - jar - ${blade.version} - blade-sql2o - https://github.com/biezhi/blade/blade-sql2o - + + blade-auth + 0.0.1 + blade-auth + http://maven.apache.org + + + org.slf4j + slf4j-api + com.bladejava blade-core - ${blade.version} - provided + ${blade-core.version} + - com.bladejava - blade-cache - ${blade.version} + javax.servlet + javax.servlet-api + ${servlet.version} provided - org.sql2o - sql2o - 1.5.4 + junit + junit + test diff --git a/blade-auth/src/main/java/com/blade/auth/CSRFConfig.java b/blade-auth/src/main/java/com/blade/auth/CSRFConfig.java new file mode 100644 index 000000000..142383c68 --- /dev/null +++ b/blade-auth/src/main/java/com/blade/auth/CSRFConfig.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.auth; + +/** + * CSRF Config + * + * @author biezhi + * @since 1.0 + */ +public class CSRFConfig { + + // For the global key generation token, default random string + String secret = "blade"; + + // ID to save the session name of the user, default is "csrf_token" + String session = "csrf_token"; + + // HTTP request header information field for passing the token, default is "X-CSRFToken"" + String header = "X-CSRFToken"; + + // Form field name for passing a token, defaukt is "_csrf" + String form = "_csrf"; + + // Cookie name for passing a token, default is "_csrf" + String cookie = "_csrf"; + + // Cookie path, default is "/" + String cookiePath = "/"; + + // Generate the token's length, the default 32 + int length = 32; + + // Cookie long, the default 60 seconds + int expire = 3600; + + // Is used to specify whether the Cookie is set to HTTPS, default is false + boolean secured = false; + + // Is used to specify whether the token is set to the header information in the response, default is false + boolean setHeader = false; + + // Is used to specify whether the token is set to the Cookie of the response, default is false + boolean setCookie = false; + + public CSRFConfig() { + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public void setSession(String session) { + this.session = session; + } + + public void setHeader(String header) { + this.header = header; + } + + public void setForm(String form) { + this.form = form; + } + + public void setCookie(String cookie) { + this.cookie = cookie; + } + + public void setLength(int length) { + this.length = length; + } + + public void setExpire(int expire) { + this.expire = expire; + } + + public void setSetHeader(boolean setHeader) { + this.setHeader = setHeader; + } + + public void setSetCookie(boolean setCookie) { + this.setCookie = setCookie; + } + + public void setCookiePath(String cookiePath) { + this.cookiePath = cookiePath; + } + + public void setSecured(boolean secured) { + this.secured = secured; + } + +} diff --git a/blade-auth/src/main/java/com/blade/auth/CSRFTokenManager.java b/blade-auth/src/main/java/com/blade/auth/CSRFTokenManager.java new file mode 100644 index 000000000..a16a41c68 --- /dev/null +++ b/blade-auth/src/main/java/com/blade/auth/CSRFTokenManager.java @@ -0,0 +1,198 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.auth; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.blade.kit.HashidKit; +import com.blade.kit.StringKit; +import com.blade.mvc.http.Request; +import com.blade.mvc.http.Response; +import com.blade.mvc.http.wrapper.Session; + +/** + * CSRF token Manager + * + * @author biezhi + * @since 1.0 + */ +public class CSRFTokenManager { + + private static Logger LOGGER = LoggerFactory.getLogger(CSRFTokenManager.class); + + private static CSRFConfig config = new CSRFConfig(); + + private static HashidKit HASHID = new HashidKit(config.secret, config.length); + + private CSRFTokenManager() { + } + + public static void config(CSRFConfig config){ + CSRFTokenManager.config = config; + HASHID = new HashidKit(config.secret, config.length); + } + + /** + * Create a token + * + * @param request request object + * @param response response object + * @return return token + */ + public static String createToken(Request request, Response response) { + String token = null; + synchronized (request) { + Session session = request.session(); + String objToken = session.attribute(config.session); + + if(StringKit.isBlank(objToken)){ + token = createNewToken(request, response); + LOGGER.debug("create csrf_token:{}", token); + } else { + token = objToken; + session.attribute(config.session, token); + } + } + return token; + } + + /** + * Create a token + * + * @param request request object + * @param response response object + * @return return token + */ + public static String createNewToken(Request request, Response response) { + String token = null; + synchronized (request) { + Session session = request.session(); + session.removeAttribute(config.session); + token = HASHID.encode( System.currentTimeMillis() ); + session.attribute(config.session, token); + if(config.setHeader){ + response.header(config.header, token); + } + if(config.setCookie){ + response.cookie(config.cookiePath, config.cookie, token, config.expire, config.secured); + } + LOGGER.debug("create csrf_token:{}", token); + } + return token; + } + + public static boolean verify(Request request, Response response) { + // csrftoken attribute from session + String sToken = request.session().attribute(config.session); + if (sToken == null) { + // Generate new token into session + sToken = CSRFTokenManager.createToken(request, response); + return true; + } else { + String pToken = request.query(config.form); + if(config.setHeader){ + pToken = request.header(config.form); + } + if(config.setCookie){ + pToken = request.cookie(config.form); + } + if (StringKit.isNotBlank(pToken) && sToken.equals(pToken)) { + return true; + } + } + + return false; + } + + /** + * According to form parameter verification + * + * @param request request object + * @param response response object + * @return return verify is success + */ + public static boolean verifyAsForm(Request request, Response response) { + // csrftoken attribute from session + String sToken = request.session().attribute(config.session); + if (sToken == null) { + // Generate new token into session + sToken = CSRFTokenManager.createToken(request, response); + return true; + } else { + String pToken = request.query(config.form); + if(config.setHeader){ + pToken = request.header(config.form); + } + if(config.setCookie){ + pToken = request.cookie(config.form); + } + if (StringKit.isNotBlank(pToken) && sToken.equals(pToken)) { + return true; + } + } + + return false; + } + + /** + * According to header information verification + * + * @param request request object + * @param response response object + * @return return verify is success + */ + public static boolean verifyAsHeader(Request request, Response response) { + // csrftoken attribute from session + String sToken = request.session().attribute(config.session); + if (sToken == null) { + // Generate new token into session + sToken = CSRFTokenManager.createToken(request, response); + return true; + } else { + String pToken = request.header(config.header); + if (StringKit.isNotBlank(pToken) && sToken.equals(pToken)) { + return true; + } + } + + return false; + } + + /** + * According to cookie verification + * + * @param request request object + * @param response response object + * @return return verify is success + */ + public static boolean verifyAsCookie(Request request, Response response) { + // csrftoken attribute from session + String sToken = request.session().attribute(config.session); + if (sToken == null) { + // Generate new token into session + sToken = CSRFTokenManager.createToken(request, response); + return true; + } else { + String pToken = request.cookie(config.cookie); + if (StringKit.isNotBlank(pToken) && sToken.equals(pToken)) { + return true; + } + } + return false; + } + +} diff --git a/blade-auth/src/main/java/com/blade/auth/Xss.java b/blade-auth/src/main/java/com/blade/auth/Xss.java new file mode 100644 index 000000000..1b79074d4 --- /dev/null +++ b/blade-auth/src/main/java/com/blade/auth/Xss.java @@ -0,0 +1,11 @@ +package com.blade.auth; + +import com.blade.kit.text.HTMLFilter; + +public class Xss { + + public String filter(String str){ + return HTMLFilter.htmlSpecialChars(str); + } + +} diff --git a/blade-auth/src/main/java/com/blade/auth/package-info.java b/blade-auth/src/main/java/com/blade/auth/package-info.java new file mode 100644 index 000000000..8cc06e0e8 --- /dev/null +++ b/blade-auth/src/main/java/com/blade/auth/package-info.java @@ -0,0 +1,4 @@ +/** + * Authentication And Security + */ +package com.blade.auth; \ No newline at end of file diff --git a/blade-auth/src/test/java/com/blade/auth/AppTest.java b/blade-auth/src/test/java/com/blade/auth/AppTest.java new file mode 100644 index 000000000..45f271d54 --- /dev/null +++ b/blade-auth/src/test/java/com/blade/auth/AppTest.java @@ -0,0 +1,38 @@ +package com.blade.auth; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/blade-beetl/pom.xml b/blade-beetl/pom.xml deleted file mode 100644 index 318680756..000000000 --- a/blade-beetl/pom.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - 4.0.0 - - com.bladejava - blade-root - 1.0 - - - blade-beetl - jar - ${blade.version} - blade-beetl - https://github.com/biezhi/blade/blade-beetl - - - - com.bladejava - blade-core - ${blade.version} - - - org.beetl - beetl-core - 2.2.3 - - - diff --git a/blade-beetl/src/main/java/blade/render/BeetlRender.java b/blade-beetl/src/main/java/blade/render/BeetlRender.java deleted file mode 100644 index d2ee6d82a..000000000 --- a/blade-beetl/src/main/java/blade/render/BeetlRender.java +++ /dev/null @@ -1,112 +0,0 @@ -package blade.render; - -import java.io.IOException; -import java.util.Enumeration; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.beetl.core.Configuration; -import org.beetl.core.GroupTemplate; -import org.beetl.core.Template; -import org.beetl.core.exception.BeetlException; -import org.beetl.core.resource.FileResourceLoader; - -import blade.Blade; -import blade.BladeWebContext; -import blade.exception.BladeException; - -/** - * Velocity渲染引擎 - * @author biezhi - * - */ -public class BeetlRender extends Render { - - private GroupTemplate groupTemplate = null; - - /** - * 默认构造函数 - */ - public BeetlRender() { - String root = Blade.webRoot(); - FileResourceLoader resourceLoader = new FileResourceLoader(root,"utf-8"); - try { - Configuration cfg = Configuration.defaultConfiguration(); - groupTemplate = new GroupTemplate(resourceLoader, cfg); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * 渲染视图 - */ - @Override - public Object render(String view) { - - try { - HttpServletRequest servletRequest = BladeWebContext.servletRequest(); - HttpServletResponse servletResponse = BladeWebContext.servletResponse(); - - view = disposeView(view); - - Template template = groupTemplate.getTemplate(view); - - Enumeration attrs = servletRequest.getAttributeNames(); - - if(null != attrs && attrs.hasMoreElements()){ - while(attrs.hasMoreElements()){ - String attr = attrs.nextElement(); - template.binding(attr, servletRequest.getAttribute(attr)); - } - } - - template.renderTo(servletResponse.getOutputStream()); - } catch (BeetlException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - - /** - * 渲染视图 - */ - @Override - public Object render(ModelAndView modelAndView) { - HttpServletRequest servletRequest = BladeWebContext.servletRequest(); - HttpServletResponse servletResponse = BladeWebContext.servletResponse(); - - if(null == modelAndView){ - throw new BladeException("modelAndView is null"); - } - - try { - - String view = disposeView(modelAndView.getView()); - - Template template = groupTemplate.getTemplate(view); - - Enumeration attrs = servletRequest.getAttributeNames(); - - if(null != attrs && attrs.hasMoreElements()){ - while(attrs.hasMoreElements()){ - String attr = attrs.nextElement(); - template.binding(attr, servletRequest.getAttribute(attr)); - } - } - - template.renderTo(servletResponse.getOutputStream()); - } catch (BeetlException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - - return null; - } - -} diff --git a/blade-cache/pom.xml b/blade-cache/pom.xml deleted file mode 100644 index 36ba051b1..000000000 --- a/blade-cache/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - 4.0.0 - - com.bladejava - blade-root - 1.0 - - - blade-cache - jar - ${blade.version} - blade-cache - https://github.com/biezhi/blade/blade-cache - - diff --git a/blade-cache/src/main/java/blade/cache/AbstractCache.java b/blade-cache/src/main/java/blade/cache/AbstractCache.java deleted file mode 100644 index 3b292ed6e..000000000 --- a/blade-cache/src/main/java/blade/cache/AbstractCache.java +++ /dev/null @@ -1,306 +0,0 @@ -package blade.cache; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * 抽象缓存基础实现 - * - * @author biezhi - * @since 1.0 - * @param - * @param - */ -@SuppressWarnings("unchecked") -public abstract class AbstractCache implements Cache { - - /** - * 同步缓存容器 - */ - protected Map> _mCache; - - /** - * 同步缓存容器 - */ - protected Map>> _hCache; - - /** - * 缓存锁 - */ - protected final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(); - - /** - * 读取锁 - */ - protected final Lock readLock = cacheLock.readLock(); - - /** - * 写入锁 - */ - protected final Lock writeLock = cacheLock.writeLock(); - - /** - * 最大缓存数 - */ - protected int cacheSize; - - /** - * 默认过期时间, 0 -> 永不过期 - */ - protected long defaultExpire; - - /** - * 是否设置默认过期时间 - */ - protected boolean existCustomExpire; - - /** - * 淘汰对象具体实现 - * @return - */ - protected abstract int eliminateCache(); - - /** - * 设置一个缓存大小并初始化 - * @param cacheSize - */ - public AbstractCache(int cacheSize) { - this.cacheSize = cacheSize; - this._mCache = Collections.synchronizedMap(new HashMap>()); - this._hCache = Collections.synchronizedMap(new HashMap>>()); - } - - /** - * 放一个缓存 - */ - public void set(K key, V obj) { - set(key, obj, defaultExpire); - } - - /** - * 放一个缓存并设置缓存时间 - */ - public void set(K key, V value, long expire) { - writeLock.lock(); - try { - CacheObject co = new CacheObject(key, value, expire); - if (expire != 0) { - existCustomExpire = true; - } - if (isFull()) { - eliminate() ; - } - _mCache.put(key, co); - } - finally { - writeLock.unlock(); - } - } - - /** - * 放一个缓存 - */ - public void hset(K key, F field, V obj) { - hset(key, field, obj, defaultExpire); - } - - /** - * 放一个hash类型缓存并设置缓存时间 - */ - public void hset(K key, F field, V value, long expire) { - writeLock.lock(); - try { - CacheObject co = new CacheObject(key, value, expire); - - if(expire != 0){ - existCustomExpire = true; - } - - if(isFull()){ - eliminate() ; - } - - Map> coMap = (Map>) _hCache.get(key); - if(null == coMap){ - coMap = new HashMap>(); - } - coMap.put(field, co); - - _hCache.put(key, coMap); - } - finally { - writeLock.unlock(); - } - } - - - /** - * 取一个缓存 - */ - public V get(K key) { - readLock.lock(); - try { - CacheObject co = _mCache.get(key); - if (co == null) { - return null; - } - if (co.isExpired() == true) { - _mCache.remove(key); - return null; - } - return co.getValue(); - } finally { - readLock.unlock(); - } - } - - /** - * 取一个缓存 - */ - public V hget(K key, F field) { - readLock.lock(); - try { - Map> coMap = _hCache.get(key); - - if(null == coMap){ - return null; - } - - CacheObject co = coMap.get(field); - - if(null == co){ - return null; - } - - if (co.isExpired() == true) { - coMap.remove(field); - return null; - } - - return co.getValue(); - } finally { - readLock.unlock(); - } - } - - /** - * 移除一个缓存 - */ - public void del(K key) { - writeLock.lock(); - try { - _mCache.remove(key); - } finally { - writeLock.unlock(); - } - } - - /** - * 移除一个缓存 - */ - public void hdel(K key) { - writeLock.lock(); - try { - _hCache.remove(key); - } finally { - writeLock.unlock(); - } - } - - /** - * 移除一个缓存 - */ - public void del(K key, F feild) { - writeLock.lock(); - try { - Map> coMap = _hCache.get(key); - if(null != coMap){ - coMap.remove(feild); - } - } finally { - writeLock.unlock(); - } - } - - @Override - public Set keys() { - return _mCache.keySet(); - } - - @Override - public Set flieds(K key) { - Map> coMap = _hCache.get(key); - if(null == coMap){ - return null; - } - return (Set) coMap.keySet(); - } - - public int elementsInCache() { - return ( _mCache.size() + _hCache.size() ); - } - - @Override - public int size() { - return ( _mCache.size() + _hCache.size() ); - } - - protected boolean isNeedClearExpiredObject(){ - return defaultExpire > 0 || existCustomExpire ; - } - - public final int eliminate() { - writeLock.lock(); - try { - return eliminateCache(); - } - finally { - writeLock.unlock(); - } - } - - @Override - public boolean isFull() { - if (cacheSize == 0) {// o -> 无限制 - return false; - } - return ( _mCache.size() + _hCache.size() ) >= cacheSize; - } - - @Override - public void clear() { - writeLock.lock(); - try { - _mCache.clear(); - } finally { - writeLock.unlock(); - } - } - - @Override - public int getCacheSize() { - return cacheSize; - } - - @Override - public boolean isEmpty() { - return size() == 0; - } - - @Override - public Cache cacheSize(int cacheSize) { - this.cacheSize = cacheSize; - return this; - } - - @Override - public Cache expire(long expire) { - this.defaultExpire = expire; - return this; - } - -} \ No newline at end of file diff --git a/blade-cache/src/main/java/blade/cache/Cache.java b/blade-cache/src/main/java/blade/cache/Cache.java deleted file mode 100644 index 557f86ef3..000000000 --- a/blade-cache/src/main/java/blade/cache/Cache.java +++ /dev/null @@ -1,153 +0,0 @@ - -package blade.cache; - -import java.util.Set; - - -/** - * 缓存顶级接口 - * - * @author biezhi - * @since 1.0 - * @param - * @param - */ -public interface Cache { - - /** - * 向缓存添加value对象,其在缓存中生存时间为默认值 - * - * @param key - * @param value - * @throws CacheException - */ - void set(K key, V value); - - /** - * 向缓存添加value对象,并指定存活时间 - * - * @param key - * @param value 过期时间 - * @param expire - * @throws CacheException - */ - void set(K key, V value, long expire); - - /** - * 向缓存添加一个hash类型的数据 - * - * @param key - * @param field - * @param value - */ - void hset(K key, F field, V value); - - /** - * 向缓存添加一个hash类型的数据并设置有效期 - * - * @param key - * @param field - * @param value - * @param expire - */ - void hset(K key, F field, V value, long expire); - - /** - * 查找缓存对象 - * - * @param key - * @return - * @throws CacheException - */ - V get(K key); - - /** - * 查找缓存对象 - * - * @param key - * @param field - * @return - */ - V hget(K key, F field); - - /** - * 删除缓存对象 - * - * @param key - */ - void del(K key); - - /** - * 删除缓存对象 - * - * @param key - */ - void hdel(K key); - - /** - * 删除缓存对象 - * - * @param key - * @param field - */ - void del(K key, F field); - - /** - * @return 返回所有key - */ - Set keys(); - - /** - * @return 返回一个key所有flied - */ - Set flieds(K key); - - /** - * @return 返回当前缓存的大小 - */ - int size(); - - /** - * 淘汰对象 - * - * @return 被删除对象大小 - */ - int eliminate(); - - /** - * 缓存是否已经满 - * @return - */ - boolean isFull(); - - /** - * 清除所有缓存对象 - */ - void clear(); - - /** - * 返回缓存大小 - * - * @return - */ - int getCacheSize(); - - /** - * 缓存中是否为空 - */ - boolean isEmpty(); - - /** - * 设置缓存大小 - * @param cacheSize - * @return - */ - Cache cacheSize(int cacheSize); - - /** - * 设置缓存有效期 - * @param expire - * @return - */ - Cache expire(long expire); - } diff --git a/blade-cache/src/main/java/blade/cache/CacheCleaner.java b/blade-cache/src/main/java/blade/cache/CacheCleaner.java deleted file mode 100644 index 2f68766f1..000000000 --- a/blade-cache/src/main/java/blade/cache/CacheCleaner.java +++ /dev/null @@ -1,85 +0,0 @@ -package blade.cache; - -import java.util.Set; - -/** - * 定时清理缓存线程 - * - * @author biezhi - * @since 1.0 - */ -public class CacheCleaner extends Thread { - - /** - * 清理间隔 - */ - private long _cleanInterval; - - /** - * 是不是在睡眠中 - */ - private boolean _sleep = false; - - /** - * 设置清理间隔并初始化 - * - * @param cleanInterval - */ - public CacheCleaner(long cleanInterval) { - _cleanInterval = cleanInterval; - setName(this.getClass().getName()); - setDaemon(true); - } - - /** - * 设置清理间隔 - * - * @param cleanInterval - */ - public void setCleanInterval(long cleanInterval) { - _cleanInterval = cleanInterval; - synchronized (this) { - if (_sleep) { - interrupt(); - } - } - } - - /** - * 执行缓存清理工作 - */ - @Override - public void run() { - while (true) { - try { - try { - sleep(_cleanInterval); - } catch (Throwable t) { - } finally { - _sleep = false; - } - CacheManager cacheFactory = CacheManager.getInstance(); - Set cacheIds = cacheFactory.getCacheIds(); - if (null != cacheIds) { - for (String cacheId : cacheIds) { - Cache cache = cacheFactory.getCache(cacheId); - if (cache != null) { - cache.clear(); - } - yield(); - } - } - } catch (Throwable t) { - t.printStackTrace(); - } - _sleep = true; -// try { -// sleep(_cleanInterval); -// } catch (Throwable t) { -// } finally { -// _sleep = false; -// } - } - } - -} \ No newline at end of file diff --git a/blade-cache/src/main/java/blade/cache/CacheException.java b/blade-cache/src/main/java/blade/cache/CacheException.java deleted file mode 100644 index 16e8a3c97..000000000 --- a/blade-cache/src/main/java/blade/cache/CacheException.java +++ /dev/null @@ -1,19 +0,0 @@ -package blade.cache; - -/** - * 缓存异常封装 - * - * @author biezhi - * @since 1.0 - */ -public class CacheException extends Exception { - - private static final long serialVersionUID = 2794445171318574075L; - - public CacheException() { - } - - public CacheException(String msg) { - super(msg); - } -} \ No newline at end of file diff --git a/blade-cache/src/main/java/blade/cache/CacheManager.java b/blade-cache/src/main/java/blade/cache/CacheManager.java deleted file mode 100644 index 81479ff10..000000000 --- a/blade-cache/src/main/java/blade/cache/CacheManager.java +++ /dev/null @@ -1,199 +0,0 @@ -package blade.cache; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import blade.cache.impl.FIFOCache; -import blade.cache.impl.LFUCache; -import blade.cache.impl.LRUCache; - -/** - * 缓存管理对象 - * - * @author biezhi - * @since 1.0 - */ -@SuppressWarnings({"rawtypes", "unchecked"}) -public class CacheManager { - - private static final CacheManager _instance = new CacheManager(); - - private static final String CACHE_SUFFIX = "blade_cache_ID_"; - - private Map _cacheMap; - - private CacheCleaner _cleaner; - - private int _cacheSize = 100; - - // 一小时 - private static final int DEFAULT_CLEAN_TIME = 3600000; - - private static Object _mlock = new Object(); - - public CacheManager() { - _cacheMap = new HashMap(); - _cleaner = new CacheCleaner(DEFAULT_CLEAN_TIME); //默认30秒自动清空缓存 - _cleaner.start(); - } - - public static CacheManager getInstance(){ - return _instance; - } - - public void setCleanInterval(long time) { - _cleaner.setCleanInterval(time); - } - - public Cache getCache(String cacheId){ - if (cacheId == null) { - throw new NullPointerException("cacheId is null"); - } - synchronized (_mlock) { - return _cacheMap.get(cacheId); - } - } - - /***************************** LRUCache:START **************************************/ - public Cache newLRUCache(){ - synchronized (_mlock) { - String cacheId = CACHE_SUFFIX + System.currentTimeMillis(); - return newLRUCache(cacheId, _cacheSize); - } - } - - public Cache newLRUCache(String cacheId){ - if (cacheId == null) { - throw new NullPointerException("cacheId is null"); - } - return newLRUCache(cacheId, _cacheSize); - } - - public Cache newLRUCache(int cacheSize){ - synchronized (_mlock) { - String cacheId = CACHE_SUFFIX + System.currentTimeMillis(); - return newLRUCache(cacheId, cacheSize); - } - } - - public Cache newLRUCache(String cacheId, int cacheSize){ - synchronized (_mlock) { - Cache cache = new LRUCache(cacheSize); - _cacheMap.put(cacheId, cache); - return cache; - } - } - /***************************** LRUCache:END **************************************/ - - - /***************************** LFUCache:START **************************************/ - public Cache newLFUCache(){ - synchronized (_mlock) { - String cacheId = CACHE_SUFFIX + System.currentTimeMillis(); - return newLFUCache(cacheId, _cacheSize); - } - } - - public Cache newLFUCache(String cacheId){ - if (cacheId == null) { - throw new NullPointerException("cacheId is null"); - } - return newLFUCache(cacheId, _cacheSize); - } - - public Cache newLFUCache(int cacheSize){ - synchronized (_mlock) { - String cacheId = CACHE_SUFFIX + System.currentTimeMillis(); - return newLFUCache(cacheId, cacheSize); - } - } - - public Cache newLFUCache(String cacheId, int cacheSize){ - synchronized (_mlock) { - Cache cache = new LFUCache(cacheSize); - _cacheMap.put(cacheId, cache); - return cache; - } - } - /***************************** LFUCache:END **************************************/ - - - /***************************** LFUCache:START **************************************/ - public Cache newFIFOCache(){ - synchronized (_mlock) { - String cacheId = CACHE_SUFFIX + System.currentTimeMillis(); - return newFIFOCache(cacheId, _cacheSize); - } - } - - public Cache newFIFOCache(String cacheId){ - if (cacheId == null) { - throw new NullPointerException("cacheId is null"); - } - return newLFUCache(cacheId, _cacheSize); - } - - public Cache newFIFOCache(int cacheSize){ - synchronized (_mlock) { - String cacheId = CACHE_SUFFIX + System.currentTimeMillis(); - return newFIFOCache(cacheId, cacheSize); - } - } - - public Cache newFIFOCache(String cacheId, int cacheSize){ - synchronized (_mlock) { - Cache cache = new FIFOCache(cacheSize); - _cacheMap.put(cacheId, cache); - return cache; - } - } - /***************************** LFUCache:END **************************************/ - - /** - * @return 返回所有缓存id - */ - public Set getCacheIds(){ - synchronized (_mlock) { - if(null != _cacheMap && _cacheMap.size() > 0){ - return _cacheMap.keySet(); - } - } - return null; - } - - /** - * 移除一个缓存 - * @param cacheId - * @throws CacheException - */ - public void removeCache(String cacheId) throws CacheException { - if(cacheId == null) { - throw new NullPointerException("cacheId is null"); - } - synchronized(_mlock){ - _cacheMap.remove(cacheId); - } - } - - /** - * 移除所有缓存 - * @param cacheId - * @throws CacheException - */ - public void removeAll() { - synchronized(_mlock){ - if(null != _cacheMap && _cacheMap.size() > 0){ - - Set keys = _cacheMap.keySet(); - for(String key : keys){ - Cache cache = _cacheMap.get(key); - if(null != cache){ - cache.clear(); - } - } - _cacheMap.clear(); - } - } - } -} \ No newline at end of file diff --git a/blade-cache/src/main/java/blade/cache/CacheObject.java b/blade-cache/src/main/java/blade/cache/CacheObject.java deleted file mode 100644 index fa3010967..000000000 --- a/blade-cache/src/main/java/blade/cache/CacheObject.java +++ /dev/null @@ -1,98 +0,0 @@ -package blade.cache; - -/** - * 缓存实体对象 - * - * @author biezhi - * @since 1.0 - * @param - * @param - */ -public class CacheObject { - - private K key; - private V value; - private long expires; // 对象存活时间(time-to-live) - private long lastAccess; // 最后访问时间 - private long accessCount; // 访问次数 - private CacheObject previous; - private CacheObject next; - - public CacheObject(K k, V v, long expires) { - this.key = k; - this.value = v; - this.expires = expires; - } - - public CacheObject() { - } - - /** - * @return 返回是否已经过期 - */ - public boolean isExpired() { - if (expires == 0) { - return false; - } - return lastAccess + expires < System.currentTimeMillis(); - } - - public V getValue() { - lastAccess = System.currentTimeMillis(); - accessCount++; - return value; - } - - public K getKey() { - return key; - } - - public long getExpires() { - return expires; - } - - public void setExpires(long expires) { - this.expires = expires; - } - - public long getLastAccess() { - return lastAccess; - } - - public void setLastAccess(long lastAccess) { - this.lastAccess = lastAccess; - } - - public long getAccessCount() { - return accessCount; - } - - public void setAccessCount(long accessCount) { - this.accessCount = accessCount; - } - - public CacheObject getPrevious() { - return previous; - } - - public void setPrevious(CacheObject previous) { - this.previous = previous; - } - - public CacheObject getNext() { - return next; - } - - public void setNext(CacheObject next) { - this.next = next; - } - - public void setKey(K key) { - this.key = key; - } - - public void setValue(V value) { - this.value = value; - } - -} \ No newline at end of file diff --git a/blade-cache/src/main/java/blade/cache/impl/FIFOCache.java b/blade-cache/src/main/java/blade/cache/impl/FIFOCache.java deleted file mode 100644 index 20399c474..000000000 --- a/blade-cache/src/main/java/blade/cache/impl/FIFOCache.java +++ /dev/null @@ -1,49 +0,0 @@ -package blade.cache.impl; - -import java.util.Iterator; - -import blade.cache.AbstractCache; -import blade.cache.CacheObject; - - -/** - * FIFO实现 - * - * @author biezhi - * @since 1.0 - * @param - * @param - */ -public class FIFOCache extends AbstractCache { - - public FIFOCache(int cacheSize) { - super(cacheSize); - } - - @Override - protected int eliminateCache() { - - int count = 0; - K firstKey = null; - - Iterator> iterator = _mCache.values().iterator(); - while (iterator.hasNext()) { - CacheObject cacheObject = iterator.next(); - - if (cacheObject.isExpired()) { - iterator.remove(); - count++; - } else { - if (firstKey == null) - firstKey = cacheObject.getKey(); - } - } - - if (firstKey != null && isFull()) {// 删除过期对象还是满,继续删除链表第一个 - _mCache.remove(firstKey); - } - - return count; - } - -} \ No newline at end of file diff --git a/blade-cache/src/main/java/blade/cache/impl/LFUCache.java b/blade-cache/src/main/java/blade/cache/impl/LFUCache.java deleted file mode 100644 index 9a82ccaeb..000000000 --- a/blade-cache/src/main/java/blade/cache/impl/LFUCache.java +++ /dev/null @@ -1,69 +0,0 @@ -package blade.cache.impl; - -import java.util.Iterator; - -import blade.cache.AbstractCache; -import blade.cache.CacheObject; - -/** - * LFU实现 - * - * @author biezhi - * @since 1.0 - * @param - * @param - */ -public class LFUCache extends AbstractCache { - - public LFUCache(int cacheSize) { - super(cacheSize); - } - - /** - * 实现删除过期对象 和 删除访问次数最少的对象 - * - */ - @Override - protected int eliminateCache() { - Iterator> iterator = _mCache.values().iterator(); - int count = 0; - long minAccessCount = Long.MAX_VALUE; - - while (iterator.hasNext()) { - CacheObject cacheObject = iterator.next(); - - if (cacheObject.isExpired()) { - iterator.remove(); - count++; - continue; - } else { - minAccessCount = Math.min(cacheObject.getAccessCount(), - minAccessCount); - } - } - - if (count > 0) - return count; - - if (minAccessCount != Long.MAX_VALUE) { - - iterator = _mCache.values().iterator(); - - while (iterator.hasNext()) { - CacheObject cacheObject = iterator.next(); - - cacheObject.setAccessCount(cacheObject.getAccessCount() - minAccessCount); - - if (cacheObject.getAccessCount() <= 0) { - iterator.remove(); - count++; - } - - } - - } - - return count; - } - -} \ No newline at end of file diff --git a/blade-cache/src/main/java/blade/cache/impl/LRUCache.java b/blade-cache/src/main/java/blade/cache/impl/LRUCache.java deleted file mode 100644 index 939c82f7a..000000000 --- a/blade-cache/src/main/java/blade/cache/impl/LRUCache.java +++ /dev/null @@ -1,64 +0,0 @@ -package blade.cache.impl; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; - -import blade.cache.AbstractCache; -import blade.cache.CacheObject; - -/** - * LRU 实现 - * - * @author biezhi - * @since 1.0 - * @param - * @param - */ -public class LRUCache extends AbstractCache { - - public LRUCache(int cacheSize) { - - super(cacheSize) ; - - //linkedHash已经实现LRU算法 是通过双向链表来实现,当某个位置被命中,通过调整链表的指向将该位置调整到头位置,新加入的内容直接放在链表头,如此一来,最近被命中的内容就向链表头移动,需要替换时,链表最后的位置就是最近最少使用的位置 - this._mCache = new LinkedHashMap>( cacheSize +1 , 1f,true ) { - - private static final long serialVersionUID = 1L; - - @Override - protected boolean removeEldestEntry(Map.Entry> eldest) { - return LRUCache.this.removeEldestEntry(eldest); - } - - }; - } - - private boolean removeEldestEntry(Map.Entry> eldest) { - if (cacheSize == 0) - return false; - return size() > cacheSize; - } - - /** - * 只需要实现清除过期对象就可以了,linkedHashMap已经实现LRU - */ - @Override - protected int eliminateCache() { - - if(!isNeedClearExpiredObject()){ return 0 ;} - - Iterator> iterator = _mCache.values().iterator(); - int count = 0 ; - while(iterator.hasNext()){ - CacheObject cacheObject = iterator.next(); - - if(cacheObject.isExpired() ){ - iterator.remove(); - count++ ; - } - } - - return count; - } - -} \ No newline at end of file diff --git a/blade-cache/src/test/java/blade/test/CacheTest.java b/blade-cache/src/test/java/blade/test/CacheTest.java deleted file mode 100644 index c414640fa..000000000 --- a/blade-cache/src/test/java/blade/test/CacheTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package blade.test; - -import org.junit.Test; - -import blade.cache.Cache; -import blade.cache.CacheManager; - -public class CacheTest { - - @Test - public void testLRU2(){ - CacheManager cm = CacheManager.getInstance(); - - Cache cache = cm.newLRUCache(); - cache.set("name:1", "jack"); - cache.set("name:2", "jack2"); - - System.out.println(cache.get("name:2")); - - } - - @Test - public void testAutoClean(){ - CacheManager cm = CacheManager.getInstance(); - cm.setCleanInterval(1000); - - Cache cache = cm.newLRUCache(); - cache.set("name:1", "jack"); - cache.set("name:2", "jack2"); - - System.out.println(cache.get("name:2")); - - try { - Thread.sleep(3000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(cache.get("name:2")); - } - - @Test - public void testHashCache(){ - CacheManager cm = CacheManager.getInstance(); - - Cache cache = cm.newLRUCache(); - cache.hset("user:list", "a1", "123"); - cache.hset("user:list", "a2", "456"); - cache.hset("user:list", "a3", "789"); - - System.out.println(cache.hget("user:list", "a1")); - System.out.println(cache.hget("user:list", "a2")); - System.out.println(cache.hget("user:list", "a3")); - - } - -} diff --git a/blade-core/pom.xml b/blade-core/pom.xml index c9a8a1f25..c80b313e9 100644 --- a/blade-core/pom.xml +++ b/blade-core/pom.xml @@ -1,43 +1,70 @@ - - - 4.0.0 - - com.bladejava - blade-root - 1.0 - - - blade-core - jar - ${blade.version} - blade-core - https://github.com/biezhi/blade/blade-core - - - 9.0.2.v20130417 - - - - - - com.bladejava - blade-kit - ${blade.version} - - - - org.eclipse.jetty - jetty-server - ${jetty.version} - provided - - - org.eclipse.jetty - jetty-webapp - ${jetty.version} - provided - - - + + + 4.0.0 + + + com.bladejava + blade + 1.0 + + + blade-core + jar + ${blade-core.version} + blade-core + https://github.com/biezhi/blade/blade-core + + + + org.slf4j + slf4j-api + + + + org.slf4j + slf4j-log4j12 + + + + com.bladejava + blade-kit + ${blade-kit.version} + + + + javax.servlet + javax.servlet-api + ${servlet.version} + provided + + + + junit + junit + test + + + + org.mockito + mockito-all + test + + + + org.eclipse.jetty + jetty-server + ${jetty.version} + test + + + org.eclipse.jetty + jetty-webapp + ${jetty.version} + test + + + + + diff --git a/blade-core/src/main/java/blade/BladeBase.java b/blade-core/src/main/java/blade/BladeBase.java deleted file mode 100644 index d9bebb8ac..000000000 --- a/blade-core/src/main/java/blade/BladeBase.java +++ /dev/null @@ -1,471 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade; - -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.Map; - -import blade.ioc.Container; -import blade.ioc.DefaultContainer; -import blade.render.Render; -import blade.render.RenderFactory; -import blade.route.DefaultRouteMatcher; -import blade.server.BladeServer; - -/** - * Blade的基础类 - * - * @author biezhi - * @since 1.0 - */ -abstract class BladeBase { - - /** - * 默认路由后缀包,用户扫描路由所在位置,默认为route,用户可自定义 - */ - public static String PACKAGE_ROUTE = "route"; - - protected static final String DEFAULT_ACCEPT_TYPE = "*/*"; - - /** - * 默认拦截器后缀包,用户扫描拦截器所在位置,默认为interceptor,用户可自定义 - */ - public static String PACKAGE_INTERCEPTOR = "interceptor"; - - public static final Charset UTF_8 = Charset.forName("UTF-8"); - - /** - * 是否以jetty方式运行 - */ - public static boolean runJetty = false; - - /** - * web应用根目录,应用启动时载入 - */ - private static String WEB_ROOT = ""; - - /** - * Blade默认编码,可修改 - */ - protected static String ENCODING = UTF_8.toString(); - - /** - * 默认视图的路径,默认渲染引擎为JSP,设置WEB-INF目录更安全,可配置 - */ - protected static String VIEW_PATH = "/WEB-INF/"; - - /** - * 静态资源所在文件夹 - */ - protected static String[] STATIC_FOLDER = null; - - /** - * 框架是否已经初始化 - */ - protected static boolean IS_INIT = false; - - /** - * 默认视图文件后缀名 - */ - protected static String VIEW_EXT = ".jsp"; - - /** - * blade全局初始化对象,在web.xml中配置,必须 - */ - protected static BladeApplication bladeApplication; - - /** - * 路由匹配器,用于添加,删除,查找路由 - */ - protected static DefaultRouteMatcher routeMatcher; - - /** - * 存放要扫描的包map - */ - protected static final Map packageMap = new HashMap(); - - /** - * 默认的404视图 - */ - protected static String VIEW_404 = null; - - /** - * 默认的500视图 - */ - protected static String VIEW_500 = null; - - /** - * jetty启动的默认端口 - */ - protected static int PORT = 9000; - - protected static boolean DEBUG = true; - - /** - * IOC容器,存储路由到ioc中 - */ - private final static Container container = DefaultContainer.single(); - - /** - * 包类型枚举 - * - * basepackge 基础包,默认的路由,拦截器包 - * route 路由包,所有路由所在包,可递归 - * interceptor 拦截器包,所有拦截器所在包,不可递归 - * ioc IOC对象所在包,可递归 - * @author biezhi - * - */ - public enum PackageNames { - basepackge, route, interceptor, ioc - } - - protected BladeBase() { - } - - /*--------------------SET CONST:START-------------------------*/ - - /** - * 设置路由包,如:com.baldejava.route - * 可传入多个包,所有的路由类都在该包下 - * - * @param pckages 路由包路径 - */ - public static synchronized void routes(String...pckages){ - if(null != pckages && pckages.length >0){ - packageMap.put(PackageNames.route, pckages); - } - } - - /** - * 设置顶层包,框架自动寻找路由包和拦截器包,如:com.bladejava - * 如上规则,会超找com.bladejava.route、com.bladejava.interceptor下的路由和拦截器 - * - * @param basePackage 默认包路径 - */ - public static synchronized void defaultRoute(String basePackage){ - if(null != basePackage){ - packageMap.put(PackageNames.basepackge, new String[]{basePackage}); - } - } - - /** - * 设置拦截器所在的包路径,如:com.bladejava.interceptor - * - * @param packageName 拦截器所在的包 - */ - public static synchronized void interceptor(String packageName) { - if(null != packageName && packageName.length() >0){ - packageMap.put(PackageNames.interceptor, new String[]{packageName}); - } - } - - /** - * 设置依赖注入包,如:com.bladejava.service - * - * @param pckages 所有需要做注入的包,可传入多个 - */ - public static synchronized void ioc(String...pckages){ - if(null != pckages && pckages.length >0){ - packageMap.put(PackageNames.ioc, pckages); - } - } - - /** - * 设置渲染引擎,默认是JSP引擎 - * - * @param render 渲染引擎对象 - */ - public static synchronized void viewEngin(Render render) { - RenderFactory.init(render); - } - - /** - * 设置默认视图路径,默认为WEB_ROOT/WEB-INF目录 - * - * @param viewPath 视图路径,如:/WEB-INF/views - */ - public static synchronized void viewPath(final String viewPath) { - if(null != viewPath && viewPath.startsWith("/")){ - VIEW_PATH = viewPath; - } - } - - /** - * 设置视图默认后缀名,默认为.jsp - * - * @param viewExt 视图后缀,如:.html .vm - */ - public static synchronized void viewExt(final String viewExt) { - if(null != viewExt && viewExt.startsWith(".")){ - VIEW_EXT = viewExt; - } - } - - /** - * 同事设置视图所在目录和视图后缀名 - * - * @param viewPath 视图路径,如:/WEB-INF/views - * @param viewExt 视图后缀,如:.html .vm - */ - public static synchronized void view(final String viewPath, final String viewExt) { - viewPath(viewPath); - viewExt(viewExt); - } - - /** - * 设置框架静态文件所在文件夹 - * - * @param folder - */ - public static synchronized void staticFolder(final String ... folder) { - STATIC_FOLDER = folder; - } - - /** - * 动态设置全局初始化类 - * - * @param clazz 全局初始化Class - */ - public static synchronized void app(Class clazz){ - try { - BladeBase.bladeApplication = clazz.newInstance(); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - /** - * 动态设置全局初始化类 - * - * @param bladeApplication 全局初始化bladeApplication - */ - public static synchronized void app(BladeApplication bladeApplication){ - BladeBase.bladeApplication = bladeApplication; - } - - /** - * 设置404视图页面 - * - * @param view404 404视图页面 - */ - public static synchronized void view404(final String view404){ - BladeBase.VIEW_404 = view404; - } - - /** - * 设置500视图页面 - * - * @param view500 500视图页面 - */ - public static synchronized void view500(final String view500){ - BladeBase.VIEW_500 = view500; - } - - /** - * 设置web根目录 - * - * @param webRoot web根目录物理路径 - */ - public static synchronized void webRoot(final String webRoot){ - BladeBase.WEB_ROOT = webRoot; - } - - /** - * 设置系统是否以debug方式运行 - * @param isdebug true:是,默认true;false:否 - */ - public static synchronized void debug(boolean isdebug){ - BladeBase.DEBUG = isdebug; - } - - /**--------------------SET CONST:END-------------------------*/ - - - - /**--------------------GET CONST:START-------------------------*/ - - /** - * @return 返回Blade要扫描的基础包 - */ - public static String[] defaultRoutes(){ - return packageMap.get(PackageNames.basepackge); - } - - /** - * @return 返回路由包数组 - */ - public static String[] routes(){ - return packageMap.get(PackageNames.route); - } - - /** - * @return 返回拦截器包数组,只有一个元素 这里统一用String[] - */ - public static String[] interceptor(){ - return packageMap.get(PackageNames.interceptor); - } - - /** - * @return 返回视图存放路径 - */ - public static String viewPath(){ - return VIEW_PATH; - } - - /** - * @return 返回系统默认字符编码 - */ - public static String encoding(){ - return ENCODING; - } - - /** - * @return 返回balde启动端口 - */ - public static String viewExt(){ - return VIEW_EXT; - } - - /** - * @return 返回404视图 - */ - public static String view404(){ - return BladeBase.VIEW_404; - } - - /** - * @return 返回500视图 - */ - public static String view500(){ - return BladeBase.VIEW_500; - } - - /** - * @return 返回webroot路径 - */ - public static String webRoot(){ - return BladeBase.WEB_ROOT; - } - - /** - * @return 返回系统是否以debug方式运行 - */ - public static boolean debug(){ - return BladeBase.DEBUG; - } - - /** - * @return 返回静态资源目录 - */ - public static String[] staticFolder(){ - return BladeBase.STATIC_FOLDER; - } - - /** - * @return 返回BladeApplication对象 - */ - public static BladeApplication application(){ - return bladeApplication; - } - - /**--------------------GET CONST:END-------------------------*/ - - - /**----------------------jetty:START-------------------------*/ - - /** - * 设置jetty启动端口 - * - * @param port 端口号,范围在0~65535之间,默认为9000 - */ - public static synchronized void port(int port){ - if(port > 0 && port < 65535){ - PORT = port; - } - } - - /** - * 运行jetty服务 - * - * @param port 端口号,范围在0~65535之间,默认为9000 - * @param host host,默认为本机;127.0.0.1/localhost - * @param context context,应用上下文,默认为"/" - */ - public static void run(Integer port, String host, String context) { - PORT = port; - BladeServer.run(port, host, context); - } - - /** - * 运行jetty服务 - */ - public static void run() { - run(PORT, null, null); - } - - /** - * 运行jetty服务并设置主机 - * - * @param host host,默认为本机;127.0.0.1/localhost - */ - public static void run(String host) { - run(PORT, host, null); - } - - /** - * 运行jetty服务并设置端口 - * - * @param port 端口号,范围在0~65535之间,默认为9000 - */ - public static void run(Integer port) { - run(port, null, null); - } - - public static void run(Class clazz, Integer port) { - if(null != clazz){ - app(clazz); - } - run(port, null, null); - } - - /** - * 运行jetty服务并设置端口和主机 - * - * @param host host,默认为本机;127.0.0.1/localhost - * @param port 端口号,范围在0~65535之间,默认为9000 - */ - public static void run(String host, Integer port) { - run(port, host, null); - } - /**----------------------jetty:END-------------------------*/ - - /** - * 手动注册一个对象到ioc容器中 - * - * @param object 要注册的object - */ - public static synchronized void register(Object object){ - container.registBean(object); - } - - static synchronized void init() { - BladeBase.IS_INIT = true; - } - -} \ No newline at end of file diff --git a/blade-core/src/main/java/blade/BladeFilter.java b/blade-core/src/main/java/blade/BladeFilter.java deleted file mode 100644 index 690d38e2e..000000000 --- a/blade-core/src/main/java/blade/BladeFilter.java +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade; - -import java.io.IOException; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import blade.kit.log.Logger; -import blade.route.RouteMatcherBuilder; - -/** - * blade核心过滤器,mvc总线 - * 匹配所有请求过滤 - * - * @author biezhi - * @since 1.0 - */ -public class BladeFilter implements Filter { - - private static final Logger LOGGER = Logger.getLogger(BladeFilter.class); - - /** - * blade全局初始化类 - */ - private static final String APPLCATION_CLASS = "applicationClass"; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - - // 防止重复初始化 - if(!Blade.IS_INIT){ - - BladeBase.webRoot(filterConfig.getServletContext().getRealPath("/")); - - BladeWebContext.servletContext(filterConfig.getServletContext()); - - final BladeApplication application = getApplication(filterConfig); - application.init(); - Blade.app(application); - - // 构建所有路由 - RequestHandler.routeMatcher = RouteMatcherBuilder.building(); - - // 全局初始化 - IocApplication.init(); - - application.contextInitialized(BladeWebContext.servletContext()); - - LOGGER.info("blade init complete!"); - BladeBase.init(); - } - - } - - /** - * 获取全局初始化对象,初始化应用 - * - * @param filterConfig 过滤器配置对象 - * @return 一个全局初始化对象 - * @throws ServletException - */ - private BladeApplication getApplication(FilterConfig filterConfig) throws ServletException { - try { - String applicationClassName = filterConfig.getInitParameter(APPLCATION_CLASS); - if(!BladeBase.runJetty && null != applicationClassName){ - Class applicationClass = Class.forName(applicationClassName); - return (BladeApplication) applicationClass.newInstance(); - } - return BladeBase.bladeApplication; - } catch (Exception e) { - throw new ServletException(e); - } - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException{ - - HttpServletRequest httpRequest = (HttpServletRequest) request; - HttpServletResponse httpResponse = (HttpServletResponse) response; - - httpRequest.setCharacterEncoding(BladeBase.encoding()); - httpResponse.setCharacterEncoding(BladeBase.encoding()); - - /** - * 是否被RequestHandler执行 - */ - boolean isHandler = RequestHandler.single().handler(httpRequest, httpResponse); - if(!isHandler && !httpResponse.isCommitted()){ - chain.doFilter(httpRequest, httpResponse); - } - } - - @Override - public void destroy() { - IocApplication.destroy(); - LOGGER.info("blade destroy!"); - } - -} diff --git a/blade-core/src/main/java/blade/BladeWebContext.java b/blade-core/src/main/java/blade/BladeWebContext.java deleted file mode 100644 index 0b9b9300a..000000000 --- a/blade-core/src/main/java/blade/BladeWebContext.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade; - -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import blade.servlet.Request; -import blade.servlet.Response; -import blade.servlet.Session; - -/** - * 全局的WeContext - * - * @author biezhi - * @since 1.0 - */ -public final class BladeWebContext { - - /** - * 当前线程的Request对象 - */ - private static ThreadLocal currentRequest = new ThreadLocal(); - - /** - * 当前线程的Response对象 - */ - private static ThreadLocal currentResponse = new ThreadLocal(); - - /** - * ServletContext对象,在应用初始化时创建 - */ - private static ServletContext servletContext; - - private BladeWebContext(){} - - /** - * @return 返回当前线程的Request对象 - */ - public static Request request() { - return currentRequest.get(); - } - - /** - * @return 返回当前线程的HttpServletRequest对象 - */ - public static HttpServletRequest servletRequest() { - return request().servletRequest(); - } - - /** - * @return 返回当前线程的Response对象 - */ - public static Response response() { - return currentResponse.get(); - } - - /** - * @return 返回当前线程的HttpServletResponse对象 - */ - public static HttpServletResponse servletResponse() { - return response().servletResponse(); - } - - /** - * @return 返回当前线程的Session对象 - */ - public static Session session() { - return request().session(); - } - - /** - * 设置ServletContext - * - * @param servletContext ServletContext对象 - */ - public static void servletContext(ServletContext servletContext) { - BladeWebContext.servletContext = servletContext; - } - - /** - * @return 返回当前线程的ServletContext对象 - */ - public static ServletContext servletContext() { - return servletContext; - } - - /** - * 设置context对象到ActionContext中 - * - * @param request HttpServletRequest对象 - * @param response HttpServletResponse对象 - */ - public static void put(Request request, Response response) { - currentRequest.set(request); - currentResponse.set(response); - } - - /** - * 移除当前线程的Request、Response对象 - */ - public static void remove(){ - currentRequest.remove(); - currentResponse.remove(); - } - -} \ No newline at end of file diff --git a/blade-core/src/main/java/blade/IocApplication.java b/blade-core/src/main/java/blade/IocApplication.java deleted file mode 100644 index bafdfa520..000000000 --- a/blade-core/src/main/java/blade/IocApplication.java +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade; - -import java.util.Set; - -import blade.BladeBase.PackageNames; -import blade.ioc.Container; -import blade.ioc.DefaultContainer; -import blade.kit.log.Logger; -import blade.kit.resource.ClassPathClassReader; -import blade.kit.resource.ClassReader; - -/** - * IOC容器初始化类 - *

- * 用于初始化ioc对象 - *

- * - * @author biezhi - * @since 1.0 - */ -public final class IocApplication { - - private static final Logger LOGGER = Logger.getLogger(IocApplication.class); - - /** - * IOC容器,单例获取默认的容器实现 - */ - static final Container container = DefaultContainer.single(); - - /** - * 类读取对象,加载class - */ - static final ClassReader classReader = new ClassPathClassReader(); - - public static void init(){ - - // 初始化全局配置类 - initApp(); - - // 初始化ioc容器 - initIOC(); - - // 初始化注入 - container.initWired(); - } - - private static void initApp(){ - container.registBean(Blade.application()); - } - - /** - * 初始化IOC容器,加载ioc包的对象 - * 要配置符合ioc的注解的类才会被加载 - * - */ - private static void initIOC() { - String[] iocPackages = BladeBase.packageMap.get(PackageNames.ioc); - if(null != iocPackages && iocPackages.length > 0){ - for(String packageName : iocPackages){ - registerBean(packageName); - } - } - } - - /** - * 注册一个包下的所有对象 - * - * @param packageName 包名称 - */ - private static void registerBean(String packageName) { - - // 是否递归扫描 - boolean recursive = false; - if (packageName.endsWith(".*")) { - packageName = packageName.substring(0, packageName.length() - 2); - recursive = true; - } - - // 扫描包下所有class - Set> classes = classReader.getClass(packageName, recursive); - for (Class clazz : classes) { - // 注册带有Component和Service注解的类 - if (container.isRegister(clazz.getAnnotations())) { - container.registBean(clazz); - } - } - - if(Blade.debug()){ - Set beanNames = container.getBeanNames(); - for(String beanName : beanNames){ - LOGGER.debug("Load The Class:" + beanName); - } - } - } - - /** - * 销毁 - */ - public static void destroy() { - // 清空ioc容器 - container.removeAll(); - } - -} diff --git a/blade-core/src/main/java/blade/RequestHandler.java b/blade-core/src/main/java/blade/RequestHandler.java deleted file mode 100644 index 36e99c370..000000000 --- a/blade-core/src/main/java/blade/RequestHandler.java +++ /dev/null @@ -1,326 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade; - -import java.lang.reflect.Method; -import java.util.List; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import blade.exception.BladeException; -import blade.ioc.Container; -import blade.ioc.DefaultContainer; -import blade.ioc.Scope; -import blade.kit.PathKit; -import blade.kit.ReflectKit; -import blade.kit.log.Logger; -import blade.render.ModelAndView; -import blade.route.DefaultRouteMatcher; -import blade.route.HttpMethod; -import blade.route.RouteMatcher; -import blade.servlet.Request; -import blade.servlet.Response; -import blade.wrapper.RequestResponseBuilder; -import blade.wrapper.RequestWrapper; -import blade.wrapper.ResponseWrapper; - -/** - * 请求执行的Handler - *

- * 拦截器所有blade的请求,处理route and interceptor - *

- * - * @author biezhi - * @since 1.0 - */ -public class RequestHandler { - - private static final Logger LOGGER = Logger.getLogger(RequestHandler.class); - - private static final String ACCEPT_TYPE_REQUEST_MIME_HEADER = "Accept"; - - /** - * 服务器500错误时返回的HTML - */ - private static final String INTERNAL_ERROR = "

500 Internal Error

"; - - private final static Container container = DefaultContainer.single(); - - /** - * 路由处理器,查找请求过来的URL - */ - static DefaultRouteMatcher routeMatcher; - - private RequestHandler(){} - - public static RequestHandler single() { - return RequestHandlerHolder.single; - } - - /** - * 单例的RequestHandler - * - * @author biezhi - * @since 1.0 - * - */ - private static class RequestHandlerHolder { - private static final RequestHandler single = new RequestHandler(); - } - - /** - * handler执行方法 - * - * @param httpRequest HttpServletRequest请求对象 - * @param httpResponse HttpServletResponse响应对象 - * @return 是否拦截到请求 - */ - boolean handler(HttpServletRequest httpRequest, HttpServletResponse httpResponse){ - - // http方法, GET/POST ... - String method = httpRequest.getMethod(); - - // 请求的uri - String uri = PathKit.getRelativePath(httpRequest); - - // 如果是静态资源则交给filter处理 - if(null != Blade.staticFolder() && Blade.staticFolder().length > 0){ - if(!filterStaticFolder(uri)){ - return false; - } - } - - String acceptType = httpRequest.getHeader(ACCEPT_TYPE_REQUEST_MIME_HEADER); - - // 响应体 - String bodyContent = null; - Request request = null; - - // 构建一个包装后的response - Response response = RequestResponseBuilder.build(httpResponse); - - // 创建RequestWrapper And RequestWrapper - RequestWrapper requestWrapper = new RequestWrapper(); - ResponseWrapper responseWrapper = new ResponseWrapper(response); - - if(Blade.debug()){ - LOGGER.debug("Request : " + method + "\t" + uri); - } - - HttpMethod httpMethod = HttpMethod.valueOf(method); - - try { - - // 执行before拦截 - before(requestWrapper, responseWrapper, httpRequest, uri, acceptType); - - // 查找用户请求的uri - RouteMatcher match = routeMatcher.findRouteMatcher(httpMethod, uri, acceptType); - - // 如果找到 - if (match != null) { - - Class target = match.getTarget(); - - Object targetObject = container.getBean(target, Scope.SINGLE); - - // 要执行的路由方法 - Method execMethod = match.getExecMethod(); - - if(null != requestWrapper.getDelegate()){ - request = requestWrapper.getDelegate(); - request.initRequest(match); - } else { - request = RequestResponseBuilder.build(match, httpRequest); - } - requestWrapper.setDelegate(request); - - BladeWebContext.put(requestWrapper, responseWrapper); - - // 执行route方法 - Object result = executeMethod(targetObject, execMethod, requestWrapper, responseWrapper); - - // 执行after拦截 - after(requestWrapper, responseWrapper, httpRequest, uri, acceptType); - - if(null != result){ - render(responseWrapper, result); - } - return true; - } else { - // 没有找到 - response.render404(uri); - } - } catch (BladeException bex) { - LOGGER.error(bex.getMessage()); - httpResponse.setStatus(500); - if (bex.getMessage() != null) { - bodyContent = bex.getMessage(); - } else { - bodyContent = INTERNAL_ERROR; - } - } - boolean consumed = bodyContent != null; - if (consumed) { - // 写入内容到浏览器 - if (!httpResponse.isCommitted()) { - response.render500(bodyContent); - return true; - } - } - return false; - - } - - /** - * 前置事件,在route执行前执行 - * 这里如果执行则Request和Response都会被创建好 - * - * @param requestWrapper RequestWrapper对象,包装了Request对象 - * @param responseWrapper ResponseWrapper对象,包装了Response对象 - * @param httpRequest HttpServletRequest请求对象,用于构建Request - * @param uri 请求的URI - * @param acceptType 请求头过滤 - */ - private void before(RequestWrapper requestWrapper, ResponseWrapper responseWrapper, HttpServletRequest httpRequest, final String uri, final String acceptType){ - - List matchSet = routeMatcher.findInterceptor(HttpMethod.BEFORE, uri, acceptType); - - for (RouteMatcher filterMatch : matchSet) { - - Class target = filterMatch.getTarget(); - - Object targetObject = container.getBean(target, Scope.SINGLE); - - Method execMethod = filterMatch.getExecMethod(); - - Request request = RequestResponseBuilder.build(filterMatch, httpRequest); - requestWrapper.setDelegate(request); - - executeMethod(targetObject, execMethod, requestWrapper, responseWrapper); - - } - } - - /** - * 后置事件,在route执行后执行 - * - * @param requestWrapper RequestWrapper对象,包装了Request对象 - * @param responseWrapper ResponseWrapper对象,包装了Response对象 - * @param httpRequest HttpServletRequest请求对象,用于构建Request - * @param uri 请求的URI - * @param acceptType 请求头过滤 - */ - private String after(RequestWrapper requestWrapper, ResponseWrapper responseWrapper, HttpServletRequest httpRequest, final String uri, final String acceptType){ - List matchSet = routeMatcher.findInterceptor(HttpMethod.AFTER, uri, acceptType); - - String bodyContent = null; - for (RouteMatcher filterMatch : matchSet) { - Class target = filterMatch.getTarget(); - - Object targetObject = container.getBean(target, Scope.SINGLE); - - Method execMethod = filterMatch.getExecMethod(); - - if (requestWrapper.getDelegate() == null) { - Request request = RequestResponseBuilder.build(filterMatch, httpRequest); - requestWrapper.setDelegate(request); - } else { - requestWrapper.initRequest(filterMatch); - } - - executeMethod(targetObject, execMethod, requestWrapper, responseWrapper); - - String bodyAfterFilter = responseWrapper.getDelegate().body(); - if (bodyAfterFilter != null) { - bodyContent = bodyAfterFilter; - } - } - - return bodyContent; - } - - /** - * 获取方法内的参数 - * - * @param request Request对象,用于注入到method参数列表中 - * @param response Response对象,用于注入到method参数列表中 - * @param params params参数列表 - * @return 返回生成后的参数数组 - */ - private Object[] getArgs(Request request, Response response, Class[] params){ - - int len = params.length; - Object[] args = new Object[len]; - - for(int i=0; i paramTypeClazz = params[i]; - if(paramTypeClazz.getName().equals(Request.class.getName())){ - args[i] = request; - } - if(paramTypeClazz.getName().equals(Response.class.getName())){ - args[i] = response; - } - } - - return args; - } - - /** - * 执行路由方法 - * @param object 方法的实例,即该方法所在类的对象 - * @param method 要执行的method - * @param request Request对象,作为参数注入 - * @param response Response对象,作为参数注入 - * @return 返回方法执行后的返回值 - */ - private Object executeMethod(Object object, Method method, Request request, Response response){ - int len = method.getParameterTypes().length; - if(len > 0){ - Object[] args = getArgs(request, response, method.getParameterTypes()); - return ReflectKit.invokeMehod(object, method, args); - } else { - return ReflectKit.invokeMehod(object, method); - } - } - - /** - * 渲染视图 - * - * @param response - * @param result - * @return - */ - private Object render(Response response, Object result){ - if(result instanceof String){ - response.render(result.toString()); - } else if(result instanceof ModelAndView){ - response.render( (ModelAndView) result ); - } - return null; - } - - private boolean filterStaticFolder(String uri){ - int len = Blade.staticFolder().length; - for(int i=0; ibiezhi - * @since 1.0 - */ -public abstract class AbstractBeanFactory { - - protected Container container = DefaultContainer.single(); - - public abstract Object getBean(String className); - - public abstract Object getBean(Class clazz); - - public boolean resetBean(Class clazz, Object object){ - System.out.println("resetBean object=" + object); - if(null != clazz.getInterfaces() && null != object){ - container.removeBean(clazz); - container.getBeanMap().put(clazz.getName(), object); - } - return true; - } - - public Set getBeanNames(){ - return container.getBeanNames(); - } - - public Collection getBeans(){ - return container.getBeans(); - } - - public List getBeansByAnnotation(Class annotation){ - return container.getBeansByAnnotation(annotation); - } - - public List> getClassesByAnnotation(Class annotation){ - return container.getClassesByAnnotation(annotation); - } - -} \ No newline at end of file diff --git a/blade-core/src/main/java/blade/ioc/Container.java b/blade-core/src/main/java/blade/ioc/Container.java deleted file mode 100644 index 3517f98a4..000000000 --- a/blade-core/src/main/java/blade/ioc/Container.java +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.ioc; - -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * IOC容器顶层接口 - * - * @author biezhi - * @since 1.0 - */ -public interface Container { - - /** - * 根据bean名称和对象作用于获取一个bean对象 - * - * @param name bean名称,可以是类名 - * @param scope 对象作用域,单例或者每次都创建 - * @return 一个bean对象 - */ - Object getBean(String name, Scope scope); - - /** - * 根据class和对象作用于获取一个bean对象 - * - * @param type class类型 - * @param scope 对象作用域,单例或者每次都创建 - * @return 一个bean对象 - */ - Object getBean(Class type, Scope scope); - - /** - * @return 返回所有bean的名称集合 - */ - Set getBeanNames(); - - /** - * @return 返回所有bean的集合 - */ - Collection getBeans(); - - /** - * 根据注解获取ioc容器中匹配的bean class集合 - * - * @param annotation annotation class类型 - * @return 返回符合annotation class类型的所有class - */ - List> getClassesByAnnotation(Class annotation); - - /** - * 根据注解获取ioc容器中匹配的bean对象集合 - * - * @param annotation annotation class类型 - * @return 返回符合annotation class类型的所有bean - */ - List getBeansByAnnotation(Class annotation); - - /** - * 判断是否存在一个bean,根据class类型 - * - * @param clazz 类的class类型 - * @return true:存在,false:不存在 - */ - boolean hasBean(Class clazz); - - /** - * 判断是否存在一个bean,根据bena name - * - * @param name bean的名称,一般是class名称 - * @return true:存在,false:不存在 - */ - boolean hasBean(String name); - - /** - * 根据名称从ioc容器中移除一个bean对象 - * - * @param name 要移除的bean对象名称 - * @return true:成功,false:失败 - */ - boolean removeBean(String name); - - /** - * 根据名称从ioc容器中移除一个bean对象 - * - * @param clazz 要移除的bean class类型 - * @return true:成功,false:失败 - */ - boolean removeBean(Class clazz); - - /** - * @return 清空容器 - */ - boolean removeAll(); - - /** - * 获取annotations中的注解是否可以注册进入ioc容器 - * - * @param annotations annotations要检测的annotation数组 - * @return true:可以注册,false:不可以注册 - */ - boolean isRegister(Annotation[] annotations); - - /** - * 注册一个class类型的bean到容器中 - * - * @param clazz 要注册的class类型 - * @return 返回注册后的bean对象 - */ - Object registBean(Class clazz); - - /** - * 注册一个对象到bean容器中 - * - * @param object 要注册的object - * @return 返回注册后的Bean实例 - */ - Object registBean(Object object); - - /** - * 注册一个class集合进入ioc容器 - * - * @param classes 要注册的class集合 - */ - void registBean(Set> classes); - - /** - * 初始化IOC - */ - void initWired(); - - /** - * @return 返回ioc容器中的所有bean对象的K,V - */ - Map getBeanMap(); - -} \ No newline at end of file diff --git a/blade-core/src/main/java/blade/ioc/DefaultContainer.java b/blade-core/src/main/java/blade/ioc/DefaultContainer.java deleted file mode 100644 index 466d18809..000000000 --- a/blade-core/src/main/java/blade/ioc/DefaultContainer.java +++ /dev/null @@ -1,331 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.ioc; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import blade.annotation.Component; -import blade.annotation.Inject; -import blade.annotation.Path; -import blade.kit.CloneKit; -import blade.kit.CollectionKit; -import blade.kit.ReflectKit; -import blade.kit.log.Logger; - -/** - * 默认的IOC容器实现 - * - * @author biezhi - * @since 1.0 - */ -public class DefaultContainer implements Container { - - private static final Logger LOGGER = Logger.getLogger(DefaultContainer.class); - - /** - * 保存所有bean对象 - */ - private static final Map beansMap = CollectionKit.newConcurrentHashMap(); - - /** - * 保存所有注解的class - */ - private static final Map, List> annotationMap = CollectionKit.newConcurrentHashMap(); - - private DefaultContainer() { - } - - public static DefaultContainer single() { - return DefaultContainerHoder.single; - } - - private static class DefaultContainerHoder { - private static final DefaultContainer single = new DefaultContainer(); - } - - public Map getBeanMap() { - return beansMap; - } - - @Override - public Object getBean(String name, Scope scope) { - Object obj = beansMap.get(name); - if(null != scope && scope == Scope.PROTOTYPE){ - try { - return CloneKit.deepClone(obj); - } catch (Exception e) { - LOGGER.error("克隆对象失败," + e.getMessage()); - } - } - return obj; - } - - @Override - public Object getBean(Class type, Scope scope) { - Iterator it = beansMap.values().iterator(); - while (it.hasNext()) { - Object obj = it.next(); - if (type.isAssignableFrom(obj.getClass())) { - if(null != scope && scope == Scope.PROTOTYPE){ - try { - return CloneKit.deepClone(obj); - } catch (Exception e) { - LOGGER.error("克隆对象失败," + e.getMessage()); - } - } else { - return obj; - } - } - } - return null; - } - - @Override - public Set getBeanNames() { - return beansMap.keySet(); - } - - @Override - public Collection getBeans() { - return beansMap.values(); - } - - @Override - public boolean hasBean(Class clz) { - if (null != this.getBean(clz, Scope.SINGLE)) { - return true; - } - return false; - } - - @Override - public boolean hasBean(String name) { - if (null != this.getBean(name, Scope.SINGLE)) { - return true; - } - return false; - } - - @Override - public boolean removeBean(String name) { - Object object = beansMap.remove(name); - return (null != object); - } - - @Override - public boolean removeBean(Class clazz) { - Object object = beansMap.remove(clazz.getName()); - return (null != object); - } - - /** - * 注册一个bean对象到容器里 - * - * @param clazz 要注册的class - * @return 返回注册后的bean对象 - */ - @Override - public Object registBean(Class clazz) { - - String name = clazz.getCanonicalName(); - - Object object = null; - - //非抽象类、接口 - if (!Modifier.isAbstract(clazz.getModifiers()) && !clazz.isInterface()) { - - object = ReflectKit.newInstance(clazz); - - put(name, object); - //实现的接口对应存储 - if(clazz.getInterfaces().length > 0){ - put(clazz.getInterfaces()[0].getCanonicalName(), object); - } - - //带有annotation - if(null != clazz.getDeclaredAnnotations()){ - putAnnotationMap(clazz, object); - } - } - return object; - } - - /** - * bean容器存储 - * - * @param name 要进入IOC容器的bean名称 - * @param object 要进入IOC容器的bean对象 - */ - private void put(String name, Object object){ - if(null == beansMap.get(name)){ - beansMap.put(name, object); - } - } - - /** - * 给annotationMap添加元素 - * - * @param clazz 要注入的class类型 - * @param object 注册的bean对象 - */ - private void putAnnotationMap(Class clazz, Object object){ - Annotation[] annotations = clazz.getAnnotations(); - for(Annotation annotation : annotations){ - if(null != annotation){ - List listObject = annotationMap.get(annotation.annotationType()); - if(CollectionKit.isEmpty(listObject)){ - listObject = CollectionKit.newArrayList(); - } - listObject.add(object); - put(annotation.annotationType(), listObject); - } - } - } - - /** - * annotationBean容器存储 - * - * @param clazz 允许注册的Annotation类型 - * @param listObject 要注入的对象列表 - */ - private void put(Class clazz, List listObject){ - if(null == annotationMap.get(clazz)){ - annotationMap.put(clazz, listObject); - } - } - - /** - * 初始化注入 - */ - @Override - public void initWired() { - Iterator it = beansMap.values().iterator(); - try { - while (it.hasNext()) { - - Object obj = it.next(); - - // 所有字段 - Field[] fields = obj.getClass().getDeclaredFields(); - for (Field field : fields) { - - // 需要注入的字段 - Inject inject = field.getAnnotation(Inject.class); - if (null != inject) { - - // 要注入的字段 - Object injectField = this.getBean(field.getType(), Scope.SINGLE); - - // 指定装配的类 - if (inject.value() != Class.class) { - injectField = this.getBean(inject.value(), Scope.SINGLE); - // 容器有该类 - if (null == injectField) { - injectField = this.registBean(inject.value()); - } - } else{ - // 没有指定装配class, 容器没有该类,则创建一个对象放入容器 - if (null == injectField) { - injectField = this.registBean(field.getType()); - } - } - if (null == injectField) { - throw new RuntimeException("Unable to load " + field.getType().getCanonicalName() + "!"); - } - boolean accessible = field.isAccessible(); - field.setAccessible(true); - field.set(obj, injectField); - field.setAccessible(accessible); - } - } - } - } catch (SecurityException e) { - LOGGER.error(e.getMessage()); - } catch (IllegalArgumentException e) { - LOGGER.error(e.getMessage()); - } catch (IllegalAccessException e) { - LOGGER.error(e.getMessage()); - } - } - - /** - * 判断是否是可以注册的bean - * - * @param annotations 注解类型 - * @return true:可以注册 false:不可以注册 - */ - @Override - public boolean isRegister(Annotation[] annotations) { - if (null == annotations || annotations.length == 0) { - return false; - } - for (Annotation annotation : annotations) { - if (annotation instanceof Component || annotation instanceof Path) { - return true; - } - } - return false; - } - - @Override - public List> getClassesByAnnotation(Class annotation) { - List objectList = getBeansByAnnotation(annotation); - if(!CollectionKit.isEmpty(objectList)){ - List> classList = CollectionKit.newArrayList(objectList.size()); - for(Object object : objectList){ - classList.add(object.getClass()); - } - return classList; - } - return null; - } - - @Override - public List getBeansByAnnotation(Class annotation) { - return annotationMap.get(annotation); - } - - @Override - public void registBean(Set> classes) { - if(!CollectionKit.isEmpty(classes)){ - for(Class clazz : classes){ - this.registBean(clazz); - } - } - } - - @Override - public Object registBean(Object object) { - String name = object.getClass().getName(); - put(name, object); - return object; - } - - @Override - public boolean removeAll() { - beansMap.clear(); - annotationMap.clear(); - return true; - } - -} \ No newline at end of file diff --git a/blade-core/src/main/java/blade/ioc/package-info.java b/blade-core/src/main/java/blade/ioc/package-info.java deleted file mode 100644 index 3b4f02639..000000000 --- a/blade-core/src/main/java/blade/ioc/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * blade依赖注入包 - */ -package blade.ioc; \ No newline at end of file diff --git a/blade-core/src/main/java/blade/kit/package-info.java b/blade-core/src/main/java/blade/kit/package-info.java deleted file mode 100644 index 66554ccaf..000000000 --- a/blade-core/src/main/java/blade/kit/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * blade工具包 - */ -package blade.kit; \ No newline at end of file diff --git a/blade-core/src/main/java/blade/package-info.java b/blade-core/src/main/java/blade/package-info.java deleted file mode 100644 index bd5612aa1..000000000 --- a/blade-core/src/main/java/blade/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * blade - */ -package blade; \ No newline at end of file diff --git a/blade-core/src/main/java/blade/plugin/package-info.java b/blade-core/src/main/java/blade/plugin/package-info.java deleted file mode 100644 index 60d209844..000000000 --- a/blade-core/src/main/java/blade/plugin/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * blade插件包 - */ -package blade.plugin; \ No newline at end of file diff --git a/blade-core/src/main/java/blade/render/JspRender.java b/blade-core/src/main/java/blade/render/JspRender.java deleted file mode 100644 index bfb439a41..000000000 --- a/blade-core/src/main/java/blade/render/JspRender.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.render; - -import java.io.IOException; -import java.util.Map; -import java.util.Set; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import blade.Blade; -import blade.BladeWebContext; - -/** - * JSP渲染引擎,默认的渲染器 - * - * @author biezhi - * @since 1.0 - */ -public final class JspRender extends Render { - - private JspRender() { - } - - /** - * 视图渲染 - */ - public Object render(final String view){ - try { - HttpServletRequest servletRequest = BladeWebContext.servletRequest(); - HttpServletResponse servletResponse = BladeWebContext.servletResponse(); - - // 设置编码 - servletRequest.setCharacterEncoding(Blade.encoding()); - servletResponse.setCharacterEncoding(Blade.encoding()); - - // 构造jsp地址 - String realPath = disposeView(view); - - // 跳转到页面 - servletRequest.getRequestDispatcher(realPath).forward(servletRequest, servletResponse); - - } catch (ServletException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - /** - * ModelAndView渲染 - */ - public Object render(ModelAndView modelAndView){ - try { - HttpServletRequest servletRequest = BladeWebContext.servletRequest(); - HttpServletResponse servletResponse = BladeWebContext.servletResponse(); - - String realPath = disposeView(modelAndView.getView()); - - Map model = modelAndView.getModel(); - - if (null != model && !model.isEmpty()) { - Set keys = model.keySet(); - for (String key : keys) { - servletRequest.setAttribute(key, model.get(key)); - } - } - servletRequest.getRequestDispatcher(realPath).forward(servletRequest, servletResponse); - } catch (ServletException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - - public static JspRender single() { - return JspRenderHolder.single; - } - - private static class JspRenderHolder { - private static final JspRender single = new JspRender(); - } - -} diff --git a/blade-core/src/main/java/blade/render/Render.java b/blade-core/src/main/java/blade/render/Render.java deleted file mode 100644 index 04145010c..000000000 --- a/blade-core/src/main/java/blade/render/Render.java +++ /dev/null @@ -1,257 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.render; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; - -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import blade.Blade; -import blade.BladeWebContext; -/** - * 渲染器抽象类 - * - * @author biezhi - * @since 1.0 - */ -public abstract class Render { - - static final String VIEW_NOTFOUND = "

404 %s

"; - - public void render404(String viewName){ - render404(null, viewName); - } - - /** - * 404视图 - * - * @param httpResponse HttpServletResponse对象 - * @param viewName 视图名称 - */ - public void render404(HttpServletResponse httpResponse, String viewName){ - try { - String view404 = Blade.view404(); - if(null != view404){ - ModelAndView modelAndView = new ModelAndView(view404); - modelAndView.add("viewName", viewName); - render(modelAndView); - } else { - if(null == httpResponse){ - httpResponse = BladeWebContext.servletResponse(); - } - - httpResponse.setContentType("text/html; charset=utf-8"); - httpResponse.setStatus(404); - ServletOutputStream outputStream = httpResponse.getOutputStream(); - outputStream.print(String.format(VIEW_NOTFOUND, viewName + " Not Found")); - outputStream.flush(); - outputStream.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * 渲染500视图 - * - * @param bodyContent 错误消息 - */ - public void render500(String bodyContent){ - try { - - String view500 = Blade.view500(); - if(null != view500){ - ModelAndView modelAndView = new ModelAndView(view500); - modelAndView.add("body", bodyContent); - render(modelAndView); - } else { - HttpServletResponse httpResponse = BladeWebContext.servletResponse(); - - httpResponse.setContentType("text/html; charset=utf-8"); - ServletOutputStream outputStream = httpResponse.getOutputStream(); - - outputStream.print(bodyContent.toString()); - outputStream.flush(); - outputStream.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * 输出json - * - * @param json json内容 - */ - public void json(String json){ - if(null != json){ - HttpServletResponse response = BladeWebContext.servletResponse(); - HttpServletRequest request = BladeWebContext.servletRequest(); - - response.setHeader("Cache-Control", "no-cache"); - String userAgent = request.getHeader("User-Agent"); - if (userAgent.contains("MSIE")) { - response.setContentType("text/html;charset=utf-8"); - } else { - response.setContentType("application/json;charset=utf-8"); - } - try { - request.setCharacterEncoding("utf-8"); - PrintWriter out = response.getWriter(); - out.print(json.toString()); - out.flush(); - out.close(); - } catch (UnsupportedEncodingException e1) { - e1.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - /** - * 输出text - * @param text text内容 - */ - public void text(String text){ - if(null != text){ - HttpServletResponse response = BladeWebContext.servletResponse(); - HttpServletRequest request = BladeWebContext.servletRequest(); - response.setHeader("Cache-Control", "no-cache"); - response.setContentType("text/plain;charset=utf-8"); - try { - request.setCharacterEncoding("utf-8"); - PrintWriter out = response.getWriter(); - out.print(text); - out.flush(); - out.close(); - } catch (UnsupportedEncodingException e1) { - e1.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - /** - * 输出xml - * @param xml xml内容 - */ - public void xml(String xml){ - if(null != xml){ - HttpServletResponse response = BladeWebContext.servletResponse(); - HttpServletRequest request = BladeWebContext.servletRequest(); - response.setHeader("Cache-Control", "no-cache"); - response.setContentType("text/xml;charset=utf-8"); - try { - request.setCharacterEncoding("utf-8"); - PrintWriter out = response.getWriter(); - out.print(xml); - out.flush(); - out.close(); - } catch (UnsupportedEncodingException e1) { - e1.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - /** - * 输出HTML - * @param html html内容 - */ - public void html(String html){ - if(null != html){ - HttpServletResponse response = BladeWebContext.servletResponse(); - HttpServletRequest request = BladeWebContext.servletRequest(); - response.setHeader("Cache-Control", "no-cache"); - response.setContentType("text/html;charset=utf-8"); - try { - request.setCharacterEncoding("utf-8"); - PrintWriter out = response.getWriter(); - out.print(html); - out.flush(); - out.close(); - } catch (UnsupportedEncodingException e1) { - e1.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - /** - * 输出javascript - * @param javascript js内容 - */ - public void javascript(String javascript){ - if(null != javascript){ - HttpServletResponse response = BladeWebContext.servletResponse(); - HttpServletRequest request = BladeWebContext.servletRequest(); - response.setHeader("Cache-Control", "no-cache"); - response.setContentType("text/javascript;charset=utf-8"); - try { - request.setCharacterEncoding("utf-8"); - PrintWriter out = response.getWriter(); - out.print(javascript); - out.flush(); - out.close(); - } catch (UnsupportedEncodingException e1) { - e1.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - /** - * 处理视图 - * @param view 视图名称 - * @return 返回取出多余"/"的全路径 - */ - String disposeView(String view){ - if(null != view){ - view = Blade.viewPath() + view; - view = view.replaceAll("[/]+", "/"); - if(!view.endsWith(Blade.viewExt())){ - view = view + Blade.viewExt(); - } - } - return view; - } - - /** - * 渲染方法 - * @param view 视图名称 - * @return null - */ - public abstract Object render(final String view); - - /** - * 渲染方法 - * @param modelAndView modelAndView对象 - * @return null - */ - public abstract Object render(ModelAndView modelAndView); - -} diff --git a/blade-core/src/main/java/blade/render/package-info.java b/blade-core/src/main/java/blade/render/package-info.java deleted file mode 100644 index a8e93cae8..000000000 --- a/blade-core/src/main/java/blade/render/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * blade视图渲染 - */ -package blade.render; \ No newline at end of file diff --git a/blade-core/src/main/java/blade/route/DefaultRouteMatcher.java b/blade-core/src/main/java/blade/route/DefaultRouteMatcher.java deleted file mode 100644 index cb2a988b7..000000000 --- a/blade-core/src/main/java/blade/route/DefaultRouteMatcher.java +++ /dev/null @@ -1,288 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.route; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import blade.Blade; -import blade.kit.MimeParse; -import blade.kit.StringKit; -import blade.kit.log.Logger; - -/** - * 默认的路由匹配器 - * - * @author biezhi - * @since 1.0 - */ -public class DefaultRouteMatcher { - - private static final Logger LOGGER = Logger.getLogger(DefaultRouteMatcher.class); - - // 存储所有路由 - private List routes; - // 存储所有拦截器 - private List interceptors; - - public DefaultRouteMatcher() { - routes = new ArrayList(); - interceptors = new ArrayList(); - } - - /** - * 查询是否有路由 - * - * @param httpMethod http请求方法,GET/POST - * @param uri 请求路径 - * @param acceptType 请求的acceptType - * @return 返回一个路由匹配对象 - */ - public RouteMatcher findRouteMatcher(HttpMethod httpMethod, String uri, String acceptType) { - - uri = uri.length() > 1 && uri.endsWith("/") ? uri.substring(0, uri.length() - 1) : uri; - - List routeEntries = this.findRouteMatcher(httpMethod, uri); - - RouteMatcher entry = findTargetWithGivenAcceptType(routeEntries, acceptType); - - return entry != null ? new RouteMatcher(entry.target, entry.execMethod, entry.httpMethod, entry.path, uri, acceptType) : null; - } - - /** - * 查询一个路由集合 - * - * @param httpMethod http请求方法,GET/POST - * @param path 请求路径 - * @param acceptType 请求的acceptType - * @return 返回一个路由匹配对象集合 - */ - public List findInterceptor(HttpMethod httpMethod, String uri, String acceptType) { - if(uri.length() > 1){ - uri = uri.endsWith("/") ? uri.substring(0, uri.length() - 1) : uri; - } - List matchSet = new ArrayList(); - List routeEntries = this.findInterceptor(httpMethod, uri); - - for (RouteMatcher routeEntry : routeEntries) { - if (acceptType != null) { - - String bestMatch = MimeParse.bestMatch(Arrays.asList(routeEntry.acceptType), acceptType); - - if (routeWithGivenAcceptType(bestMatch)) { - matchSet.add(routeEntry); - } - } else { - matchSet.add(routeEntry); - } - } - - return matchSet; - } - - /** - * 清空路由集合 - */ - public void clearRoutes() { - routes.clear(); - } - - /** - * 移除一个路由 - * - * @param path 移除路由的路径 - * @param httpMethod 移除路由的方法 - * @return true:移除成功,false:移除失败 - */ - public boolean removeRoute(String path, String httpMethod) { - if (StringKit.isEmpty(path)) { - throw new IllegalArgumentException("path cannot be null or blank"); - } - - if (StringKit.isEmpty(httpMethod)) { - throw new IllegalArgumentException("httpMethod cannot be null or blank"); - } - - HttpMethod method = HttpMethod.valueOf(httpMethod); - - return removeRoute(method, path); - } - - public boolean removeRoute(String path) { - if (StringKit.isEmpty(path)) { - throw new IllegalArgumentException("path cannot be null or blank"); - } - - return removeRoute((HttpMethod)null, path); - } - - /** - * 添加一个路由对象 - * - * @param target 路由目标执行的class - * @param execMethod 路由执行方法 - * @param url 路由url - * @param method 路由http方法 - * @param acceptType 路由acceptType - */ - public void addRoute(Class target, Method execMethod, String url, HttpMethod method, String acceptType) { - RouteMatcher entry = new RouteMatcher(); - entry.target = target; - entry.execMethod = execMethod; - entry.httpMethod = method; - entry.path = url; - entry.requestURI = url; - entry.acceptType = acceptType; - - if(Blade.debug()){ - LOGGER.debug("Add Route:" + entry); - } - - // 添加到路由集合 - routes.add(entry); - } - - /** - * 添加一个拦截器对象 - * - * @param target 路由目标执行的class - * @param execMethod 路由执行方法 - * @param url 路由url - * @param method 路由http方法 - * @param acceptType 路由acceptType - */ - public void addInterceptor(Class target, Method execMethod, String url, HttpMethod method, String acceptType) { - RouteMatcher entry = new RouteMatcher(); - entry.target = target; - entry.execMethod = execMethod; - entry.httpMethod = method; - entry.path = url; - entry.requestURI = url; - entry.acceptType = acceptType; - - if(Blade.debug()){ - LOGGER.debug("Add Interceptor:" + entry); - } - - // 添加到路由集合 - interceptors.add(entry); - } - - private Map getAcceptedMimeTypes(List routes) { - Map acceptedTypes = new HashMap(); - - for (RouteMatcher routeEntry : routes) { - if (!acceptedTypes.containsKey(routeEntry.acceptType)) { - acceptedTypes.put(routeEntry.acceptType, routeEntry); - } - } - - return acceptedTypes; - } - - private boolean routeWithGivenAcceptType(String bestMatch) { - return !MimeParse.NO_MIME_TYPE.equals(bestMatch); - } - - /** - * 查找所有匹配HttpMethod和path的路由 - * - * @param httpMethod http方法 - * @param path 路由路径 - * @return 返回匹配的所有路由集合 - */ - private List findRouteMatcher(HttpMethod httpMethod, String path) { - path = path.length() > 1 && path.endsWith("/") ? path.substring(0, path.length() - 1) : path; - - List matchSet = new ArrayList(); - for (RouteMatcher entry : routes) { - if (entry.matches(httpMethod, path)) { - matchSet.add(entry); - } - } - return matchSet; - } - - /** - * 查找所有匹配HttpMethod和path的路由 - * - * @param httpMethod http方法 - * @param path 路由路径 - * @return 返回匹配的所有路由集合 - */ - private List findInterceptor(HttpMethod httpMethod, String path) { - List matchSet = new ArrayList(); - for (RouteMatcher entry : interceptors) { - if (entry.matches(httpMethod, path)) { - matchSet.add(entry); - } - } - return matchSet; - } - - /** - * 查找符合请求头的路由 - * @param routeMatches - * @param acceptType - * @return - */ - private RouteMatcher findTargetWithGivenAcceptType(List routeMatches, String acceptType) { - if (acceptType != null && routeMatches.size() > 0) { - Map acceptedMimeTypes = getAcceptedMimeTypes(routeMatches); - String bestMatch = MimeParse.bestMatch(acceptedMimeTypes.keySet(), acceptType); - - if (routeWithGivenAcceptType(bestMatch)) { - return acceptedMimeTypes.get(bestMatch); - } else { - return null; - } - } else { - if (routeMatches.size() > 0) { - return routeMatches.get(0); - } - } - - return null; - } - - private boolean removeRoute(HttpMethod httpMethod, String path) { - List forRemoval = new ArrayList(); - - for (RouteMatcher routeEntry : routes) { - HttpMethod httpMethodToMatch = httpMethod; - - if (httpMethod == null) { - // Use the routeEntry's HTTP method if none was given, so that only path is used to match. - httpMethodToMatch = routeEntry.httpMethod; - } - - if (routeEntry.matches(httpMethodToMatch, path)) { - - if(Blade.debug()){ - LOGGER.debug("Removing path {}", path, httpMethod == null ? "" : " with HTTP method " + httpMethod); - } - - forRemoval.add(routeEntry); - } - } - - return routes.removeAll(forRemoval); - } -} diff --git a/blade-core/src/main/java/blade/route/RouteMatcher.java b/blade-core/src/main/java/blade/route/RouteMatcher.java deleted file mode 100644 index bd37b2f80..000000000 --- a/blade-core/src/main/java/blade/route/RouteMatcher.java +++ /dev/null @@ -1,209 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.route; - -import java.lang.reflect.Method; -import java.util.List; - -import blade.kit.PathKit; - -/** - * 路由匹配对象 - * - * @author biezhi - * @since 1.0 - */ -public class RouteMatcher { - - /** - * 目标运行类实例 - */ - Class target; - - /** - * 要运行的方法对象 - */ - Method execMethod; - - /** - * http请求方法 - */ - HttpMethod httpMethod; - - /** - * 路由path - */ - String path; - - /** - * 请求URI - */ - String requestURI; - - /** - * 允许的acceptType - */ - String acceptType; - - public RouteMatcher() { - } - - public RouteMatcher(Class target, Method execMethod, HttpMethod httpMethod, String path, String requestUri, String acceptType) { - super(); - this.target = target; - this.execMethod = execMethod; - this.httpMethod = httpMethod; - this.path = path; - this.requestURI = requestUri; - this.acceptType = acceptType; - } - - public String getAcceptType() { - return acceptType; - } - - public HttpMethod getHttpMethod() { - return httpMethod; - } - - public Method getExecMethod() { - return execMethod; - } - - public String getPath() { - return path; - } - - public String getRequestURI() { - return requestURI; - } - - public Class getTarget() { - return target; - } - - /** - * 根据http方法和path进行匹配 - * - * @param httpMethod http方法,GET/POST - * @param path 匹配的路径 - * @return true:匹配成功,false:匹配失败 - */ - boolean matches(HttpMethod httpMethod, String path) { - - // 如果是拦截器的全部匹配模式则跳过,返回true - if ((httpMethod == HttpMethod.BEFORE || httpMethod == HttpMethod.AFTER) - && (this.httpMethod == httpMethod) - && this.path.equals(PathKit.ALL_PATHS)) { - return true; - } - - boolean match = false; - - if (this.httpMethod == HttpMethod.ALL || this.httpMethod == httpMethod) { - match = matchPath(path); - } - - return match; - } - - /** - * 继续匹配 - * - * @param uri - * @return - */ - private boolean matchPath(String uri) { - - // /hello - if (!this.path.endsWith("*") && ((uri.endsWith("/") && !this.path.endsWith("/")) - || (this.path.endsWith("/") && !uri.endsWith("/")))) { - return false; - } - - if (this.path.equals(uri)) { - return true; - } - - // 检查参数 - List thisPathList = PathKit.convertRouteToList(this.path); - List uriList = PathKit.convertRouteToList(uri); - - int thisPathSize = thisPathList.size(); - int uriSize = uriList.size(); - - if (thisPathSize == uriSize) { - for (int i = 0; i < thisPathSize; i++) { - String thisPathPart = thisPathList.get(i); - String pathPart = uriList.get(i); - - if ((i == thisPathSize - 1) && (thisPathPart.equals("*") && this.path.endsWith("*"))) { - // 通配符匹配 - return true; - } - - if ((!thisPathPart.startsWith(":")) - && !thisPathPart.equals(pathPart) - && !thisPathPart.equals("*")) { - return false; - } - } - // 全部匹配 - return true; - } else { - if (this.path.endsWith("*")) { - if (uriSize == (thisPathSize - 1) && (path.endsWith("/"))) { - uriList.add(""); - uriList.add(""); - uriSize += 2; - } - - if (thisPathSize < uriSize) { - for (int i = 0; i < thisPathSize; i++) { - String thisPathPart = thisPathList.get(i); - String pathPart = uriList.get(i); - if (thisPathPart.equals("*") && (i == thisPathSize - 1) && this.path.endsWith("*")) { - return true; - } - if (!thisPathPart.startsWith(":") - && !thisPathPart.equals(pathPart) - && !thisPathPart.equals("*")) { - return false; - } - } - return true; - } - } - return false; - } - } - - @Override - public boolean equals(Object obj) { - if(obj instanceof RouteMatcher){ - RouteMatcher r = (RouteMatcher) obj; - return this.httpMethod == r.httpMethod && this.execMethod.getName().equals(r.execMethod.getName()) - && this.path.equals(r.path) && this.acceptType.equals(r.acceptType); - } - return false; - } - - @Override - public String toString() { - return httpMethod.name() + "------" + path; - } - -} diff --git a/blade-core/src/main/java/blade/route/RouteMatcherBuilder.java b/blade-core/src/main/java/blade/route/RouteMatcherBuilder.java deleted file mode 100644 index 6a98ec274..000000000 --- a/blade-core/src/main/java/blade/route/RouteMatcherBuilder.java +++ /dev/null @@ -1,270 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.route; - -import java.lang.reflect.Method; -import java.util.Set; - -import blade.Blade; -import blade.annotation.After; -import blade.annotation.Before; -import blade.annotation.Interceptor; -import blade.annotation.Path; -import blade.annotation.Route; -import blade.ioc.Container; -import blade.ioc.DefaultContainer; -import blade.kit.log.Logger; -import blade.kit.resource.ClassPathClassReader; -import blade.kit.resource.ClassReader; - -/** - * 路由构造器 - * - * @author biezhi - * @since 1.0 - */ -public final class RouteMatcherBuilder { - - private static final Logger LOGGER = Logger.getLogger(RouteMatcherBuilder.class); - - private static DefaultRouteMatcher routeMatcher = null; - - /** - * 类读取器,用于在指定规则中扫描类 - */ - private final static ClassReader classReader = new ClassPathClassReader(); - - /** - * IOC容器,存储路由到ioc中 - */ - private final static Container container = DefaultContainer.single(); - - private RouteMatcherBuilder() { - } - - /** - * 开始构建路由 - * - * @return 返回构建路由后的构造器 - */ - public static synchronized DefaultRouteMatcher building() { - - if (routeMatcher != null) { - routeMatcher.clearRoutes(); - routeMatcher = null; - } - - routeMatcher = new DefaultRouteMatcher(); - - if(Blade.debug()){ - LOGGER.debug("creates RouteMatcher"); - } - - String[] basePackages = Blade.defaultRoutes(); - - if(null != basePackages && basePackages.length > 0){ - - String basePackage = basePackages[0]; - - // 处理如:com.xxx.* 表示递归扫描包 - String suffix = basePackage.endsWith(".*") ? ".*" : ""; - basePackage = basePackage.endsWith(".*") ? basePackage.substring(0, basePackage.length() - 2) : basePackage; - - String routePackage = basePackage + "." + Blade.PACKAGE_ROUTE + suffix; - String interceptorPackage = basePackage + "." + Blade.PACKAGE_INTERCEPTOR + suffix; - - buildRoute(routePackage); - - buildInterceptor(interceptorPackage); - - } else { - // 路由 - String[] routePackages = Blade.routes(); - if(null != routePackages && routePackages.length > 0){ - buildRoute(routePackages); - } - - // 拦截器 - String[] interceptorPackages = Blade.interceptor(); - if(null != interceptorPackages && interceptorPackages.length > 0){ - buildInterceptor(interceptorPackages); - } - } - return routeMatcher; - } - - /** - * 构建拦截器 - * - * @param interceptorPackages 要添加的拦截器包 - */ - private static void buildInterceptor(String... interceptorPackages){ - // 拦截器 - for(String packageName : interceptorPackages){ - - boolean recursive = false; - - if (packageName.endsWith(".*")) { - packageName = packageName.substring(0, packageName.length() - 2); - recursive = true; - } - - // 扫描所有的Interceptor - Set> classes = classReader.getClassByAnnotation(packageName, Interceptor.class, recursive); - - if(null != classes && classes.size() > 0){ - for(Class interceptorClazz : classes){ - parseInterceptor(interceptorClazz); - } - } - } - } - - /** - * 构建路由 - * - * @param routePackages 要添加的路由包 - */ - private static void buildRoute(String... routePackages){ - // 路由 - for(String packageName : routePackages){ - - boolean recursive = false; - - if (packageName.endsWith(".*")) { - packageName = packageName.substring(0, packageName.length() - 2); - recursive = true; - } - - // 扫描所有的Controoler - Set> classes = classReader.getClassByAnnotation(packageName, Path.class, recursive); - - if(null != classes && classes.size() > 0){ - for(Class pathClazz : classes){ - parseRouter(pathClazz); - } - } - } - - } - - /** - * 解析拦截器 - * - * @param interceptor 要解析的拦截器class - */ - private static void parseInterceptor(final Class interceptor){ - - Method[] methods = interceptor.getMethods(); - if(null == methods || methods.length == 0){ - return; - } - - container.registBean(interceptor); - - for (Method method : methods) { - - Before before = method.getAnnotation(Before.class); - After after = method.getAnnotation(After.class); - - if (null != before) { - - String path = before.value().startsWith("/") ? before.value() : "/" + before.value(); - - path = path.length() > 1 && path.endsWith("/") ? path.substring(0, path.length() - 1) : path; - - String acceptType = before.acceptType(); - buildInterceptor(interceptor, method, path, HttpMethod.BEFORE, acceptType); - } - - if (null != after) { - String path = after.value().startsWith("/") ? after.value() : "/" + after.value(); - - path = path.length() > 1 && path.endsWith("/") ? path.substring(0, path.length() - 1) : path; - - String acceptType = after.acceptType(); - buildInterceptor(interceptor, method, path, HttpMethod.AFTER, acceptType); - } - } - } - - /** - * 解析一个控制器中的所有路由 - * - * @param controller 要解析的路由class - */ - private static void parseRouter(final Class router){ - - Method[] methods = router.getMethods(); - if(null == methods || methods.length == 0){ - return; - } - - final String nameSpace = router.getAnnotation(Path.class).value(); - - container.registBean(router); - - for (Method method : methods) { - - Route mapping = method.getAnnotation(Route.class); - - //route方法 - if (null != mapping) { - - ////构建路由 - - String path = mapping.value().startsWith("/") ? mapping.value() : "/" + mapping.value(); - path = nameSpace + path; - path = path.replaceAll("[/]+", "/"); - - path = path.length() > 1 && path.endsWith("/") ? path.substring(0, path.length() - 1) : path; - - HttpMethod methodType = mapping.method(); - - String acceptType = mapping.acceptType(); - - buildRoute(router, method, path, methodType, acceptType); - } - } - } - - /** - * 构建一个路由 - * - * @param target 路由目标执行的class - * @param execMethod 路由执行方法 - * @param path 路由url - * @param method 路由http方法 - * @param acceptType 路由acceptType - */ - private static void buildRoute(Class target, Method execMethod, String path, HttpMethod method, String acceptType){ - routeMatcher.addRoute(target, execMethod, path, method, acceptType); - } - - /** - * 构建一个路由 - * - * @param target 路由目标执行的class - * @param execMethod 路由执行方法 - * @param path 路由url - * @param method 路由http方法 - * @param acceptType 路由acceptType - */ - private static void buildInterceptor(Class target, Method execMethod, String path, HttpMethod method, String acceptType){ - routeMatcher.addInterceptor(target, execMethod, path, method, acceptType); - } - -} diff --git a/blade-core/src/main/java/blade/route/package-info.java b/blade-core/src/main/java/blade/route/package-info.java deleted file mode 100644 index d33ca2e58..000000000 --- a/blade-core/src/main/java/blade/route/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * blade路由实现 - */ -package blade.route; \ No newline at end of file diff --git a/blade-core/src/main/java/blade/server/BladeServer.java b/blade-core/src/main/java/blade/server/BladeServer.java deleted file mode 100644 index 820782515..000000000 --- a/blade-core/src/main/java/blade/server/BladeServer.java +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.server; - -import java.io.File; -import java.io.IOException; -import java.util.EnumSet; - -import javax.servlet.DispatcherType; - -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.webapp.WebAppContext; - -import blade.Blade; -import blade.BladeFilter; -import blade.kit.log.Logger; - -/** - * 内置jetty服务 - * - * @author biezhi - * @since 1.0 - */ -public final class BladeServer { - - /** - * 默认的应用所在位置 - */ - private static String DEFAULT_APP_PATH = BladeServer.class.getClassLoader().getResource("").getPath(); - - private static final Logger LOGGER = Logger.getLogger(BladeServer.class); - - private BladeServer(){ - - } - - static{ - try { - File rootDir = new File("");// 参数为空 - final String courseFile = rootDir.getCanonicalPath(); - - // 标准maven构建的webapp - File webapp = new File(courseFile + File.separator + "src/main/webapp"); - if(webapp.exists()){ - DEFAULT_APP_PATH = "src/main/webapp"; - } else { - // 普通web项目 - webapp = new File(courseFile + File.separator + "WebContent/"); - if(webapp.exists()){ - DEFAULT_APP_PATH = "WebContent"; - } - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - public static void setDefaultAppPath(final String defaultPath){ - BladeServer.DEFAULT_APP_PATH = defaultPath; - } - - /** - * 创建用于开发运行调试的Jetty Server, - */ - public static Server createServerInSource(Integer port, String host, String contextPath) { - - Server server = new Server(); - // 设置在JVM退出时关闭Jetty的钩子。 - server.setStopAtShutdown(true); - - // 这是http的连接器 - ServerConnector connector = new ServerConnector(server); - connector.setPort(port); - connector.setHost(host); - - // 解决Windows下重复启动Jetty居然不报告端口冲突的问题. - connector.setReuseAddress(false); - connector.setName("blade-jetty-server"); - server.setConnectors(new Connector[] { connector }); - - WebAppContext webContext = new WebAppContext(DEFAULT_APP_PATH, contextPath); - - webContext.addFilter(BladeFilter.class, "/*", EnumSet.of(DispatcherType.INCLUDE,DispatcherType.REQUEST,DispatcherType.FORWARD, DispatcherType.ASYNC)); - - // 设置webapp的位置 - webContext.setResourceBase(DEFAULT_APP_PATH); - webContext.setClassLoader(Thread.currentThread().getContextClassLoader()); - server.setHandler(webContext); - return server; - } - - public static void run(Integer port, String host, String contextPath){ - try { - - if(null == port){ - port = 9000; - } - - if(null == host){ - host = "0.0.0.0"; - } - - if(null == contextPath){ - contextPath = "/"; - } - - final Server server = createServerInSource(port, host, contextPath); - - Blade.runJetty = true; - server.stop(); - server.start(); - LOGGER.info("Blade Server Run In : http://" + host + ":" + port + contextPath); - server.join(); - } catch (Exception e) { - e.printStackTrace(); - System.exit(-1); - } - } -} \ No newline at end of file diff --git a/blade-core/src/main/java/blade/server/package-info.java b/blade-core/src/main/java/blade/server/package-info.java deleted file mode 100644 index 132a123fd..000000000 --- a/blade-core/src/main/java/blade/server/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * blade jetty服务 - */ -package blade.server; \ No newline at end of file diff --git a/blade-core/src/main/java/blade/servlet/FileItem.java b/blade-core/src/main/java/blade/servlet/FileItem.java deleted file mode 100644 index 009cacd14..000000000 --- a/blade-core/src/main/java/blade/servlet/FileItem.java +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.servlet; - -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.util.Arrays; - -import blade.kit.io.FastByteArrayOutputStream; - -/** - * 文件上传Item - * - * @author biezhi - * @since 1.0 - */ -public class FileItem { - - private String name; - - private boolean isFile; - private String fileName; - private String contentType; - private InputStream inputStream; - - public static final String FORM_DATA = "form-data"; - - public static final String MULTIPART_FORM_DATA = "multipart/form-data"; - - // 数据体缓存 - private FastByteArrayOutputStream outputStream; - - public void write(byte[] buf, int off, int len) { - if (outputStream == null) { - outputStream = new FastByteArrayOutputStream(); - } - outputStream.write(buf, off, len); - } - - // ----------------------------------------------------------------- - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getFileName() { - return fileName; - } - - public void setFileName(String fileName) { - this.fileName = fileName; - } - - public String getContentType() { - return contentType; - } - - public void setContentType(String contentType) { - this.contentType = contentType; - } - - public boolean isFile() { - return isFile; - } - - public void setFile(boolean isFile) { - this.isFile = isFile; - } - - public byte[] getFileContent() { - byte[] buf = outputStream.toByteArray(); - int dirtyCount = 2; - - // 最后会多出一个\r\n, - // 根据ServletInputStream, \n 就算一行结束, 因此对于\r需要特殊判断 - if ('\r' != buf[buf.length - 2]) - dirtyCount = 1; - - return Arrays.copyOfRange(buf, 0, buf.length - dirtyCount); - } - - public String getString(String encoding){ - try { - return outputStream == null ? null : new String(getFileContent(), encoding); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - return null; - } - - public String getString() { - return outputStream == null ? null : new String(getFileContent()); - } - - public InputStream getInputStream() { - return inputStream; - } - - public void setInputStream(InputStream inputStream) { - this.inputStream = inputStream; - } - -} - diff --git a/blade-core/src/main/java/blade/servlet/QueryParamsMap.java b/blade-core/src/main/java/blade/servlet/QueryParamsMap.java deleted file mode 100644 index 5bdcb7d89..000000000 --- a/blade-core/src/main/java/blade/servlet/QueryParamsMap.java +++ /dev/null @@ -1,268 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.servlet; - -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.servlet.http.HttpServletRequest; - -/** - * 查询参数封装 - *

- * 封装了URL上的查询参数和Path参数 - *

- * - * @author biezhi - * @since 1.0 - */ -public class QueryParamsMap { - - /** - * 一个空的参数查询map - */ - private static final QueryParamsMap NULL = new QueryParamsMap(); - - /** - * 查询参数字典,存放url上传输的参数 - */ - private Map queryMap = new HashMap(); - - /** - * 所有的值数组 - */ - private String[] values; - - private Pattern p = Pattern.compile("\\A[\\[\\]]*([^\\[\\]]+)\\]*"); - - /** - * 根据request创建一个QueryParamsMap对象 - * 从request.getParameterMap()方法解析参数 - * - * @param request HttpServletRequest请求对象 - */ - public QueryParamsMap(HttpServletRequest request) { - if (request == null) { - throw new IllegalArgumentException("HttpServletRequest cannot be null."); - } - loadQueryString(request.getParameterMap()); - } - - /** - * 并没有什么卵用 _(:з」∠)_ - */ - private QueryParamsMap() { - } - - - /** - * 解析和创建键和值,一键多值 - * - * @param key 形如: user[info][name] - * @param values 所有值 - */ - protected QueryParamsMap(String key, String... values) { - loadKeys(key, values); - } - - /** - * 构造一个查询参数对象 - * - * @param params 要加载进的所有参数 - */ - protected QueryParamsMap(Map params) { - loadQueryString(params); - } - - /** - * 加载查询参数 - * - * @param params 要加载进的所有参数 - */ - protected final void loadQueryString(Map params) { - for (Map.Entry param : params.entrySet()) { - loadKeys(param.getKey(), param.getValue()); - } - } - - /** - * 加载所有key - * - * @param key 要加载的键 - * @param value 家加载的值 - */ - protected final void loadKeys(String key, String[] value) { - String[] parsed = parseKey(key); - - if (parsed == null) { - return; - } - - if (!queryMap.containsKey(parsed[0])) { - queryMap.put(parsed[0], new QueryParamsMap()); - } - if (!parsed[1].isEmpty()) { - queryMap.get(parsed[0]).loadKeys(parsed[1], value); - } else { - queryMap.get(parsed[0]).values = value.clone(); - } - } - - protected final String[] parseKey(String key) { - Matcher m = p.matcher(key); - - if (m.find()) { - return new String[] {cleanKey(m.group()), key.substring(m.end())}; - } else { - return null; - } - } - - protected static final String cleanKey(String group) { - if (group.startsWith("[")) { - return group.substring(1, group.length() - 1); - } else { - return group; - } - } - - /** - * 根据key返回QueryParamsMap - * 注入:user[name]=fede - * 获取:get("user").get("name").value() || get("user","name").value() - * - * @param keys 键列表 - * @return 返回一个查询参数map - */ - public QueryParamsMap get(String... keys) { - QueryParamsMap ret = this; - for (String key : keys) { - if (ret.queryMap.containsKey(key)) { - ret = ret.queryMap.get(key); - } else { - ret = NULL; - } - } - return ret; - } - - /** - * @return 返回键的值 - */ - public String value() { - if (hasValue()) { - return values[0]; - } else { - return null; - } - } - - /** - * 根据传入的keys获取值 - * - * @param keys keys - * @return 返回键的值 - */ - public String value(String... keys) { - return get(keys).value(); - } - - /** - * @return 是否包含该key - */ - public boolean hasKeys() { - return !this.queryMap.isEmpty(); - } - - /** - * @return 是否包含value - */ - public boolean hasValue() { - return this.values != null && this.values.length > 0; - } - - /** - * @return 返回Boolean类型值 - */ - public Boolean booleanValue() { - return hasValue() ? Boolean.valueOf(value()) : null; - } - - /** - * @return Integer类型的值 - */ - public Integer integerValue() { - return hasValue() ? Integer.valueOf(value()) : null; - } - - /** - * @return 返回Long类型值 - */ - public Long longValue() { - return hasValue() ? Long.valueOf(value()) : null; - } - - /** - * @return 返回Float类型值 - */ - public Float floatValue() { - return hasValue() ? Float.valueOf(value()) : null; - } - - /** - * @return 返回Double类型值 - */ - public Double doubleValue() { - return hasValue() ? Double.valueOf(value()) : null; - } - - /** - * @return 返回values - */ - public String[] values() { - return this.values.clone(); - } - - /** - * @return 返回queryMap - */ - Map getQueryMap() { - return queryMap; - } - - /** - * @return 返回values - */ - String[] getValues() { - return values; - } - - /** - * @return 将queryMap转换为map - */ - public Map toMap() { - Map map = new HashMap(); - - for (Entry key : this.queryMap.entrySet()) { - map.put(key.getKey(), key.getValue().values); - } - - return map; - } -} diff --git a/blade-core/src/main/java/blade/servlet/Request.java b/blade-core/src/main/java/blade/servlet/Request.java deleted file mode 100644 index 35f22d15c..000000000 --- a/blade-core/src/main/java/blade/servlet/Request.java +++ /dev/null @@ -1,554 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.servlet; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; - -import blade.kit.IOKit; -import blade.kit.PathKit; -import blade.kit.StringKit; -import blade.kit.log.Logger; -import blade.route.RouteMatcher; - -/** - * HttpServletRequest请求包装类 - *

- * 提供对HttpServletRequest API的简单操作 - *

- * - * @author biezhi - * @since 1.0 - */ -public class Request { - - private static final Logger LOGGER = Logger.getLogger(Request.class); - - private static final String USER_AGENT = "user-agent"; - - private Map pathParams; - private List splat; - private QueryParamsMap queryMap; - - private HttpServletRequest servletRequest; - - private Session session = null; - - private String body = null; - private byte[] bodyAsBytes = null; - - private Set headers = null; - - protected Request() { - } - - /** - * 构造一个Request对象 - * - * @param match 路由匹配对象,用于存储URL参数等信息 - * @param request HttpServletRequest请求对象 - */ - public Request(RouteMatcher match, HttpServletRequest request) { - this.servletRequest = request; - initRequest(match); - } - - /** - * 初始化Request - * - * @param match 路由匹配对象,用于存储URL参数等信息 - */ - public void initRequest(RouteMatcher match) { - - List requestList = PathKit.convertRouteToList(match.getRequestURI()); - List pathList = PathKit.convertRouteToList(match.getPath()); - - this.pathParams = getPathParams(requestList, pathList); - this.splat = getSplat(requestList, pathList); - - } - - /** - * @return 返回URL路径上的所有参数 - */ - public Map pathParams() { - return Collections.unmodifiableMap(this.pathParams); - } - - /** - * 返回在URL路径上的参数值,如:/users/:name - * @param param 参数名称 - * @return 返回URL上对应的String参数值 - */ - public String pathParam(String param) { - if (param == null) { - return null; - } - - if (param.startsWith(":")) { - return this.pathParams.get(param.toLowerCase()); - } else { - return this.pathParams.get(":" + param.toLowerCase()); - } - } - - /** - * 返回int类型的path param - * @param param 参数名称 - * @return 返回URL上对应的Integer参数值 - */ - public Integer pathParamToInt(String param) { - String value = pathParam(param); - if(null != value){ - return Integer.valueOf(value); - } - return null; - } - - /** - * @return 返回通配符 - */ - public String[] splat() { - return splat.toArray(new String[splat.size()]); - } - - /** - * @return 返回请求method 如:GET, POST, PUT, ... - */ - public String requestMethod() { - return servletRequest.getMethod(); - } - - /** - * @return 返回请求scheme - */ - public String scheme() { - return servletRequest.getScheme(); - } - - /** - * @return 返回主机名称 - */ - public String host() { - return servletRequest.getHeader("host"); - } - - /** - * @return 返回请求UA - */ - public String userAgent() { - return servletRequest.getHeader(USER_AGENT); - } - - /** - * @return 返回服务端口 - */ - public int port() { - return servletRequest.getServerPort(); - } - - - /** - * @return 返回pathinfo 如:"/example/foo" - */ - public String pathInfo() { - return servletRequest.getPathInfo(); - } - - /** - * @return 返回servletPath - */ - public String servletPath() { - return servletRequest.getServletPath(); - } - - /** - * @return 返回contextpath - */ - public String contextPath() { - return servletRequest.getContextPath(); - } - - /** - * @return 返回url - */ - public String url() { - return servletRequest.getRequestURL().toString(); - } - - /** - * @return 返回contentType - */ - public String contentType() { - return servletRequest.getContentType(); - } - - /** - * @return 返回客户端IP - */ - public String ip() { - return servletRequest.getRemoteAddr(); - } - - /** - * @return 返回请求body - */ - public String body() { - if (body == null) { - readBody(); - } - return body; - } - - /** - * @return 返回转换为字节数组的请求body - */ - public byte[] bodyAsBytes() { - if (bodyAsBytes == null) { - readBody(); - } - return bodyAsBytes; - } - - private void readBody() { - try { - bodyAsBytes = IOKit.toByteArray(servletRequest.getInputStream()); - body = new String(bodyAsBytes); - } catch (Exception e) { - LOGGER.warn("Exception when reading body", e); - } - } - - /** - * @return 返回请求body的长度 - */ - public int contentLength() { - return servletRequest.getContentLength(); - } - - /** - * 获取query参数 - * - * @param queryParam 查询键 - * @return 返回查询到的value - */ - public String query(String queryParam) { - return servletRequest.getParameter(queryParam); - } - - /** - * 获取一个数组类型的query - * - * @param queryParam 查询键 - * @return 返回查询到的value数组 - */ - public String[] querys(String queryParam) { - return servletRequest.getParameterValues(queryParam); - } - - /** - * 获取query参数冰转换为Integer类型 - * @param queryParam 查询键 - * @return 返回查询到的int value - */ - public Integer queryToInt(String queryParam) { - String value = query(queryParam); - if(StringKit.isNotEmpty(value)){ - return Integer.valueOf(value); - } - return null; - } - - /** - * 获取query参数冰转换为Long类型 - * @param queryParam 查询键 - * @return 返回查询到的long value - */ - public Long queryToLong(String queryParam) { - String value = query(queryParam); - if(StringKit.isNotEmpty(value)){ - return Long.valueOf(value); - } - return null; - } - - /** - * 获取query参数冰转换为Boolean类型 - * @param queryParam 查询键 - * @return 返回查询到的boolean value - */ - public Boolean queryToBoolean(String queryParam) { - String value = query(queryParam); - if(StringKit.isNotEmpty(value)){ - return Boolean.valueOf(value); - } - return null; - } - - /** - * 获取query参数冰转换为Double类型 - * @param queryParam 查询键 - * @return 返回查询到的double value - */ - public Double queryToDouble(String queryParam) { - String value = query(queryParam); - if(StringKit.isNotEmpty(value)){ - return Double.valueOf(value); - } - return null; - } - - /** - * 获取query参数并转换为Float类型 - * @param queryParam 查询键 - * @return 返回查询到的float value - */ - public Float queryToFloat(String queryParam) { - String value = query(queryParam); - if(StringKit.isNotEmpty(value)){ - return Float.valueOf(value); - } - return null; - } - - /** - * 获取头信息 - * - * @param header 要查找的请求头 - * @return 返回请求头信息 - */ - public String header(String header) { - return servletRequest.getHeader(header); - } - - /** - * @return 返回查询参数集合 - */ - public Set querys() { - return servletRequest.getParameterMap().keySet(); - } - - /** - * @return 返回所有头信息 - */ - public Set headers() { - if (headers == null) { - headers = new TreeSet(); - Enumeration enumeration = servletRequest.getHeaderNames(); - while (enumeration.hasMoreElements()) { - headers.add(enumeration.nextElement()); - } - } - return headers; - } - - /** - * @return 返回查询字符串 - */ - public String queryString() { - return servletRequest.getQueryString(); - } - - /** - * 设置request属性 - * - * @param attribute 属性名称 - * @param value 属性值 - */ - public void attribute(String attribute, Object value) { - servletRequest.setAttribute(attribute, value); - } - - /** - * 获取request属性 - * - * @param attribute 属性名称 - * @return 返回属性值 - */ - public Object attribute(String attribute) { - return servletRequest.getAttribute(attribute); - } - - - /** - * @return 返回所有request属性 - */ - public Set attributes() { - Set attrList = new HashSet(); - Enumeration attributes = (Enumeration) servletRequest.getAttributeNames(); - while (attributes.hasMoreElements()) { - attrList.add(attributes.nextElement()); - } - return attrList; - } - - /** - * @return 返回原生HttpServletRequest对象 - */ - public HttpServletRequest servletRequest() { - return servletRequest; - } - - /** - * @return 返回查询map - */ - public QueryParamsMap queryMap() { - initQueryMap(); - - return queryMap; - } - - /** - * @param key the key - * @return the query map - */ - public QueryParamsMap queryMap(String key) { - return queryMap().get(key); - } - - private void initQueryMap() { - if (queryMap == null) { - queryMap = new QueryParamsMap(servletRequest()); - } - } - - /** - * @return 返回会话对象 - */ - public Session session() { - if (session == null) { - session = new Session(servletRequest.getSession()); - } - return session; - } - - /** - * 返回会话对象,如果不存在则创建一个 - * @param create true:创建,false:不创建 - * @return 返回一个会话对象 - */ - public Session session(boolean create) { - if (session == null) { - HttpSession httpSession = servletRequest.getSession(create); - if (httpSession != null) { - session = new Session(httpSession); - } - } - return session; - } - - /** - * @return 返回cookies - */ - public Map cookies() { - Map result = new HashMap(); - Cookie[] cookies = servletRequest.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - result.put(cookie.getName(), cookie.getValue()); - } - } - return result; - } - - /** - * 根据cookie名称获取cookie - * - * @param name cookie名称 - * @return 返回cookie值 - */ - public String cookie(String name) { - Cookie[] cookies = servletRequest.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals(name)) { - return cookie.getValue(); - } - } - } - return null; - } - - /** - * @return 返回uri - */ - public String uri() { - return servletRequest.getRequestURI(); - } - - /** - * @return 返回请求协议 - */ - public String protocol() { - return servletRequest.getProtocol(); - } - - private static Map getPathParams(List request, List matched) { - - Map params = new HashMap(); - - for (int i = 0; (i < request.size()) && (i < matched.size()); i++) { - String matchedPart = matched.get(i); - if (PathKit.isParam(matchedPart)) { - LOGGER.debug("matchedPart: " - + matchedPart - + " = " - + request.get(i)); - params.put(matchedPart.toLowerCase(), request.get(i)); - } - } - return Collections.unmodifiableMap(params); - } - - private static List getSplat(List request, List matched) { - - int nbrOfRequestParts = request.size(); - int nbrOfMatchedParts = matched.size(); - - boolean sameLength = (nbrOfRequestParts == nbrOfMatchedParts); - - List splat = new ArrayList(); - - for (int i = 0; (i < nbrOfRequestParts) && (i < nbrOfMatchedParts); i++) { - String matchedPart = matched.get(i); - - if (PathKit.isSplat(matchedPart)) { - - StringBuilder splatParam = new StringBuilder(request.get(i)); - if (!sameLength && (i == (nbrOfMatchedParts - 1))) { - for (int j = i + 1; j < nbrOfRequestParts; j++) { - splatParam.append("/"); - splatParam.append(request.get(j)); - } - } - splat.add(splatParam.toString()); - } - } - return Collections.unmodifiableList(splat); - } - -} diff --git a/blade-core/src/main/java/blade/servlet/Response.java b/blade-core/src/main/java/blade/servlet/Response.java deleted file mode 100644 index 2b89a0566..000000000 --- a/blade-core/src/main/java/blade/servlet/Response.java +++ /dev/null @@ -1,276 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.servlet; - -import java.io.IOException; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; - -import blade.kit.log.Logger; -import blade.render.ModelAndView; -import blade.render.Render; -import blade.render.RenderFactory; - -/** - * HttpServletResponse响应包装类 - *

- * 封装HttpServletResponse的一些方法 - *

- * - * @author biezhi - * @since 1.0 - */ -public class Response { - - private static final Logger LOGGER = Logger.getLogger(Response.class); - - private HttpServletResponse response; - - // 默认JSP引擎 - private Render render = RenderFactory.getRender(); - - private String body; - - protected Response() { - } - - public Response(HttpServletResponse response) { - this.response = response; - } - - - /** - * 设置响应状态码 - * - * @param statusCode 状态码 - */ - public void status(int statusCode) { - response.setStatus(statusCode); - } - - /** - * 设置contentType - * - * @param contentType contentType - */ - public void contentType(String contentType) { - response.setContentType(contentType); - } - - /** - * 设置响应内容 - * - * @param body 响应主体 - */ - public void body(String body) { - this.body = body; - } - - /** - * @return 返回响应体内容 - */ - public String body() { - return this.body; - } - - /** - * @return 返回原生HttpServletResponse - */ - public HttpServletResponse servletResponse() { - return response; - } - - /** - * 重定向到location - * - * @param location 重定向的location - */ - public void redirect(String location) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Redirecting ({} {} to {}", "Found", HttpServletResponse.SC_FOUND, location); - } - try { - response.sendRedirect(location); - } catch (IOException ioException) { - LOGGER.warn("Redirect failure", ioException); - } - } - - /** - * 重定向并修改响应码 - * - * @param location 重定向的location - * @param statusCode 状态码 - */ - public void redirect(String location, int statusCode) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Redirecting ({} to {}", statusCode, location); - } - response.setStatus(statusCode); - response.setHeader("Location", location); - response.setHeader("Connection", "close"); - try { - response.sendError(statusCode); - } catch (IOException e) { - LOGGER.warn("Exception when trying to redirect permanently", e); - } - } - - /** - * 设置响应头 - * - * @param header 头信息键 - * @param value 头信息值 - */ - public void header(String header, String value) { - response.addHeader(header, value); - } - - /** - * 设置cookie - * - * @param name cookie name - * @param value cookie value - */ - public void cookie(String name, String value) { - cookie(name, value, -1, false); - } - - /** - * 设置cookie - * - * @param name cookie name - * @param value cookie name - * @param maxAge cookie有效期 - */ - public void cookie(String name, String value, int maxAge) { - cookie(name, value, maxAge, false); - } - - /** - * 设置cookie - * - * @param name cookie name - * @param value cookie name - * @param maxAge cookie有效期 - * @param secured 是否SSL - */ - public void cookie(String name, String value, int maxAge, boolean secured) { - cookie("", name, value, maxAge, secured); - } - - /** - * 设置cookie - * @param path cookie所在域 - * @param name cookie name - * @param value cookie name - * @param maxAge cookie有效期 - * @param secured 是否SSL - */ - public void cookie(String path, String name, String value, int maxAge, boolean secured) { - Cookie cookie = new Cookie(name, value); - cookie.setPath(path); - cookie.setMaxAge(maxAge); - cookie.setSecure(secured); - response.addCookie(cookie); - } - - /** - * 移除cookie - * - * @param name 要移除的cookie name - */ - public void removeCookie(String name) { - Cookie cookie = new Cookie(name, ""); - cookie.setMaxAge(0); - response.addCookie(cookie); - } - - /** - * 渲染一个视图 - * @param view - */ - public void render(String view){ - render.render(view); - } - - /** - * 根据ModelAndView进行渲染 - * @param modelAndView - */ - public void render(ModelAndView modelAndView){ - render.render(modelAndView); - } - - /** - * 返回文字格式 - * @param text - */ - public void text(String text){ - render.text(text); - } - - /** - * 返回json格式 - * @param json - */ - public void json(String json){ - render.json(json); - } - - /** - * 返回xml格式 - * @param xml - */ - public void xml(String xml){ - render.xml(xml); - } - - /** - * 返回js格式 - * @param javascript - */ - public void javascript(String javascript){ - render.javascript(javascript); - } - - /** - * 返回html格式 - * @param html - */ - public void html(String html){ - render.html(html); - } - - /** - * 404默认视图 - * - * @param viewName - */ - public void render404(String viewName){ - render.render404(this.response, viewName); - } - - /** - * 500默认视图 - * - * @param bodyContent - */ - public void render500(String bodyContent){ - render.render500(bodyContent); - } -} diff --git a/blade-core/src/main/java/blade/servlet/ServletFileUpload.java b/blade-core/src/main/java/blade/servlet/ServletFileUpload.java deleted file mode 100644 index 5abaa2b09..000000000 --- a/blade-core/src/main/java/blade/servlet/ServletFileUpload.java +++ /dev/null @@ -1,270 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.servlet; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; - -import blade.route.HttpMethod; - -/** - * 文件上传对象 - * - * @author biezhi - * @since 1.0 - */ -public class ServletFileUpload { - - private static int BUF_SIZE = 1024 * 1024; - - private HttpServletRequest request; - - private static final String MULTIPART = "multipart/"; - - /** - * 所有的上传文本域 - */ - private Map> allFileItems; - - private ServletInputStream in; - private byte[] buf; - private String line; - - private ServletFileUpload(HttpServletRequest request) { - this.request = request; - } - - public boolean isMultipartContent(HttpServletRequest request) { - if (!HttpMethod.POST.toString().equalsIgnoreCase(request.getMethod())) { - return false; - } - String contentType = request.getContentType(); - if (contentType == null) { - return false; - } - if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) { - return true; - } - return false; - } - - /** - * 获取一个普通文本参数 - * - * @param name 参数名称 - * @return 返回要获取的参数 - */ - public String param(String name) { - FileItem part = fileItem(name); - return part != null && !part.isFile() ? part.getString() : null; - } - - /** - * 获取多个普通文本参数 - * - * @param name 参数名称 - * @return 返回要获取的参数数组 - */ - public String[] params(String name) { - String[] params = null; - - FileItem[] parts = fileItems(name); - - if (parts != null) { - params = new String[parts.length]; - for (int i = 0; i < parts.length; i++) { - FileItem part = parts[i]; - params[i] = part.isFile() ? part.getString() : null; - } - } - - return params; - } - - /** - * 获取一个参数 - * - * @param name 参数名称 - * @return 返回FileItem对象 - */ - public FileItem fileItem(String name) { - FileItem[] parts = fileItems(name); - return parts == null ? null : parts[0]; - } - - /** - * 获取多个个参数 - * - * @param name 参数名称 - * @return 返回FileItem对象数组 - */ - public FileItem[] fileItems(String name) { - try { - if (allFileItems == null) - parseMultiFileItem(); - - if (allFileItems == null) - return null; - - List list = allFileItems.get(name); - if (list == null || list.size() == 0) - return null; - - return list.toArray(new FileItem[list.size()]); - } catch (IOException e) { - // quiet - } - - return null; - } - - /** - * 设置缓冲大小 - * - * @param buffer 缓冲大小,用字节表示 - */ - public void setBufferSize(final int buffer){ - BUF_SIZE = buffer; - } - - public void parseMultiFileItem() throws IOException { - in = request.getInputStream(); - buf = new byte[BUF_SIZE]; - - String contentType = request.getContentType(); - int pos = contentType.indexOf("boundary="); - String boundary = contentType.substring(pos + 9); - - FileItem fileItem = null; - - int len = -1; - while ((len = readLine()) != -1) { - if (line.endsWith(boundary)) { // 开始一个域 - - putFileItem(fileItem); - - // 域名 - readLine(); - fileItem = checkFileItem(line); // disposition - - if (fileItem.isFile()) { - // 若是文件类型, 文件类型描述 - readLine(); - String type = line.substring("Content-Type: ".length()); - fileItem.setContentType(type); - } - - // 开始数据体前会有一个空行 - readLine(); - continue; - - } else if (line.indexOf(boundary) > -1) { // 全部结束 - putFileItem(fileItem); - break; - } - - // 数据体处理 - fileItem.write(buf, 0, len); - fileItem.setInputStream(in); - } - } - - - /** - * 产生一个域对象, 并判断是不是文件 - * - * @param disposition 匹配disposition - * @return 返回FileItem对象 - */ - private FileItem checkFileItem(String disposition) { - String regexFile = "^Content-Disposition: form-data; name=\"(.+)\"; filename=\"(.+)\"$"; - String regexComm = "^Content-Disposition: form-data; name=\"(.+)\"$"; - - FileItem fileItem = new FileItem(); - - // 文件域 - Matcher m = Pattern.compile(regexFile).matcher(disposition); - if (m.find()) { - fileItem.setFile(true); - fileItem.setName(m.group(1)); - fileItem.setFileName(m.group(2)); - return fileItem; - } - - // 普通文本域 - m = Pattern.compile(regexComm).matcher(disposition); - if (m.find()) { - fileItem.setFile(false); - fileItem.setName(m.group(1)); - return fileItem; - } - - return null; - } - - /** - * @return 读取一行到缓冲区, 返回读取字节数 - * @throws IOException - */ - private int readLine() throws IOException { - int len = in.readLine(buf, 0, buf.length); - line = new String(buf, 0, len).trim(); - - return len; - } - - /** - * 临时保存域对象 - * - * @param FileItem FileItem对象 - */ - private void putFileItem(FileItem fileItem) { - if (fileItem == null) - return; - - if (allFileItems == null) { - allFileItems = new HashMap>(); - } - - List list = allFileItems.get(fileItem.getName()); - if (list == null) { - list = new ArrayList(); - allFileItems.put(fileItem.getName(), list); - } - - list.add(fileItem); - } - - /** - * 生成一个上传文件对象 - * - * @param servletRequest 请求对象,用于解析表单文件 - * @return 返回一个上传文件对象 - */ - public static ServletFileUpload parseRequest(HttpServletRequest servletRequest) { - return new ServletFileUpload(servletRequest); - } - -} \ No newline at end of file diff --git a/blade-core/src/main/java/blade/servlet/package-info.java b/blade-core/src/main/java/blade/servlet/package-info.java deleted file mode 100644 index 8f93e0cfd..000000000 --- a/blade-core/src/main/java/blade/servlet/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * blade处理请求和响应 - */ -package blade.servlet; \ No newline at end of file diff --git a/blade-core/src/main/java/blade/wrapper/RequestResponseBuilder.java b/blade-core/src/main/java/blade/wrapper/RequestResponseBuilder.java deleted file mode 100644 index 3ce54662a..000000000 --- a/blade-core/src/main/java/blade/wrapper/RequestResponseBuilder.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.wrapper; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import blade.route.RouteMatcher; -import blade.servlet.Request; -import blade.servlet.Response; - -/** - * Request、Response构建器 - *

- * 构建Request和Response - *

- * - * @author biezhi - * @since 1.0 - */ -public final class RequestResponseBuilder { - - private RequestResponseBuilder() { - } - - /** - * 构建一个Request对象 - * - * @param match 匹配到的路由对象 - * @param request HttpServletRequest请求对象 - * @return Request 返回包装后的request - */ - public static Request build(RouteMatcher match, HttpServletRequest request) { - return new Request(match, request); - } - - /** - * 构建一个Response对象 - * - * @param response HttpServletResponse响应对象 - * @return Response 返回包装后的response - */ - public static Response build(HttpServletResponse response) { - return new Response(response); - } - -} diff --git a/blade-core/src/main/java/blade/wrapper/RequestWrapper.java b/blade-core/src/main/java/blade/wrapper/RequestWrapper.java deleted file mode 100644 index 3d5347ddd..000000000 --- a/blade-core/src/main/java/blade/wrapper/RequestWrapper.java +++ /dev/null @@ -1,229 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.wrapper; - -import java.util.Map; -import java.util.Set; - -import javax.servlet.http.HttpServletRequest; - -import blade.servlet.QueryParamsMap; -import blade.servlet.Request; -import blade.servlet.Session; - -/** - * Request增强 - * - * @author biezhi - * @since 1.0 - */ -public final class RequestWrapper extends Request { - - private Request delegate; - - public void setDelegate(Request delegate) { - this.delegate = delegate; - } - - public Request getDelegate() { - return delegate; - } - - @Override - public String requestMethod() { - return delegate.requestMethod(); - } - - @Override - public String scheme() { - return delegate.scheme(); - } - - @Override - public int port() { - return delegate.port(); - } - - @Override - public String pathInfo() { - return delegate.pathInfo(); - } - - @Override - public String servletPath() { - return delegate.servletPath(); - } - - @Override - public String contextPath() { - return delegate.contextPath(); - } - - @Override - public String contentType() { - return delegate.contentType(); - } - - @Override - public String body() { - return delegate.body(); - } - - @Override - public byte[] bodyAsBytes() { - return delegate.bodyAsBytes(); - } - - @Override - public int contentLength() { - return delegate.contentLength(); - } - - @Override - public boolean equals(Object obj) { - return delegate.equals(obj); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public Map pathParams() { - return delegate.pathParams(); - } - - @Override - public String pathParam(String param) { - return delegate.pathParam(param); - } - - @Override - public String[] splat() { - return delegate.splat(); - } - - @Override - public String host() { - return delegate.host(); - } - - @Override - public String ip() { - return delegate.ip(); - } - - @Override - public String query(String queryParam) { - return delegate.query(queryParam); - } - - @Override - public String header(String header) { - return delegate.header(header); - } - - @Override - public Set querys() { - return delegate.querys(); - } - - @Override - public Set headers() { - return delegate.headers(); - } - - @Override - public String queryString() { - return delegate.queryString(); - } - - @Override - public HttpServletRequest servletRequest() { - return delegate.servletRequest(); - } - - @Override - public String toString() { - return delegate.toString(); - } - - @Override - public String userAgent() { - return delegate.userAgent(); - } - - @Override - public String url() { - return delegate.url(); - } - - @Override - public String uri() { - return delegate.uri(); - } - - @Override - public String protocol() { - return delegate.protocol(); - } - - @Override - public void attribute(String attribute, Object value) { - delegate.attribute(attribute, value); - } - - @Override - public Object attribute(String attribute) { - return delegate.attribute(attribute); - } - - @Override - public Set attributes() { - return delegate.attributes(); - } - - @Override - public Session session() { - return delegate.session(); - } - - @Override - public Session session(boolean create) { - return delegate.session(create); - } - - @Override - public QueryParamsMap queryMap() { - return delegate.queryMap(); - } - - @Override - public QueryParamsMap queryMap(String key) { - return delegate.queryMap(key); - } - - @Override - public Map cookies() { - return delegate.cookies(); - } - - @Override - public String cookie(String name) { - return delegate.cookie(name); - } -} diff --git a/blade-core/src/main/java/blade/wrapper/ResponseWrapper.java b/blade-core/src/main/java/blade/wrapper/ResponseWrapper.java deleted file mode 100644 index 7b3282632..000000000 --- a/blade-core/src/main/java/blade/wrapper/ResponseWrapper.java +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.wrapper; - -import javax.servlet.http.HttpServletResponse; - -import blade.servlet.Response; - -/** - * Response增强 - * - * @author biezhi - * @since 1.0 - */ -public class ResponseWrapper extends Response { - - private Response delegate; - - private boolean redirected = false; - - public ResponseWrapper() { - // TODO Auto-generated constructor stub - } - - public ResponseWrapper(Response delegate) { - this.delegate = delegate; - } - - public void setDelegate(Response delegate) { - this.delegate = delegate; - } - - public Response getDelegate() { - return delegate; - } - - @Override - public void status(int statusCode) { - delegate.status(statusCode); - } - - @Override - public void body(String body) { - delegate.body(body); - } - - @Override - public String body() { - return delegate.body(); - } - - @Override - public boolean equals(Object obj) { - return delegate.equals(obj); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public HttpServletResponse servletResponse() { - return delegate.servletResponse(); - } - - @Override - public void redirect(String location) { - redirected = true; - delegate.redirect(location); - } - - @Override - public void redirect(String location, int httpStatusCode) { - redirected = true; - delegate.redirect(location, httpStatusCode); - } - - /** - * @return true if redirected has been done - */ - boolean isRedirected() { - return redirected; - } - - @Override - public void header(String header, String value) { - delegate.header(header, value); - } - - @Override - public String toString() { - return delegate.toString(); - } - - @Override - public void contentType(String contentType) { - delegate.contentType(contentType); - } - - @Override - public void cookie(String name, String value) { - delegate.cookie(name, value); - } - - @Override - public void cookie(String name, String value, int maxAge) { - delegate.cookie(name, value, maxAge); - } - - @Override - public void cookie(String name, String value, int maxAge, boolean secured) { - delegate.cookie(name, value, maxAge, secured); - } - - @Override - public void cookie(String path, String name, String value, int maxAge, boolean secured) { - delegate.cookie(path, name, value, maxAge, secured); - } - - @Override - public void removeCookie(String name) { - delegate.removeCookie(name); - } -} diff --git a/blade-core/src/main/java/blade/wrapper/package-info.java b/blade-core/src/main/java/blade/wrapper/package-info.java deleted file mode 100644 index 4d6f56a30..000000000 --- a/blade-core/src/main/java/blade/wrapper/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 对HttpServletRequest和HttpServletResponse的包装 - */ -package blade.wrapper; \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/Blade.java b/blade-core/src/main/java/com/blade/Blade.java new file mode 100644 index 000000000..ed068bfb4 --- /dev/null +++ b/blade-core/src/main/java/com/blade/Blade.java @@ -0,0 +1,690 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade; + +import com.blade.config.Configuration; +import com.blade.embedd.EmbedServer; +import com.blade.exception.EmbedServerException; +import com.blade.exception.RouteException; +import com.blade.ioc.Ioc; +import com.blade.ioc.SimpleIoc; +import com.blade.kit.Assert; +import com.blade.kit.CollectionKit; +import com.blade.kit.StringKit; +import com.blade.kit.base.Config; +import com.blade.mvc.handler.RouteHandler; +import com.blade.mvc.http.HttpMethod; +import com.blade.mvc.interceptor.Interceptor; +import com.blade.mvc.route.Route; +import com.blade.mvc.route.RouteBuilder; +import com.blade.mvc.route.RouteGroup; +import com.blade.mvc.route.Routers; +import com.blade.mvc.route.loader.ClassPathRouteLoader; +import com.blade.plugin.Plugin; + +import javax.servlet.Filter; +import javax.servlet.http.HttpServlet; +import java.io.InputStream; +import java.text.ParseException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Blade Core Class + * + * @author biezhi + * @since 1.6.6 + */ +public final class Blade { + + /** + * Indicates whether the framework has been initialized + */ + private boolean isInit = false; + + /** + * Framework Global Configuration + */ + private Configuration configuration; + + /** + * Default ioc container + */ + private Ioc ioc = new SimpleIoc(); + + /** + * Default routes + */ + private Routers routers = new Routers(); + + /** + * Route builder + */ + private RouteBuilder routeBuilder; + + /** + * Is enabled server + */ + private Boolean enableServer = false; + + /** + * plugins + */ + private Set> plugins; + + /** + * filters + */ + private Map, String[]> filters = CollectionKit.newHashMap(8); + + /** + * servlets + */ + private Map, String[]> servlets = CollectionKit.newHashMap(8); + + /** + * embed web server e.g:jetty/tomcat + */ + private EmbedServer embedServer; + + private Blade() { + this.configuration = new Configuration(); + this.plugins = CollectionKit.newHashSet(); + this.routeBuilder = new RouteBuilder(this.routers); + } + + private static final class BladeHolder { + private static final Blade $ = new Blade(); + } + + /** + * @return Single case method returns Blade object + */ + @Deprecated + public static Blade me() { + return BladeHolder.$; + } + + /** + * + * @param location + * @return + */ + @Deprecated + public static Blade me(String location) { + return $(location); + } + + /** + * @return Single case method returns Blade object + */ + public static Blade $() { + return BladeHolder.$; + } + + /** + * load blade application config file + * + * @param location + * @return + */ + public static Blade $(String location) { + Assert.notEmpty(location); + Blade blade = BladeHolder.$; + blade.loadAppConf(location); + return blade; + } + + public void init() { + if (!this.isInit) { + this.isInit = true; + } + } + + /** + * @return return route manager + */ + public Routers routers() { + return routers; + } + + public RouteBuilder routeBuilder() { + return routeBuilder; + } + + /** + * @return return blade ioc container + */ + public Ioc ioc() { + return ioc; + } + + /** + * Setting a ioc container + * + * @param ioc object + * @return return blade + */ + public Blade container(Ioc ioc) { + Assert.notNull(ioc); + this.ioc = ioc; + return this; + } + + /** + * Setting Properties configuration file File path based on classpath + * + * @param location properties file name + * @return return blade + */ + public Blade loadAppConf(String location) { + Assert.notBlank(location); + configuration.load(location); + return this; + } + + /** + * Setting route package,e.g:com.baldejava.route Can be introduced into + * multiple packages, all of which are in the package. + * + * @param packageName + * route package path, is your package name + * @return return blade + */ + public Blade addRoutePackage(String packageName) { + Assert.notBlank(packageName); + return this.addRoutePackages(packageName); + } + + /** + * Setting route package,e.g:com.baldejava.route Can be introduced into + * multiple packages, all of which are in the package. + * + * @param packages + * route package path, is your package name + * @return return blade + */ + public Blade addRoutePackages(String... packages) { + Assert.notNull(packages); + configuration.addRoutePkgs(packages); + return this; + } + + /** + * set base package + * + * @param basePackage + * @return + */ + public Blade basePackage(String basePackage) { + Assert.notBlank(basePackage); + configuration.setBasePackage(basePackage); + return this; + } + + /** + * Setting the path where the interceptor, e.g:com.bladejava.interceptor + * + * @param packageName interceptor packagename + * @return return blade + */ + public Blade interceptor(String packageName) { + Assert.notBlank(packageName); + configuration.setInterceptorPackage(packageName); + return this; + } + + /** + * Setting Ioc packages, e.g:com.bladejava.service + * + * @param packages + * All need to do into the package, can be introduced into a + * number of + * @return return blade + */ + public Blade ioc(String... packages) { + configuration.addIocPkgs(packages); + return this; + } + + /** + * Add a route + * + * @param path + * route path + * @param clazz + * Target object for routing + * @param method + * The method name of the route (at the same time, the HttpMethod + * is specified: post:saveUser, if not specified, HttpMethod.ALL) + * @return return blade + */ + public Blade route(String path, Class clazz, String method) { + routers.route(path, clazz, method); + return this; + } + + /** + * regsiter filter + * @param clazz + * @param pathSpec + * @return + */ + public Blade registerFilter(Class clazz, String... pathSpec){ + filters.put(clazz, pathSpec); + return this; + } + + /** + * regsiter servlet + * @param clazz + * @param pathSpec + * @return + */ + public Blade registerServlet(Class clazz, String... pathSpec){ + servlets.put(clazz, pathSpec); + return this; + } + + public Map, String[]> filters(){ + return filters; + } + + public Map, String[]> servlets(){ + return servlets; + } + + /** + * Register a functional route + * + * @param path + * route url + * @param clazz + * route processing class + * @param method + * route processing method name + * @param httpMethod + * HttpMethod Type, GET/POST/... + * @return Blade return blade + */ + public Blade route(String path, Class clazz, String method, HttpMethod httpMethod) { + routers.route(path, clazz, method, httpMethod); + return this; + } + + /** + * Add a route list + * + * @param routes + * route list + * @return return blade + */ + public Blade routes(List routes) { + Assert.notEmpty(routes, "Routes not is empty!"); + routers.addRoutes(routes); + return this; + } + + /** + * Register a GET request route + * + * @param path + * route path, request url + * @param handler + * execute route Handle + * @return return blade + */ + public Blade get(String path, RouteHandler handler) { + routers.route(path, handler, HttpMethod.GET); + return this; + } + + /** + * Register a POST request route + * + * @param path + * route path, request url + * @param handler + * execute route Handle + * @return return blade + */ + public Blade post(String path, RouteHandler handler) { + routers.route(path, handler, HttpMethod.POST); + return this; + } + + /** + * Register a DELETE request route + * + * @param path + * route path, request url + * @param handler + * execute route Handle + * @return return blade + */ + public Blade delete(String path, RouteHandler handler) { + routers.route(path, handler, HttpMethod.DELETE); + return this; + } + + /** + * Register a PUT request route + * + * @param path + * route path, request url + * @param handler + * execute route Handle + * @return return blade + */ + public Blade put(String path, RouteHandler handler) { + routers.route(path, handler, HttpMethod.PUT); + return this; + } + + /** + * Register for any request routing + * + * @param path + * route path, request url + * @param handler + * execute route Handle + * @return return blade + */ + public Blade all(String path, RouteHandler handler) { + routers.route(path, handler, HttpMethod.ALL); + return this; + } + + /** + * Register for any request routing + * + * @param path + * route path, request url + * @param handler + * execute route Handle + * @return return blade + */ + public Blade any(String path, RouteHandler handler) { + routers.route(path, handler, HttpMethod.ALL); + return this; + } + + /** + * Route Group. e.g blade.group('/users').get().post() + * + * @param prefix + * @return return blade + */ + public RouteGroup group(String prefix) { + Assert.notNull(prefix, "Route group prefix not is null"); + return new RouteGroup(this, prefix); + } + + /** + * Register a pre routing request interceptor + * + * @param path + * route path, request url + * @param handler + * execute route Handle + * @return return blade + */ + public Blade before(String path, RouteHandler handler) { + routers.route(path, handler, HttpMethod.BEFORE); + return this; + } + + /** + * Register a after routing request interceptor + * + * @param path + * route path, request url + * @param handler + * execute route Handle + * @return return blade + */ + public Blade after(String path, RouteHandler handler) { + routers.route(path, handler, HttpMethod.AFTER); + return this; + } + + /** + * Setting the frame static file folder + * + * @param resources + * List of directories to filter, e.g: "/public,/static,/images" + * @return return blade + */ + public Blade addResources(final String... resources) { + configuration.addResources(resources); + return this; + } + + /** + * add interceptor + * + * @param interceptor interceptor class + * @return return blade obj + */ + public Blade addInterceptor(Class interceptor) { + routeBuilder.addInterceptor(interceptor); + return this; + } + + /** + * Setting blade web root path + * + * @param webRoot + * web root path + * @return return blade + */ + public Blade webRoot(final String webRoot) { + Assert.notBlank(webRoot); + configuration.setWebRoot(webRoot); + return this; + } + + /** + * Setting blade run mode + * + * @param isDev + * is dev mode + * @return return blade + */ + public Blade isDev(boolean isDev) { + configuration.setDev(isDev); + return this; + } + + /** + * Setting jetty listen port + * + * @param port + * port, default is 9000 + * @return return blade + */ + public Blade listen(int port) { + config().put(Const.SERVER_PORT, port); + return this; + } + + /** + * start web server + * + * @param applicationClass your app root package starter + */ + public EmbedServer start(Class applicationClass, String contextPath) { + startNoJoin(applicationClass, contextPath); + embedServer.join(); + return embedServer; + } + + public EmbedServer start(Class applicationClass) { + return start(applicationClass, "/"); + } + + public EmbedServer startNoJoin(Class applicationClass, String contextPath) { + + this.loadAppConf(Const.APP_PROPERTIES); + + if(null != applicationClass){ + configuration.setApplicationClass(applicationClass); + if(StringKit.isBlank(configuration.getBasePackage())){ + configuration.setBasePackage(applicationClass.getPackage().getName()); + } + } + + try { + Class embedClazz = Class.forName(Const.JETTY_SERVER_CLASS); + if(null == embedClazz){ + embedClazz = Class.forName(Const.TOMCAT_SERVER_CLASS); + } + if(null != embedClazz){ + if(!configuration().isInit()){ + loadAppConf(Const.APP_PROPERTIES); + configuration().setEnv(config()); + } + + this.embedServer = (EmbedServer) embedClazz.newInstance(); + this.embedServer.startup(config().getInt(Const.SERVER_PORT, Const.DEFAULT_PORT), contextPath); + this.enableServer = true; + } else { + throw new EmbedServerException("Not found EmbedServer"); + } + } catch (Exception e) { + e.printStackTrace(); + } + return embedServer; + } + + /** + * @return Return EmbedServer + */ + public EmbedServer embedServer() { + return this.embedServer; + } + + /** + * @return Return blade config object + */ + @Deprecated + public Configuration applicationConfig() { + return configuration; + } + + public Configuration configuration() { + return configuration; + } + + /** + * @return Return blade config object + */ + public Config config() { + return configuration.config(); + } + + /** + * @return Return blade encoding, default is UTF-8 + */ + public String encoding() { + return configuration.getEncoding(); + } + + /** + * @return Return blade web root path + */ + public String webRoot() { + return configuration.webRoot(); + } + + /** + * @return Return is dev mode + */ + public boolean isDev() { + return configuration.isDev(); + } + + /** + * return register plugin object + * + * @param plugin + * plugin class + * @return return blade + */ + public Blade plugin(Class plugin) { + Assert.notNull(plugin); + plugins.add(plugin); + return this; + } + + /** + * Registration of a configuration file, e.g: "com.xxx.route","route.conf" + * + * @param basePackage + * controller package name + * @return return blade + */ + public Blade routeConf(String basePackage) { + return routeConf(basePackage, Const.DEFAULT_ROUTE_CONF); + } + + /** + * Registration of a configuration file, e.g: "com.xxx.route","route.conf" + * + * @param basePackage + * controller package name + * @param conf + * Configuration file path, the configuration file must be in + * classpath + * @return return blade + */ + public Blade routeConf(String basePackage, String conf) { + try { + Assert.notBlank(basePackage); + Assert.notBlank(conf); + InputStream ins = Blade.class.getResourceAsStream("/" + conf); + ClassPathRouteLoader routesLoader = new ClassPathRouteLoader(ins); + routesLoader.setBasePackage(basePackage); + List routes = routesLoader.load(); + routers.addRoutes(routes); + } catch (RouteException | ParseException e) { + e.printStackTrace(); + } + return this; + } + + /** + * @return Return blade is initialize + */ + public boolean isInit() { + return isInit; + } + + public Blade enableServer(boolean enableServer) { + this.enableServer = enableServer; + return this; + } + + public boolean enableServer() { + return this.enableServer; + } + + public Set> plugins() { + return this.plugins; + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/Const.java b/blade-core/src/main/java/com/blade/Const.java new file mode 100644 index 000000000..e9d5f5ef4 --- /dev/null +++ b/blade-core/src/main/java/com/blade/Const.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade; + +/** + * Const Interface + * + *
+ *     The basic configuration of the blade framework is stored
+ * 
+ * + * @author biezhi + * @since 1.6.6 + */ +public interface Const { + + /** + * the last blade framework version + */ + String VERSION = "1.7.0-alpha"; + + /** + * server 500 + */ + String VIEW_500 = "500 Internal Error

500 Internal Error


blade " + VERSION +"
"; + + /** + * server 404 + */ + String VIEW_404 = "404 Not Found

[ %s ] Not Found


blade " + VERSION +"
"; + + /** + * server 405 + */ + String VIEW_405 = "403 Uri Forbidden

[ %s ] Method Not Allowed


blade " + VERSION +"
"; + + /** + * default web server port + */ + int DEFAULT_PORT = 9000; + + String DEFAULT_ENCODING = "UTF-8"; + + String DEFAULT_ROUTE_CONF = "route.conf"; + + /**** package names ****/ + String ROUTE_PKGS = "route_packages"; + String IOC_PKGS = "ioc_packages"; + String CONFIG_PKGS = "config_packages"; + String RESOURCE_PKGS = "resouce_packages"; + String BASE_PKG = "base_package"; + String INTERCEPTOR_PKG = "interceptor_package"; + String FILTER_PKG = "filter_package"; + String LISTENER_PKG = "listener_package"; + + /**** blade properties ****/ + String BLADE_ROUTE = "blade.route"; + String BLADE_IOC = "blade.ioc"; + String BLADE_VIEW_404 = "blade.view404"; + String BLADE_VIEW_500 = "blade.view500"; + String BLADE_DEV = "blade.dev"; + String APP_PROPERTIES = "app.properties"; + String SERVER_PORT = "server.port"; + String JETTY_SERVER_CLASS = "com.blade.embedd.EmbedJettyServer"; + String TOMCAT_SERVER_CLASS = "com.blade.embedd.EmbedTomcatServer"; + String MVC_VIEW_404 = "mvc.view.404"; + String MVC_VIEW_500 = "mvc.view.500"; + String HTTP_ENCODING = "http.encoding"; + String MVC_STATICS = "mvc.statics"; + String APP_DEV = "app.dev"; + String APP_IOC = "app.ioc"; + String APP_BASE_PKG = "app.base-package"; + String APP_CLASSPATH = "app.classpath"; +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/annotation/Order.java b/blade-core/src/main/java/com/blade/annotation/Order.java new file mode 100644 index 000000000..9f1b85335 --- /dev/null +++ b/blade-core/src/main/java/com/blade/annotation/Order.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Order By + * + * @author biezhi + * @since 1.6.6 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Order{ + + int sort(); + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/annotation/package-info.java b/blade-core/src/main/java/com/blade/annotation/package-info.java new file mode 100644 index 000000000..31e0066ee --- /dev/null +++ b/blade-core/src/main/java/com/blade/annotation/package-info.java @@ -0,0 +1,4 @@ +/** + * Blade Annotation + */ +package com.blade.annotation; \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/banner/Banner.java b/blade-core/src/main/java/com/blade/banner/Banner.java new file mode 100644 index 000000000..addc8407c --- /dev/null +++ b/blade-core/src/main/java/com/blade/banner/Banner.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.banner; + +public interface Banner { + + String startText(); + +} diff --git a/blade-core/src/main/java/com/blade/banner/BannerFont.java b/blade-core/src/main/java/com/blade/banner/BannerFont.java new file mode 100644 index 000000000..9f312256f --- /dev/null +++ b/blade-core/src/main/java/com/blade/banner/BannerFont.java @@ -0,0 +1,248 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.banner; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.StringTokenizer; + +public class BannerFont { + + private final int height; + private final int heightWithoutDescenders; + private final int maxLine; + private final int smushMode; + private final char font[][][] = new char[256][][]; + private final String fontName; + + public static BannerFont load() throws IOException { + return load("standard.flf"); + } + + public static BannerFont load(String fontName) throws IOException { + return new BannerFont(BannerFont.class.getResource('/' + fontName)); + } + + public BannerFont(URL url) throws IOException { + this(fontLines(url)); + } + + private BannerFont(Iterator lines) { + String dummyS = lines.next(); + StringTokenizer st = new StringTokenizer(dummyS, " "); + String s = st.nextToken(); + char hardblank = s.charAt(s.length() - 1); + height = Integer.parseInt(st.nextToken()); + heightWithoutDescenders = Integer.parseInt(st.nextToken()); + maxLine = Integer.parseInt(st.nextToken()); + smushMode = Integer.parseInt(st.nextToken()); + int dummyI = Integer.parseInt(st.nextToken()); + + st = new StringTokenizer(lines.next(), " "); + if (st.hasMoreElements()) + fontName = st.nextToken(); + else + fontName = ""; + + for (int i = 0; i < dummyI - 1; i++){ + dummyS = lines.next(); + } + for (int i = 32; i < 256; i++) { + for (int h = 0; h < height; h++) { + dummyS = lines.hasNext() ? lines.next() : null; + if (dummyS == null){ + i = 256; + } else { + // System.out.println(dummyS); + int iNormal = i; + boolean abnormal = true; + if (h == 0) { + try { + i = Integer.parseInt(dummyS); + } catch (NumberFormatException e) { + abnormal = false; + } + if (abnormal) { + dummyS = lines.next(); + } else { + i = iNormal; + } + } + if (h == 0) + font[i] = new char[height][]; + int t = dummyS.length() - 1 - ((h == height - 1) ? 1 : 0); + if (height == 1) + t++; + font[i][h] = new char[t]; + for (int l = 0; l < t; l++) { + char a = dummyS.charAt(l); + font[i][h][l] = (a == hardblank) ? ' ' : a; + } + } + } + } + } + + @SuppressWarnings("deprecation") + private static Iterator fontLines(URL url) throws IOException { + InputStream ins = url.openStream(); + DataInputStream dis = new DataInputStream(new BufferedInputStream(ins)); + ArrayList list = new ArrayList(); + String line = null; + while (true) { + line = dis.readLine(); + if (line != null) { + list.add(line); + } else { + break; + } + } + dis.close(); + return list.iterator(); + } + + public int getHeight() { + return height; + } + + public int getMaxLine() { + return maxLine; + } + + public int getSmushMode() { + return smushMode; + } + + public int getHeightWithoutDescenders() { + return heightWithoutDescenders; + } + + public String getFontName() { + return fontName; + } + + private String getCharLineString(int c, int l) { + if (font[c][l] == null){ + return null; + } else { + return new String(font[c][l]); + } + } + + private static String scroll(String text, int offset) { + StringBuffer result = new StringBuffer(); + StringBuffer shift = new StringBuffer(); + for (int i = 0; i < offset; i++){ + shift.append(' '); + } + StringTokenizer st = new StringTokenizer(text, "\r\n"); + while (st.hasMoreElements()){ + result.append(shift.toString() + st.nextToken() + "\r\n"); + } + return result.toString(); + } + + private static String addLine(String text, String line, boolean leftJustify, int splitWidth) { + String result = text; + if (leftJustify){ + result += line; + } else { + result += scroll(line, (splitWidth / 2 - width(line) / 2)); + } + return result; + } + + public String asAscii(String text) { + return asAscii(text, false, true, 1024); + } + + public String asAscii(String text, boolean splitAtWord, boolean leftJustify, int splitWidth) { + String result = ""; + StringTokenizer st = new StringTokenizer(text, " "); + if (splitAtWord) { + while (st.hasMoreElements()){ + result = addLine(result, convertOneLine(st.nextToken()), leftJustify, splitWidth); + } + } else { + String line = ""; + while (st.hasMoreElements()) { + String w = st.nextToken(), word; + if (line.length() == 0) { + word = w; + } else { + word = ' ' + w; + } + String newLine = append(line, word); + if ((width(newLine) > splitWidth) && (line.length() > 0)) { + result = addLine(result, line + "\r\n", leftJustify, splitWidth); + line = append("", w); + } else{ + line = newLine; + } + } + if (line.length() > 0){ + result = addLine(result, line + "\r\n", leftJustify, splitWidth); + } + } + return result; + } + + private static int width(String text) { + int w = 0; + StringTokenizer st = new StringTokenizer(text, "\r\n"); + while (st.hasMoreElements()) + w = Math.max(w, st.nextToken().length()); + return w; + } + + private String convertOneLine(String text) { + String result = ""; + for (int l = 0; l < height; l++) { // for each line + for (int c = 0; c < text.length(); c++) // for each char + result += getCharLineString((int) text.charAt(c), l); + result += "\r\n"; + } + return result; + } + + private String append(String text, String end) { + StringBuffer result = new StringBuffer(); + int h = 0; + if (text.length() == 0){ + for (int i = 0; i < height; i++){ + text += " \r\n"; + } + } + StringTokenizer st = new StringTokenizer(text, "\r\n"); + int count = st.countTokens(); + while (st.hasMoreElements()) { + result.append(st.nextToken()); + for (int c = 0; c < end.length(); c++){ + result.append(getCharLineString((int) end.charAt(c), h)); + } + if(h < (count - 2)){ + result.append("\r\n"); + } + h++; + } + return result.toString(); + } +} diff --git a/blade-core/src/main/java/com/blade/banner/BannerStarter.java b/blade-core/src/main/java/com/blade/banner/BannerStarter.java new file mode 100644 index 000000000..0fcc3ea3f --- /dev/null +++ b/blade-core/src/main/java/com/blade/banner/BannerStarter.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.banner; + +public final class BannerStarter { + + private static Banner BANNER = new BladeBanner(); + + public static void printStart(){ + System.out.println(BANNER.startText()); + } + + public static void banner(Banner banner){ + BANNER = banner; + } + + public static void banner(final String text){ + BANNER = new Banner() { + @Override + public String startText() { + return text; + } + }; + } +} diff --git a/blade-core/src/main/java/com/blade/banner/BladeBanner.java b/blade-core/src/main/java/com/blade/banner/BladeBanner.java new file mode 100644 index 000000000..489285d62 --- /dev/null +++ b/blade-core/src/main/java/com/blade/banner/BladeBanner.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.banner; + +import com.blade.Const; + +public class BladeBanner implements Banner { + + private static final String[] banner = { + " __, _, _, __, __,", + " |_) | /_\\ | \\ |_", + " |_) | , | | |_/ |", + " ~ ~~~ ~ ~ ~ ~~~" + }; + + @Override + public String startText() { + StringBuffer text = new StringBuffer(); + for (String s : banner) { + text.append("\r\n\t\t" + s); + } + text.append("\r\n\t\t :: Blade :: (v" + Const.VERSION + ")\r\n"); + return text.toString(); + } +} diff --git a/blade-core/src/main/java/com/blade/comparator/OrderComparator.java b/blade-core/src/main/java/com/blade/comparator/OrderComparator.java new file mode 100644 index 000000000..e571780ba --- /dev/null +++ b/blade-core/src/main/java/com/blade/comparator/OrderComparator.java @@ -0,0 +1,23 @@ +package com.blade.comparator; + +import java.util.Comparator; + +import com.blade.annotation.Order; +import com.blade.kit.resource.ClassInfo; + +public class OrderComparator implements Comparator { + + @Override + public int compare(ClassInfo c1, ClassInfo c2) { + Order o1 = c1.getClazz().getAnnotation(Order.class); + Order o2 = c2.getClazz().getAnnotation(Order.class); + if (null == o1 || null == o2) + return 0; + if (o1.sort() > o2.sort()) + return 1; + if (o1.sort() < o2.sort()) + return -1; + return 0; + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/config/BaseConfig.java b/blade-core/src/main/java/com/blade/config/BaseConfig.java new file mode 100644 index 000000000..9bcf8bc5c --- /dev/null +++ b/blade-core/src/main/java/com/blade/config/BaseConfig.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.config; + +/** + * BaseConfig Interface, Implements Object can be auto execute. + * + * @author biezhi + * @since 1.6.6 + */ +public interface BaseConfig { + + void config(Configuration configuration); + +} diff --git a/blade-core/src/main/java/com/blade/config/Configuration.java b/blade-core/src/main/java/com/blade/config/Configuration.java new file mode 100644 index 000000000..a3b7ff2d6 --- /dev/null +++ b/blade-core/src/main/java/com/blade/config/Configuration.java @@ -0,0 +1,219 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.config; + +import static com.blade.Blade.$; + +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.blade.Const; +import com.blade.context.DynamicContext; +import com.blade.kit.Assert; +import com.blade.kit.StringKit; +import com.blade.kit.base.Config; +import com.blade.mvc.view.ViewSettings; + +/** + * Blade Application Config Class + * + * @author biezhi + * @since 1.6.6 + * + */ +public class Configuration { + + private static final Logger LOGGER = LoggerFactory.getLogger(Configuration.class); + + private Packages packages; + + // Encoding + private String encoding = Const.DEFAULT_ENCODING; + + // Is dev mode + private boolean isDev = true; + + private boolean isInit = false; + + private String webRoot; + + private Class applicationClass; + + private String classPath = "config"; + + private Config config = new Config(); + + public Configuration() { + this.packages = new Packages(); + this.addResources("/public/*", "/assets/*", "/static/*"); + } + + public void setEnv(Config config) { + + if (null != config && !isInit) { + + // get dev mode + this.isDev = config.getBoolean(Const.APP_DEV, true); + + // get ioc packages + packages.put(Const.IOC_PKGS, config.get(Const.APP_IOC)); + + // get view 404, 500 page + ViewSettings.$().setView500(config.get(Const.MVC_VIEW_500)); + ViewSettings.$().setView404(config.get(Const.MVC_VIEW_404)); + + // get http encoding + this.encoding = config.get(Const.HTTP_ENCODING, Const.DEFAULT_ENCODING); + + // get mvc static folders + String statics = config.get(Const.MVC_STATICS); + + // get app base package + String basePackage = config.get(Const.APP_BASE_PKG); + + this.classPath = config.get(Const.APP_CLASSPATH, "config"); + + // get server start port + Integer port = config.getInt(Const.SERVER_PORT, Const.DEFAULT_PORT); + $().listen(port); + + if (StringKit.isNotBlank(statics)) { + this.addResources(StringKit.split(statics, ',')); + } + + if (StringKit.isNotBlank(basePackage)) { + this.setBasePackage(basePackage); + } + isInit = true; + } + } + + public void addRoutePkgs(String... pkgs){ + packages.add(Const.ROUTE_PKGS, pkgs); + } + + public void addIocPkgs(String... pkgs){ + packages.add(Const.IOC_PKGS, pkgs); + } + + public String getBasePackage() { + return packages.first(Const.BASE_PKG); + } + + public String[] getConfigPkgs(){ + return packages.array(Const.CONFIG_PKGS); + } + + public String[] getIocPkgs(){ + return packages.array(Const.IOC_PKGS); + } + + public String[] getRoutePkgs(){ + return packages.array(Const.ROUTE_PKGS); + } + + public Set getResources(){ + return packages.values(Const.RESOURCE_PKGS); + } + + public String getInterceptorPkg(){ + return packages.first(Const.INTERCEPTOR_PKG); + } + + public String getFilterPkg(){ + return packages.first(Const.FILTER_PKG); + } + + public String getListenerPkg(){ + return packages.first(Const.LISTENER_PKG); + } + + public boolean isDev() { + return isDev; + } + + public String getEncoding() { + return encoding; + } + + public boolean isInit(){ + return this.isInit; + } + + public String webRoot() { + return this.webRoot; + } + + public Class getApplicationClass() { + return applicationClass; + } + + public void setApplicationClass(Class applicationClass) { + this.applicationClass = applicationClass; + DynamicContext.init(applicationClass); + } + + public void setInterceptorPackage(String interceptorPkg) { + packages.put(Const.INTERCEPTOR_PKG, interceptorPkg); + } + + public void setBasePackage(String basePackage) { + Assert.notBlank(basePackage); + + packages.put(Const.BASE_PKG, basePackage); + packages.put(Const.INTERCEPTOR_PKG, basePackage + ".interceptor"); + packages.put(Const.FILTER_PKG, basePackage + ".filter"); + packages.put(Const.LISTENER_PKG, basePackage + ".listener"); + + packages.add(Const.CONFIG_PKGS, basePackage + ".config"); + packages.add(Const.IOC_PKGS, basePackage + ".service.*"); + packages.add(Const.ROUTE_PKGS, basePackage + ".controller"); + } + + public void addResources(String... resources) { + Assert.notNull(resources); + for(String resource : resources){ + LOGGER.debug("Add Resource: {}", resource); + } + packages.add(Const.RESOURCE_PKGS, resources); + } + + public void setWebRoot(String webRoot) { + this.webRoot = webRoot; + } + + public void setDev(boolean isDev) { + this.isDev = isDev; + } + + public String getClassPath() { + return classPath; + } + + public void load(String location) { + try { + config.add(location); + } catch (Exception e){ + LOGGER.warn("[load config] " + e.getMessage()); + } + } + + public Config config() { + return config; + } +} diff --git a/blade-core/src/main/java/com/blade/config/Packages.java b/blade-core/src/main/java/com/blade/config/Packages.java new file mode 100644 index 000000000..c863d3b3c --- /dev/null +++ b/blade-core/src/main/java/com/blade/config/Packages.java @@ -0,0 +1,60 @@ +package com.blade.config; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.blade.kit.CollectionKit; +import com.blade.kit.StringKit; + +public class Packages { + + private Map> pool = CollectionKit.newHashMap(8); + + public Packages() { + } + + public void put(String name, String pkg) { + if(null != name && StringKit.isNotBlank(pkg)){ + Set values = CollectionKit.newHashSet(1); + values.add(pkg); + pool.put(name, values); + } + } + + public void add(String name, String...pkgs) { + if(null != name && null != pkgs && pkgs.length > 0){ + Set values = pool.get(name); + if(null == values){ + values = new HashSet(pkgs.length); + } else { + pool.remove(name); + } + values.addAll(Arrays.asList(pkgs)); + pool.put(name, values); + } + } + + public String[] array(String name){ + Set values = pool.get(name); + if(null != values){ + return values.toArray(new String[values.size()]); + } + return null; + } + + public Set values(String name){ + return pool.get(name); + } + + public String first(String name){ + Set values = pool.get(name); + if(null != values && !values.isEmpty()){ + return values.iterator().next(); + } + return null; + } + +} diff --git a/blade-core/src/main/java/com/blade/context/DynamicContext.java b/blade-core/src/main/java/com/blade/context/DynamicContext.java new file mode 100644 index 000000000..f3abe56f4 --- /dev/null +++ b/blade-core/src/main/java/com/blade/context/DynamicContext.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.context; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.blade.kit.resource.ClassPathClassReader; +import com.blade.kit.resource.ClassReader; +import com.blade.kit.resource.JarReaderImpl; + +/** + * Get ClassReader by JAR or folder + * + * @author biezhi + * @since 1.6.6 + */ +public final class DynamicContext { + + private static final Logger LOGGER = LoggerFactory.getLogger(DynamicContext.class); + + private static ClassReader CLASS_READER = null; + + private static boolean isJarContext = false; + + private DynamicContext() { + } + + public static void init(Class clazz){ + String rs = clazz.getResource("").toString(); + if(rs.contains(".jar")){ + CLASS_READER = new JarReaderImpl(); + isJarContext = true; + LOGGER.debug("{}", CLASS_READER); + } else{ + CLASS_READER = new ClassPathClassReader(); + } + } + + public static ClassReader getClassReader(){ + return CLASS_READER; + } + + public static boolean isJarContext() { + return isJarContext; + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/context/WebContextHolder.java b/blade-core/src/main/java/com/blade/context/WebContextHolder.java new file mode 100644 index 000000000..77f6eb3ef --- /dev/null +++ b/blade-core/src/main/java/com/blade/context/WebContextHolder.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.context; + +import javax.servlet.ServletContext; + +import com.blade.mvc.http.Request; +import com.blade.mvc.http.Response; +import com.blade.mvc.http.wrapper.Session; + +/** + * BladeWebContext + * + * @author biezhi + * @since 1.6.6 + */ +public class WebContextHolder { + + /** + * BladeWebContext object for the current thread + */ + private static ThreadLocal ctx = new ThreadLocal(); + + /** + * ServletContext Object that is created when the application is initialized + */ + private ServletContext context; + + /** + * Request + */ + private Request request; + + /** + * Response + */ + private Response response; + + private WebContextHolder(){} + + public static WebContextHolder me(){ + return ctx.get(); + } + + public static void init(ServletContext context) { + WebContextHolder bladeWebContext = new WebContextHolder(); + bladeWebContext.context = context; + ctx.set(bladeWebContext); + } + + public static void init(ServletContext context, Request request, Response response) { + WebContextHolder bladeWebContext = new WebContextHolder(); + bladeWebContext.context = context; + bladeWebContext.request = request; + bladeWebContext.response = response; + ctx.set(bladeWebContext); + } + + /** + * 移除当前线程的Request、Response对象 + */ + public static void remove(){ + ctx.remove(); + } + + public static Request request() { + return me().request; + } + + public static Response response() { + return me().response; + } + + public static Session session() { + return request().session(); + } + + public static ServletContext servletContext() { + return me().context; + } + + public ServletContext getContext() { + return context; + } + + public Request getRequest() { + return request; + } + + public Response getResponse() { + return response; + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/context/WebContextListener.java b/blade-core/src/main/java/com/blade/context/WebContextListener.java new file mode 100644 index 000000000..d62f61f95 --- /dev/null +++ b/blade-core/src/main/java/com/blade/context/WebContextListener.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.context; + +import static com.blade.Blade.$; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.blade.Blade; +import com.blade.Const; +import com.blade.banner.BannerStarter; +import com.blade.embedd.EmbedServer; +import com.blade.ioc.IocApplication; +import com.blade.kit.DispatchKit; +import com.blade.kit.SystemKit; + +/** + * Blade Web Context Listener + * + * @author biezhi + * @since 1.6.7 + */ +public class WebContextListener implements ServletContextListener, HttpSessionListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(WebContextListener.class); + + @Override + public void sessionCreated(HttpSessionEvent event) { + // session time out, default is 15 minutes, unit is minutes + int timeout = $().config().getInt("server.timeout", 15); + event.getSession().setMaxInactiveInterval(timeout * 60); + } + + @Override + public void contextInitialized(ServletContextEvent sce) { + Blade blade = Blade.$(); + if(!blade.isInit()){ + + ServletContext servletContext = sce.getServletContext(); + + WebContextHolder.init(servletContext); + + LOGGER.info("jdk.version\t=> {}", SystemKit.getJavaInfo().getVersion()); + LOGGER.info("user.dir\t\t=> {}", System.getProperty("user.dir")); + LOGGER.info("java.io.tmpdir\t=> {}", System.getProperty("java.io.tmpdir")); + LOGGER.info("user.timezone\t=> {}", System.getProperty("user.timezone")); + LOGGER.info("file.encoding\t=> {}", System.getProperty("file.encoding")); + + long initStart = System.currentTimeMillis(); + + String webRoot = DispatchKit.getWebRoot(servletContext); + + blade.webRoot(webRoot); + EmbedServer embedServer = blade.embedServer(); + if(null != embedServer){ + embedServer.setWebRoot(webRoot); + } + + LOGGER.info("blade.webroot\t=> {}", webRoot); + + try { + // initialization ioc + IocApplication iocApplication = new IocApplication(); + iocApplication.initBeans(); + + blade.init(); + + LOGGER.info("blade.isDev = {}", blade.isDev()); + + BannerStarter.printStart(); + String appName = blade.config().get("app.name", "Blade"); + LOGGER.info("{} initialize successfully, Time elapsed: {} ms.", appName, System.currentTimeMillis() - initStart); + } catch (Exception e) { + LOGGER.error("ApplicationContext init error", e); + } + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + } + + @Override + public void sessionDestroyed(HttpSessionEvent event) { + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/context/package-info.java b/blade-core/src/main/java/com/blade/context/package-info.java new file mode 100644 index 000000000..3b06fde32 --- /dev/null +++ b/blade-core/src/main/java/com/blade/context/package-info.java @@ -0,0 +1,4 @@ +/** + * Blade Context + */ +package com.blade.context; \ No newline at end of file diff --git a/blade-core/src/main/java/blade/render/RenderFactory.java b/blade-core/src/main/java/com/blade/embedd/EmbedServer.java similarity index 58% rename from blade-core/src/main/java/blade/render/RenderFactory.java rename to blade-core/src/main/java/com/blade/embedd/EmbedServer.java index ab7748b72..eebd6e92f 100644 --- a/blade-core/src/main/java/blade/render/RenderFactory.java +++ b/blade-core/src/main/java/com/blade/embedd/EmbedServer.java @@ -1,47 +1,40 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.render; - -/** - * 渲染引擎工厂 - * - * @author biezhi - * @since 1.0 - */ -public final class RenderFactory { - - // 默认JSP引擎 - private static Render render = JspRender.single(); - - private RenderFactory() { - } - - /** - * 设置一个渲染对象 - * @param render 渲染器引擎 - */ - public static void init(Render render){ - RenderFactory.render = render; - } - - /** - * @return 返回渲染器引擎 - */ - public static Render getRender(){ - return RenderFactory.render; - } - -} +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.embedd; + +import com.blade.exception.EmbedServerException; + +/** + * Jetty Server + * + * @author biezhi + * @since 1.6.6 + */ +public interface EmbedServer { + + void startup(int port) throws EmbedServerException; + + void startup(int port, String contextPath) throws EmbedServerException; + + void startup(int port, String contextPath, String webRoot) throws EmbedServerException; + + void join() throws EmbedServerException; + + void shutdown() throws EmbedServerException; + + void setWebRoot(String webRoot); + +} diff --git a/blade-core/src/main/java/com/blade/embedd/package-info.java b/blade-core/src/main/java/com/blade/embedd/package-info.java new file mode 100644 index 000000000..96aacd1f9 --- /dev/null +++ b/blade-core/src/main/java/com/blade/embedd/package-info.java @@ -0,0 +1,4 @@ +/** + * Blade Jetty Ext + */ +package com.blade.embedd; \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/exception/BladeException.java b/blade-core/src/main/java/com/blade/exception/BladeException.java new file mode 100644 index 000000000..89c937509 --- /dev/null +++ b/blade-core/src/main/java/com/blade/exception/BladeException.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.exception; + +public class BladeException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public BladeException() { + super(); + } + + public BladeException(String message, Throwable cause) { + super(message, cause); + } + + public BladeException(String message) { + super(message); + } + + public BladeException(Throwable cause) { + super(cause); + } + +} diff --git a/blade-core/src/main/java/com/blade/exception/ConfigException.java b/blade-core/src/main/java/com/blade/exception/ConfigException.java new file mode 100644 index 000000000..6f0098a33 --- /dev/null +++ b/blade-core/src/main/java/com/blade/exception/ConfigException.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.exception; + +public class ConfigException extends BladeException { + + private static final long serialVersionUID = 1L; + + public ConfigException() { + super(); + } + + public ConfigException(String message, Throwable cause) { + super(message, cause); + } + + public ConfigException(String message) { + super(message); + } + + public ConfigException(Throwable cause) { + super(cause); + } + +} diff --git a/blade-core/src/main/java/com/blade/exception/EmbedServerException.java b/blade-core/src/main/java/com/blade/exception/EmbedServerException.java new file mode 100644 index 000000000..47c5cede1 --- /dev/null +++ b/blade-core/src/main/java/com/blade/exception/EmbedServerException.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.exception; + +public class EmbedServerException extends BladeException { + + private static final long serialVersionUID = 1L; + + public EmbedServerException() { + super(); + } + + public EmbedServerException(String message, Throwable cause) { + super(message, cause); + } + + public EmbedServerException(String message) { + super(message); + } + + public EmbedServerException(Throwable cause) { + super(cause); + } + +} diff --git a/blade-core/src/main/java/com/blade/exception/MethodInvokeException.java b/blade-core/src/main/java/com/blade/exception/MethodInvokeException.java new file mode 100644 index 000000000..17d3b2571 --- /dev/null +++ b/blade-core/src/main/java/com/blade/exception/MethodInvokeException.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.exception; + +public class MethodInvokeException extends BladeException { + + private static final long serialVersionUID = 1L; + + public MethodInvokeException() { + super(); + } + + public MethodInvokeException(String message, Throwable cause) { + super(message, cause); + } + + public MethodInvokeException(String message) { + super(message); + } + + public MethodInvokeException(Throwable cause) { + super(cause); + } + +} diff --git a/blade-core/src/main/java/com/blade/exception/NotFoundException.java b/blade-core/src/main/java/com/blade/exception/NotFoundException.java new file mode 100644 index 000000000..6a1dd5091 --- /dev/null +++ b/blade-core/src/main/java/com/blade/exception/NotFoundException.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.exception; + +public class NotFoundException extends BladeException { + + private static final long serialVersionUID = 1L; + + public NotFoundException() { + super(); + } + + public NotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public NotFoundException(String message) { + super(message); + } + + public NotFoundException(Throwable cause) { + super(cause); + } + +} diff --git a/blade-core/src/main/java/blade/BladeApplication.java b/blade-core/src/main/java/com/blade/exception/RouteException.java similarity index 65% rename from blade-core/src/main/java/blade/BladeApplication.java rename to blade-core/src/main/java/com/blade/exception/RouteException.java index d75c4c04b..085b20312 100644 --- a/blade-core/src/main/java/blade/BladeApplication.java +++ b/blade-core/src/main/java/com/blade/exception/RouteException.java @@ -13,24 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade; - -import javax.servlet.ServletContext; +package com.blade.exception; /** - * Blade全局初始化类,可以在应用启动的时候做一些操作 - * + * RouteException + * * @author biezhi - * @since 1.0 + * @since 1.5 */ -public abstract class BladeApplication { - - /** - * 初始化方法,在应用启动的时候做一些初始化操作 - */ - public abstract void init(); - - public void contextInitialized(ServletContext servletContext){ - // +public class RouteException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public RouteException(String message) { + super(message); } + + public RouteException(String message, Throwable cause) { + super(message, cause); + } + + public RouteException(Throwable cause) { + super(cause); + } + } diff --git a/blade-core/src/main/java/com/blade/ioc/BeanDefine.java b/blade-core/src/main/java/com/blade/ioc/BeanDefine.java new file mode 100644 index 000000000..ce8d725e5 --- /dev/null +++ b/blade-core/src/main/java/com/blade/ioc/BeanDefine.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.ioc; + +/** + * Bean Define, IOC to define a target + * + * @author biezhi + * @since 1.5 + */ +public class BeanDefine { + + private Object bean; + private Class type; + private boolean isSignle; + + public BeanDefine(Object bean) { + this(bean, bean.getClass()); + } + + public BeanDefine(Object bean, Class type) { + this.bean = bean; + this.type = type; + this.isSignle = true; + } + + public BeanDefine(Object bean, Class type, boolean isSingle) { + this.bean = bean; + this.type = type; + this.isSignle = isSingle; + } + + public Object getBean() { + return bean; + } + + public void setBean(Object bean) { + this.bean = bean; + } + + public Class getType() { + return type; + } + + public void setType(Class type) { + this.type = type; + } + + public boolean isSignle() { + return isSignle; + } + + public void setSignle(boolean isSignle) { + this.isSignle = isSignle; + } + +} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/exception/ExceptionHandlerImpl.java b/blade-core/src/main/java/com/blade/ioc/Ioc.java similarity index 50% rename from blade-kit/src/main/java/blade/exception/ExceptionHandlerImpl.java rename to blade-core/src/main/java/com/blade/ioc/Ioc.java index 6107b651e..93b2ad4c5 100644 --- a/blade-kit/src/main/java/blade/exception/ExceptionHandlerImpl.java +++ b/blade-core/src/main/java/com/blade/ioc/Ioc.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,31 +13,41 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.exception; +package com.blade.ioc; + +import java.util.List; +import java.util.Set; + +import com.blade.ioc.loader.IocLoader; /** - * ExceptionHandlerImpl + * IOC container, it provides an interface for registration and bean. * * @author biezhi - * @since 1.0 + * @since 1.5 */ -public abstract class ExceptionHandlerImpl { +public interface Ioc { + + void load(IocLoader iocLoader); + + void addBean(Object bean); + + Object addBean(Class type); + + void addBean(String name, Object bean); + + void setBean(Class type, Object proxyBean); + + Object getBean(String name); + + T getBean(Class type); - protected Class exceptionClass; + List getBeanDefines(); - public ExceptionHandlerImpl(Class exceptionClass) { - this.exceptionClass = exceptionClass; - } - - public Class exceptionClass() { - return this.exceptionClass; - } - - public void exceptionClass(Class exceptionClass) { - this.exceptionClass = exceptionClass; - } - - public Throwable fillInStackTrace() { - return null; - } + List getBeans(); + + Set getBeanNames(); + + void clearAll(); + } diff --git a/blade-core/src/main/java/com/blade/ioc/IocApplication.java b/blade-core/src/main/java/com/blade/ioc/IocApplication.java new file mode 100644 index 000000000..f67a568aa --- /dev/null +++ b/blade-core/src/main/java/com/blade/ioc/IocApplication.java @@ -0,0 +1,232 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.ioc; + +import com.blade.Blade; +import com.blade.comparator.OrderComparator; +import com.blade.config.BaseConfig; +import com.blade.context.DynamicContext; +import com.blade.ioc.annotation.Component; +import com.blade.ioc.annotation.Service; +import com.blade.kit.CollectionKit; +import com.blade.kit.StringKit; +import com.blade.kit.resource.ClassInfo; +import com.blade.kit.resource.ClassReader; +import com.blade.mvc.annotation.Controller; +import com.blade.mvc.annotation.Intercept; +import com.blade.mvc.annotation.RestController; +import com.blade.mvc.interceptor.Interceptor; +import com.blade.mvc.route.RouteBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Modifier; +import java.util.*; + +/** + * IOC container, used to initialize the IOC object + * + * @author biezhi + * @since 1.0 + */ +public final class IocApplication { + + private static final Logger LOGGER = LoggerFactory.getLogger(IocApplication.class); + + /** + * aop interceptor + */ + private static List aopInterceptors = CollectionKit.newArrayList(8); + + /** + * Class to read object, load class + */ + private ClassReader classReader = null; + private Blade blade; + private OrderComparator orderComparator; + + public IocApplication() { + this.blade = Blade.$(); + this.classReader = DynamicContext.getClassReader(); + this.orderComparator = new OrderComparator(); + } + + /** + * load config beans + * + * @return + * @throws Exception + */ + private List loadCondigs() throws Exception { + String[] configPkgs = blade.configuration().getConfigPkgs(); + if (null != configPkgs && configPkgs.length > 0) { + List configs = CollectionKit.newArrayList(10); + for (int i = 0, len = configPkgs.length; i < len; i++) { + Set configClasses = classReader.getClassByAnnotation(configPkgs[i], Component.class, false); + if (null != configClasses) { + for (ClassInfo classInfo : configClasses) { + Class[] interfaces = classInfo.getClazz().getInterfaces(); + for (Class in : interfaces) { + if (in.equals(BaseConfig.class)) { + configs.add(classInfo); + } + } + if (classInfo.getClazz().getSuperclass().getName().equals("com.blade.aop.AbstractMethodInterceptor")) { + aopInterceptors.add(classInfo.newInstance()); + } + } + } + } + Collections.sort(configs, orderComparator); + return configs; + } + return null; + } + + private List loadServices() throws Exception { + String[] iocPkgs = blade.configuration().getIocPkgs(); + if (null != iocPkgs && iocPkgs.length > 0) { + List services = CollectionKit.newArrayList(16); + for (int i = 0, len = iocPkgs.length; i < len; i++) { + String pkgName = iocPkgs[i]; + if (StringKit.isBlank(pkgName)) { + continue; + } + // Recursive scan + boolean recursive = false; + if (pkgName.endsWith(".*")) { + pkgName = pkgName.substring(0, pkgName.length() - 2); + recursive = true; + } + + // Scan package all class + Set iocClasses = classReader.getClass(pkgName, recursive); + for (ClassInfo classInfo : iocClasses) { + Class clazz = classInfo.getClazz(); + if (!clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())) { + Component component = clazz.getAnnotation(Component.class); + Service service = clazz.getAnnotation(Service.class); + if (null != service || null != component) { + services.add(classInfo); + } + } + } + } + return services; + } + return null; + } + + private List loadControllers() { + String[] routePkgs = blade.configuration().getRoutePkgs(); + if (null != routePkgs && routePkgs.length > 0) { + List controllers = CollectionKit.newArrayList(8); + for(int i=0, len=routePkgs.length; i loadInterceptors() { + String interceptorPackage = blade.configuration().getInterceptorPkg(); + if (StringKit.isNotBlank(interceptorPackage)) { + List interceptors = CollectionKit.newArrayList(8); + Set intes = classReader.getClassByAnnotation(interceptorPackage, Intercept.class, true); + if (null != intes) { + for (ClassInfo classInfo : intes) { + if (null != classInfo.getClazz().getInterfaces() + && classInfo.getClazz().getInterfaces()[0].equals(Interceptor.class)) { + interceptors.add(classInfo); + } + } + } + return interceptors; + } + return null; + } + + public void initBeans() throws Exception { + List services = this.loadServices(); + List configs = this.loadCondigs(); + List controllers = this.loadControllers(); + // web + List inteceptors = this.loadInterceptors(); + + Ioc ioc = blade.ioc(); + + RouteBuilder routeBuilder = blade.routeBuilder(); + + // 1. init service + if (null != services) { + for(int i=0, len=services.size(); i baseConfigs = CollectionKit.newArrayList(); + + // 2. init configs + if (null != configs) { + for(int i=0, len=configs.size(); i beanDefines = ioc.getBeanDefines(); + if (null != beanDefines) { + for(int i=0, len=beanDefines.size(); i getAopInterceptors() { + return aopInterceptors; + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/ioc/IocKit.java b/blade-core/src/main/java/com/blade/ioc/IocKit.java new file mode 100644 index 000000000..880ed705f --- /dev/null +++ b/blade-core/src/main/java/com/blade/ioc/IocKit.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.ioc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.blade.ioc.annotation.InjectWith; +import com.blade.ioc.injector.FieldInjector; +import com.blade.kit.reflect.ClassDefine; + +/** + * IocKit, get Bean + * + * @author biezhi + * @since 1.5 + */ +public class IocKit { + + /** + * Get @Inject Annotated field + * + * @param ioc ioc container + * @param classDefine classDefine + * @return return FieldInjector + */ + public static List getInjectFields(Ioc ioc, ClassDefine classDefine) { + List injectors = new ArrayList(8); + for (Field field : classDefine.getDeclaredFields()) { + for (Annotation annotation : field.getAnnotations()) { + InjectWith with = annotation.annotationType().getAnnotation(InjectWith.class); + if (with != null) { + injectors.add(new FieldInjector(ioc, field)); + } + } + } + if (injectors.size() == 0) { + return Collections.emptyList(); + } + return injectors; + } + + /** + * Get bean according to BeanDefine + * + * @param ioc ioc container + * @param beanDefine beandefine object + * @return bean object + */ + public static Object getBean(BeanDefine beanDefine) { + Object bean = beanDefine.getBean(); + return bean; + } + + public static void injection(Ioc ioc, BeanDefine beanDefine) { + ClassDefine classDefine = ClassDefine.create(beanDefine.getType()); + List fieldInjectors = IocKit.getInjectFields(ioc, classDefine); + Object bean = beanDefine.getBean(); + for (FieldInjector fieldInjector : fieldInjectors) { + fieldInjector.injection(bean); + } + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/ioc/SimpleIoc.java b/blade-core/src/main/java/com/blade/ioc/SimpleIoc.java new file mode 100644 index 000000000..985b8e2c0 --- /dev/null +++ b/blade-core/src/main/java/com/blade/ioc/SimpleIoc.java @@ -0,0 +1,224 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.ioc; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.blade.ioc.loader.IocLoader; +import com.blade.kit.Assert; + +/** + * The default IOC container implementation + * + * @author biezhi + * @since 1.5 + */ +public class SimpleIoc implements Ioc { + + private static final Logger LOGGER = LoggerFactory.getLogger(Ioc.class); + + private final Map pool = new HashMap(); + + /** + * ioc loader + */ + @Override + public void load(IocLoader loader) { + loader.load(this); + } + + /** + * Add user-defined objects + */ + @Override + public void addBean(Object bean) { + Assert.notNull(bean); + addBean(bean.getClass().getName(), bean); + } + + /** + * Add user-defined objects + */ + public void addBean(Class beanClass, Object bean) { + Assert.notNull(beanClass); + addBean(beanClass.getName(), bean); + } + + /** + * Add user-defined objects + */ + public void addBean(String name, Object bean) { + Assert.notNull(bean); + BeanDefine beanDefine = new BeanDefine(bean); + addBean(name, beanDefine); + + // add interface + Class[] interfaces = beanDefine.getType().getInterfaces(); + if(interfaces.length > 0){ + for(Class interfaceClazz : interfaces){ + this.addBean(interfaceClazz.getName(), beanDefine); + } + } + } + + /** + * Update BeanDefine + */ + public void setBean(Class type, Object proxyBean) { + Assert.notNull(proxyBean); + + BeanDefine beanDefine = pool.get(type.getName()); + if(beanDefine != null){ + beanDefine.setBean(proxyBean); + } else { + beanDefine = new BeanDefine(proxyBean, type); + } + pool.put(type.getName(), beanDefine); + } + + /** + * Add user-defined objects + */ + public void addBean(String name, BeanDefine beanDefine) { + + Assert.notNull(name); + Assert.notNull(beanDefine); + +// LOGGER.debug("addBean: {}", name); + + if (pool.put(name, beanDefine) != null) { + LOGGER.warn("Duplicated Bean: {}", name); + } + + } + + /** + * Register @Component marked objects + */ + @Override + public Object addBean(Class type) { + return addBean(type, true); + } + + /** + * Register @Component marked objects + */ + public Object addBean(Class type, boolean singleton) { + Assert.notNull(type); + return addBean(type.getName(), type, singleton); + } + + /** + * Register @Component marked objects + */ + public Object addBean(String name, Class beanClass, boolean singleton) { + + Assert.notNull(name); + Assert.notNull(beanClass); + Assert.isFalse(beanClass.isInterface(), "Must not be interface: %s", beanClass.getName()); + Assert.isFalse(Modifier.isAbstract(beanClass.getModifiers()), "Must not be abstract class: %s", beanClass.getName()); + +// LOGGER.debug("addBean: {} = {}", name, beanClass.getName()); + + BeanDefine beanDefine = this.getBeanDefine(beanClass, singleton); + + if (pool.put(name, beanDefine) != null) { + LOGGER.warn("Duplicated Bean: {}", name); + } + + // add interface + Class[] interfaces = beanClass.getInterfaces(); + if(interfaces.length > 0){ + for(Class interfaceClazz : interfaces){ + if(null != this.getBean(interfaceClazz)){ + break; + } + this.addBean(interfaceClazz.getName(), beanDefine); + } + } + + return beanDefine.getBean(); + } + + public BeanDefine getBeanDefine(Class beanClass, boolean singleton) { + try { + Object object = beanClass.newInstance(); + return new BeanDefine(object, beanClass, singleton); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public T getBean(Class type) { + Object bean = this.getBean(type.getName()); + try { + return type.cast(bean); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + @Override + public Object getBean(String name) { + BeanDefine beanDefine = pool.get(name); + if (beanDefine == null) { + return null; + } + return IocKit.getBean(beanDefine); + } + + @Override + public List getBeanDefines() { + return new ArrayList(pool.values()); + } + + @Override + public List getBeans() { + Set beanNames = this.getBeanNames(); + List beans = new ArrayList(beanNames.size()); + for(String beanName : beanNames){ + Object bean = this.getBean(beanName); + if(null != bean){ + beans.add(bean); + } + } + return beans; + } + + @Override + public Set getBeanNames() { + return pool.keySet(); + } + + @Override + public void clearAll() { + pool.clear(); + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/blade/annotation/Component.java b/blade-core/src/main/java/com/blade/ioc/annotation/Component.java similarity index 89% rename from blade-core/src/main/java/blade/annotation/Component.java rename to blade-core/src/main/java/com/blade/ioc/annotation/Component.java index bbe9a0cde..912ffaa82 100644 --- a/blade-core/src/main/java/blade/annotation/Component.java +++ b/blade-core/src/main/java/com/blade/ioc/annotation/Component.java @@ -1,33 +1,36 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.annotation; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 可以被注入的bean注解 - * - * @author biezhi - * @since 1.0 - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Component{ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.ioc.annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Bean annotations can be injected + * + * @author biezhi + * @since 1.5 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Component{ + + String value() default ""; + } \ No newline at end of file diff --git a/blade-core/src/main/java/blade/annotation/Inject.java b/blade-core/src/main/java/com/blade/ioc/annotation/Inject.java similarity index 83% rename from blade-core/src/main/java/blade/annotation/Inject.java rename to blade-core/src/main/java/com/blade/ioc/annotation/Inject.java index 2edec1f74..2b3c4bcfc 100644 --- a/blade-core/src/main/java/blade/annotation/Inject.java +++ b/blade-core/src/main/java/com/blade/ioc/annotation/Inject.java @@ -13,27 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.annotation; +package com.blade.ioc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import com.blade.ioc.injector.FieldInjector; + /** - * 自动注入接口 - * + * Automatic injection + * * @author biezhi - * @since 1.0 + * @since 1.5 */ @Target(ElementType.FIELD) +@InjectWith(FieldInjector.class) @Retention(RetentionPolicy.RUNTIME) public @interface Inject { - /** - * 要注入的类类型 - * @return class - */ - Class value() default Class.class; - + String value() default ""; + } \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/ioc/annotation/InjectWith.java b/blade-core/src/main/java/com/blade/ioc/annotation/InjectWith.java new file mode 100644 index 000000000..107d5435a --- /dev/null +++ b/blade-core/src/main/java/com/blade/ioc/annotation/InjectWith.java @@ -0,0 +1,21 @@ +package com.blade.ioc.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.blade.ioc.injector.Injector; + +/**S + * 凡是标注了 InjectFieldWith 的第三方 Annotation,就被允许进行自定义注入字段 + */ +@Target(ElementType.ANNOTATION_TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface InjectWith { + + Class value(); + +} \ No newline at end of file diff --git a/blade-core/src/main/java/blade/annotation/Path.java b/blade-core/src/main/java/com/blade/ioc/annotation/Service.java similarity index 80% rename from blade-core/src/main/java/blade/annotation/Path.java rename to blade-core/src/main/java/com/blade/ioc/annotation/Service.java index bd2010711..f890cf8a1 100644 --- a/blade-core/src/main/java/blade/annotation/Path.java +++ b/blade-core/src/main/java/com/blade/ioc/annotation/Service.java @@ -1,40 +1,36 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.annotation; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 路由类注解,标识一个类是否是路由 - * - * @author biezhi - * @since 1.0 - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Path{ - - /** - * 命名空间 - * @return String - */ - String value() default "/"; - +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.ioc.annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Bean annotations can be injected + * + * @author biezhi + * @since 1.5 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Service{ + + String value() default ""; + } \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/ioc/annotation/package-info.java b/blade-core/src/main/java/com/blade/ioc/annotation/package-info.java new file mode 100644 index 000000000..93ad4b3f1 --- /dev/null +++ b/blade-core/src/main/java/com/blade/ioc/annotation/package-info.java @@ -0,0 +1,4 @@ +/** + * Blade IOC Annotation + */ +package com.blade.ioc.annotation; \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/ioc/injector/FieldInjector.java b/blade-core/src/main/java/com/blade/ioc/injector/FieldInjector.java new file mode 100644 index 000000000..00883c038 --- /dev/null +++ b/blade-core/src/main/java/com/blade/ioc/injector/FieldInjector.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.ioc.injector; + +import java.lang.reflect.Field; + +import com.blade.ioc.Ioc; + +/** + * Bean Field Injector + * + * @author biezhi + * @since 1.5 + */ +public class FieldInjector implements Injector { + + private Ioc ioc; + + private Field field; + + public FieldInjector(Ioc ioc, Field field) { + this.ioc = ioc; + this.field = field; + } + + @Override + public void injection(Object bean) { + try { + Class fieldType = field.getType(); + Object value = ioc.getBean(fieldType); + if (value == null) { + throw new IllegalStateException("Can't inject bean: " + fieldType.getName() + " for field: " + field); + } + field.setAccessible(true); + field.set(bean, value); + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/blade-core/src/main/java/blade/Blade.java b/blade-core/src/main/java/com/blade/ioc/injector/Injector.java similarity index 77% rename from blade-core/src/main/java/blade/Blade.java rename to blade-core/src/main/java/com/blade/ioc/injector/Injector.java index fd66f1fb9..1f7a891bb 100644 --- a/blade-core/src/main/java/blade/Blade.java +++ b/blade-core/src/main/java/com/blade/ioc/injector/Injector.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,19 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade; - +package com.blade.ioc.injector; /** - * Blade Core Class - * - * @author biezhi - * @since 1.0 + * Bean Injector interface * + * @author biezhi + * @since 1.5 */ -public final class Blade extends BladeBase{ - - private Blade() { - } +public interface Injector { + + void injection(Object bean); } diff --git a/blade-core/src/main/java/com/blade/ioc/injector/package-info.java b/blade-core/src/main/java/com/blade/ioc/injector/package-info.java new file mode 100644 index 000000000..3290150d5 --- /dev/null +++ b/blade-core/src/main/java/com/blade/ioc/injector/package-info.java @@ -0,0 +1,4 @@ +/** + * Blade IOC Injector + */ +package com.blade.ioc.injector; \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/ioc/loader/IocAnnotationLoader.java b/blade-core/src/main/java/com/blade/ioc/loader/IocAnnotationLoader.java new file mode 100644 index 000000000..d51b53343 --- /dev/null +++ b/blade-core/src/main/java/com/blade/ioc/loader/IocAnnotationLoader.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.ioc.loader; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.blade.context.DynamicContext; +import com.blade.ioc.SimpleIoc; +import com.blade.ioc.annotation.Component; +import com.blade.kit.CollectionKit; +import com.blade.kit.resource.ClassInfo; +import com.blade.kit.resource.ClassReader; + +/** + * Ioc annotation loader + * + * @author biezhi + * @since 1.5 + */ +public final class IocAnnotationLoader implements IocLoader { + + private Collection classes; + + private ClassReader classReader = DynamicContext.getClassReader(); + + public IocAnnotationLoader(String... packageNames) { + List> annotations = new ArrayList>(1); + annotations.add(Component.class); + this.classes = finder(Arrays.asList(packageNames), annotations, true); + } + + private Collection finder(List packageNames, List> annotations, boolean recursive){ + Collection classes = CollectionKit.newArrayList(); + for(String packageName : packageNames){ + for(Class annotation : annotations){ + classes.addAll(classReader.getClassByAnnotation(packageName, annotation, recursive)); + } + } + return classes; + } + + public IocAnnotationLoader(Collection classes) { + this.classes = classes; + } + + @Override + public void load(SimpleIoc ioc) { + for (ClassInfo classInfo : classes) { + Class cls = classInfo.getClazz(); + Component anno = cls.getAnnotation(Component.class); + if (anno != null) { + String name = anno.value().equals("") ? cls.getName() : anno.value(); + ioc.addBean(name, cls, true); + } + } + // free + classes = null; + } +} diff --git a/blade-core/src/main/java/com/blade/ioc/loader/IocLoader.java b/blade-core/src/main/java/com/blade/ioc/loader/IocLoader.java new file mode 100644 index 000000000..36d4d3858 --- /dev/null +++ b/blade-core/src/main/java/com/blade/ioc/loader/IocLoader.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.ioc.loader; + +import com.blade.ioc.SimpleIoc; + +/** + * Ioc loader interface + * + * @author biezhi + * @since 1.5 + */ +public interface IocLoader { + + void load(SimpleIoc ioc); + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/ioc/package-info.java b/blade-core/src/main/java/com/blade/ioc/package-info.java new file mode 100644 index 000000000..a4173b462 --- /dev/null +++ b/blade-core/src/main/java/com/blade/ioc/package-info.java @@ -0,0 +1,4 @@ +/** + * Blade IOC + */ +package com.blade.ioc; \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/kit/AbortPolicyWithReport.java b/blade-core/src/main/java/com/blade/kit/AbortPolicyWithReport.java new file mode 100644 index 000000000..3b5ddcbad --- /dev/null +++ b/blade-core/src/main/java/com/blade/kit/AbortPolicyWithReport.java @@ -0,0 +1,23 @@ +package com.blade.kit; + +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; + +public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy { + + private final String threadName; + + public AbortPolicyWithReport(String threadName) { + this.threadName = threadName; + } + + public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { + String msg = String.format("Blade[" + + " Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: %d)," + + " Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s)]", + threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(), e.getLargestPoolSize(), + e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating()); + System.out.println(msg); + throw new RejectedExecutionException(msg); + } +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/kit/AsmKit.java b/blade-core/src/main/java/com/blade/kit/AsmKit.java new file mode 100644 index 000000000..bed8beb0a --- /dev/null +++ b/blade-core/src/main/java/com/blade/kit/AsmKit.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; + +import org.objectweb.asm.*; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * ASM Tools + * + * @author biezhi + * @since 1.6.6 + */ +public final class AsmKit { + + /** + * + *

+ * 比较参数类型是否一致 + *

+ * + * @param types + * asm的类型({@link Type}) + * @param clazzes + * java 类型({@link Class}) + * @return + */ + private static boolean sameType(Type[] types, Class[] clazzes) { + // 个数不同 + if (types.length != clazzes.length) { + return false; + } + for (int i = 0; i < types.length; i++) { + if (!Type.getType(clazzes[i]).equals(types[i])) { + return false; + } + } + return true; + } + + /** + * + *

+ * 获取方法的参数名 + *

+ * + * @param m + * @return + */ + public static String[] getMethodParamNames(final Method m) throws IOException { + final String[] paramNames = new String[m.getParameterTypes().length]; + final String n = m.getDeclaringClass().getName(); + ClassReader cr = null; + try { + cr = new ClassReader(n); + } catch (IOException e) { + return null; + } + cr.accept(new ClassVisitor(Opcodes.ASM5) { + @Override + public MethodVisitor visitMethod(final int access, final String name, final String desc, + final String signature, final String[] exceptions) { + final Type[] args = Type.getArgumentTypes(desc); + // 方法名相同并且参数个数相同 + if (!name.equals(m.getName()) || !sameType(args, m.getParameterTypes())) { + return super.visitMethod(access, name, desc, signature, exceptions); + } + MethodVisitor v = super.visitMethod(access, name, desc, signature, exceptions); + return new MethodVisitor(Opcodes.ASM5, v) { + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, + int index) { + int i = index - 1; + // 如果是静态方法,则第一就是参数 + // 如果不是静态方法,则第一个是"this",然后才是方法的参数 + if (Modifier.isStatic(m.getModifiers())) { + i = index; + } + if (i >= 0 && i < paramNames.length) { + paramNames[i] = name; + } + super.visitLocalVariable(name, desc, signature, start, end, index); + } + }; + } + }, 0); + return paramNames; + } + +} diff --git a/blade-core/src/main/java/com/blade/kit/BladeThreadPool.java b/blade-core/src/main/java/com/blade/kit/BladeThreadPool.java new file mode 100644 index 000000000..89f54a2d8 --- /dev/null +++ b/blade-core/src/main/java/com/blade/kit/BladeThreadPool.java @@ -0,0 +1,20 @@ +package com.blade.kit; + +import java.util.concurrent.*; + +public class BladeThreadPool { + + public static Executor getExecutor(int threads, int queues) { + return getThreadPoolExecutor(threads, queues); + } + + public static ThreadPoolExecutor getThreadPoolExecutor(int threads, int queues) { + String name = "blade-pool"; + return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, + queues == 0 ? new SynchronousQueue() + : (queues < 0 ? new LinkedBlockingQueue() + : new LinkedBlockingQueue(queues)), + new NamedThreadFactory(name, true), new AbortPolicyWithReport(name)); + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/kit/DispatchKit.java b/blade-core/src/main/java/com/blade/kit/DispatchKit.java new file mode 100644 index 000000000..fa32b1a53 --- /dev/null +++ b/blade-core/src/main/java/com/blade/kit/DispatchKit.java @@ -0,0 +1,161 @@ +package com.blade.kit; + +import com.blade.Blade; +import com.blade.Const; +import com.blade.mvc.http.Response; +import com.blade.mvc.view.ViewSettings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.URL; +import java.net.URLDecoder; + +import static com.blade.Blade.$; + +public class DispatchKit { + + private static final Logger LOGGER = LoggerFactory.getLogger(DispatchKit.class); + + private static final boolean isWeb = !$().enableServer(); + + private static final Class appClass = $().configuration().getApplicationClass(); + + private static Boolean isDev = null; + + public static String getPath(Class clazz) { + URL url = clazz.getProtectionDomain().getCodeSource().getLocation(); + String filePath = null; + try { + filePath = URLDecoder.decode(url.getPath(), "utf-8"); + } catch (Exception e) { + e.printStackTrace(); + } + if (filePath.endsWith(".jar")) { + filePath = "jar:file:" + filePath + "!/"; + return filePath; + } + File file = new File(filePath); + filePath = file.getAbsolutePath(); + return filePath; + } + + public static String getWebRoot(ServletContext sc) { + if(isWeb){ + String dir = sc.getRealPath("/"); + if (dir == null) { + try { + URL url = sc.getResource("/"); + if (url != null && "file".equals(url.getProtocol())) { + dir = URLDecoder.decode(url.getFile(), "utf-8"); + } else { + throw new IllegalStateException("Can't get webroot dir, url = " + url); + } + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + return dir; + } + return getPath(appClass); + } + + public static void setNoCache(HttpServletResponse response) { + // Http 1.0 header + response.setHeader("Buffer", "false"); + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 1L); + // Http 1.1 header + response.setHeader("Cache-Control", "no-cache, no-store, max-age=0"); + } + + public static void setFileDownloadHeader(HttpServletResponse response, String fileName, String contentType) { + if (contentType == null) { + contentType = "application/x-download"; + } + response.setContentType(contentType); + // 中文文件名支持 + try { + String encodedfileName = new String(fileName.getBytes(), "ISO8859-1"); + response.setHeader("Content-Disposition", "attachment; filename=" + encodedfileName); + } catch (UnsupportedEncodingException e) { + } + } + + /** + * Print Error Message + * + * @param err + * @param code + * @param response + */ + public static void printError(Throwable err, int code, Response response) { + if (null == isDev) { + isDev = Blade.$().isDev(); + } + try { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final PrintWriter writer = new PrintWriter(baos); + + // If the developer mode, the error output to the page + if (isDev) { + writer.println(String.format(HTML, err.getClass() + " : " + err.getMessage())); + writer.println(); + err.printStackTrace(writer); + writer.println(END); + } else { + if (code == 404) { + String view404 = ViewSettings.$().getView404(); + if (StringKit.isNotBlank(view404)) { + try { + response.render(view404); + } catch (Exception e){ + LOGGER.error("", e); + } + return; + } else { + writer.write(err.getMessage()); + } + } else { + String view500 = ViewSettings.$().getView500(); + if (StringKit.isNotBlank(view500)) { + try { + response.render(view500); + } catch (Exception e){ + LOGGER.error("", e); + } + return; + } else { + writer.write(Const.VIEW_500); + } + } + } + writer.close(); + response.status(code); + InputStream body = new ByteArrayInputStream(baos.toByteArray()); + print(body, response.writer()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void print(InputStream body, PrintWriter writer) throws IOException { + print(IOKit.toString(body), writer); + } + + public static void print(String content, PrintWriter writer) throws IOException { + writer.print(content); + writer.flush(); + writer.close(); + } + + private static final String HTML = "Blade Error Page" + + "" + + "

%s

";
+
+	private static final String END = "
Blade-" + Const.VERSION + + "(Blade Framework
"; + +} diff --git a/blade-core/src/main/java/com/blade/kit/NamedThreadFactory.java b/blade-core/src/main/java/com/blade/kit/NamedThreadFactory.java new file mode 100644 index 000000000..6195170a3 --- /dev/null +++ b/blade-core/src/main/java/com/blade/kit/NamedThreadFactory.java @@ -0,0 +1,43 @@ +package com.blade.kit; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +public class NamedThreadFactory implements ThreadFactory { + + private static final AtomicInteger threadNumber = new AtomicInteger(1); + + private final AtomicInteger mThreadNum = new AtomicInteger(1); + + private final String prefix; + + private final boolean daemoThread; + + private final ThreadGroup threadGroup; + + public NamedThreadFactory() { + this("blade-threadpool-" + threadNumber.getAndIncrement(), false); + } + + public NamedThreadFactory(String prefix) { + this(prefix, false); + } + + public NamedThreadFactory(String prefix, boolean daemo) { + this.prefix = prefix + "-thread-"; + daemoThread = daemo; + SecurityManager s = System.getSecurityManager(); + threadGroup = (s == null) ? Thread.currentThread().getThreadGroup() : s.getThreadGroup(); + } + + public Thread newThread(Runnable runnable) { + String name = prefix + mThreadNum.getAndIncrement(); + Thread ret = new Thread(threadGroup, runnable, name, 0); + ret.setDaemon(daemoThread); + return ret; + } + + public ThreadGroup getThreadGroup() { + return threadGroup; + } +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/DispatcherHandler.java b/blade-core/src/main/java/com/blade/mvc/DispatcherHandler.java new file mode 100644 index 000000000..4c82a39c3 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/DispatcherHandler.java @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc; + +import com.blade.Blade; +import com.blade.Const; +import com.blade.context.WebContextHolder; +import com.blade.ioc.Ioc; +import com.blade.kit.DispatchKit; +import com.blade.kit.StringKit; +import com.blade.mvc.handler.RouteHandler; +import com.blade.mvc.http.*; +import com.blade.mvc.http.wrapper.ServletRequest; +import com.blade.mvc.http.wrapper.ServletResponse; +import com.blade.mvc.route.Route; +import com.blade.mvc.route.RouteMatcher; +import com.blade.mvc.route.Routers; +import com.blade.mvc.view.ModelAndView; +import com.blade.mvc.view.ViewSettings; +import com.blade.mvc.view.resolve.RouteViewResolve; +import com.blade.mvc.view.template.TemplateException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * Synchronous request processor + * + * @author biezhi + * @since 1.5 + */ +class DispatcherHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(DispatcherHandler.class); + + private Ioc ioc; + + private ServletContext servletContext; + + private RouteMatcher routeMatcher; + + private StaticFileFilter staticFileFilter; + + private RouteViewResolve routeViewHandler; + + public DispatcherHandler(ServletContext servletContext, Routers routers) { + this.servletContext = servletContext; + Blade blade = Blade.$(); + this.ioc = blade.ioc(); + this.routeMatcher = new RouteMatcher(routers); + this.staticFileFilter = new StaticFileFilter(blade.configuration().getResources()); + this.routeViewHandler = new RouteViewResolve(this.ioc); + } + + public void handle(HttpServletRequest httpRequest, HttpServletResponse httpResponse) { + // Create Response + Response response = new ServletResponse(httpResponse); + + try { + + // http method, GET/POST ... + String method = httpRequest.getMethod(); + + // reuqest uri + String uri = Path.getRelativePath(httpRequest.getRequestURI(), servletContext.getContextPath()); + + LOGGER.info("{}\t{}\t{}", method, uri, httpRequest.getProtocol()); + + Request request = new ServletRequest(httpRequest); + WebContextHolder.init(servletContext, request, response); + Route route = routeMatcher.getRoute(method, uri); + if (null != route) { + + if(route.getHttpMethod() != HttpMethod.valueOf(method) && route.getHttpMethod() != HttpMethod.ALL){ + render405(response, uri); + } else { + request.setRoute(route); + + // before inteceptor + List befores = routeMatcher.getBefore(uri); + boolean result = invokeInterceptor(request, response, befores); + if(result){ + // execute + this.routeHandle(request, response, route); + if(!request.isAbort()){ + // after inteceptor + List afters = routeMatcher.getAfter(uri); + invokeInterceptor(request, response, afters); + } + } + } + } else { + // Not found + render404(response, uri); + } + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + DispatchKit.printError(e, 500, response); + } + } + + private void render405(Response response, String uri) throws Exception { + String view404 = ViewSettings.$().getView404(); + if(StringKit.isNotBlank(view404)){ + ModelAndView modelAndView = new ModelAndView(view404); + modelAndView.add("viewName", uri); + response.render( modelAndView ); + } else { + response.status(HttpStatus.METHOD_NOT_ALLOWED); + response.html(String.format(Const.VIEW_405, uri)); + } + } + + /** + * 404 view render + * + * @param response response object + * @param uri 404 uri + * @throws IOException + * @throws TemplateException + */ + private void render404(Response response, String uri) throws Exception { + String view404 = ViewSettings.$().getView404(); + if(StringKit.isNotBlank(view404)){ + ModelAndView modelAndView = new ModelAndView(view404); + modelAndView.add("viewName", uri); + response.render( modelAndView ); + } else { + response.status(HttpStatus.NOT_FOUND); + response.html(String.format(Const.VIEW_404, uri)); + } + } + + /** + * Methods to perform the interceptor + * + * @param request request object + * @param response response object + * @param interceptors execute the interceptor list + * @return Return execute is ok + */ + private boolean invokeInterceptor(Request request, Response response, List interceptors) throws Exception { + for (Route route : interceptors) { + boolean flag = routeViewHandler.intercept(request, response, route); + if (!flag) { + return false; + } + } + return true; + } + + /** + * Actual routing method execution + * + * @param request request object + * @param response response object + * @param route route object + */ + private void routeHandle(Request request, Response response, Route route) throws Exception{ + Object target = route.getTarget(); + if(null == target){ + Class clazz = route.getAction().getDeclaringClass(); + target = ioc.getBean(clazz); + route.setTarget(target); + } + request.initPathParams(route.getPath()); + + // Init context + WebContextHolder.init(servletContext, request, response); + if(route.getTargetType() == RouteHandler.class){ + RouteHandler routeHandler = (RouteHandler) target; + routeHandler.handle(request, response); + } else { + routeViewHandler.handle(request, response, route); + } + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/DispatcherServlet.java b/blade-core/src/main/java/com/blade/mvc/DispatcherServlet.java new file mode 100644 index 000000000..54acd7b2f --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/DispatcherServlet.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc; + +import java.io.IOException; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.blade.Blade; +import com.blade.Const; + +/** + * Blade Core DispatcherServlet + * + * @author biezhi + * @since 1.5 + */ +public class DispatcherServlet extends HttpServlet { + + private static final long serialVersionUID = -2607425162473178733L; + + private Blade blade; + private DispatcherHandler dispatcherHandler; + + public DispatcherServlet() { + } + + @Override + public void init(ServletConfig config) throws ServletException { + blade = Blade.$(); + dispatcherHandler = new DispatcherHandler(config.getServletContext(), blade.routers()); + } + + @Override + protected void service(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException, IOException { + httpRequest.setCharacterEncoding(blade.encoding()); + httpResponse.setCharacterEncoding(blade.encoding()); + httpResponse.setHeader("server", "blade " + Const.VERSION); + dispatcherHandler.handle(httpRequest, httpResponse); + } + + @Override + public void destroy() { + super.destroy(); + } +} diff --git a/blade-core/src/main/java/com/blade/mvc/StaticFileFilter.java b/blade-core/src/main/java/com/blade/mvc/StaticFileFilter.java new file mode 100644 index 000000000..8b72d5daa --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/StaticFileFilter.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc; + +import com.blade.kit.CollectionKit; + +import java.util.Map; +import java.util.Set; + +public class StaticFileFilter { + + private Set prefixList; + private Map cache; + + public StaticFileFilter(Set prefixList) { + this.prefixList = prefixList; + this.cache = CollectionKit.newHashMap(128); + } + + public boolean isStatic(String path){ + if (cache != null) { + Boolean found = cache.get(path); + if (found != null) { + return found == Boolean.TRUE; + } + } + + for(String prefix : prefixList){ + if(path.startsWith(prefix)){ + if (cache != null) { + cache.put(path, Boolean.TRUE); + } + return true; + } + } + if (cache != null) { + cache.put(path, Boolean.FALSE); + } + return false; + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/annotation/Controller.java b/blade-core/src/main/java/com/blade/mvc/annotation/Controller.java new file mode 100644 index 000000000..a66580bbb --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/annotation/Controller.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Route class notes, identifying whether a class is routed + * + * @author biezhi + * @since 1.6.6 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Controller{ + + /** + * @return namespace + */ + String value() default "/"; + + /** + * @return route suffix + */ + String suffix() default ""; + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/annotation/CookieParam.java b/blade-core/src/main/java/com/blade/mvc/annotation/CookieParam.java new file mode 100644 index 000000000..6cc726909 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/annotation/CookieParam.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Request Cookie ParmeterAnnotation + * + * @author biezhi + * @since 1.6.6 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface CookieParam { + + String value() default ""; + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/annotation/HeaderParam.java b/blade-core/src/main/java/com/blade/mvc/annotation/HeaderParam.java new file mode 100644 index 000000000..1ccad885c --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/annotation/HeaderParam.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Request Header ParmeterAnnotation + * + * @author biezhi + * @since 1.6.6 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface HeaderParam { + + String value() default ""; + +} \ No newline at end of file diff --git a/blade-core/src/main/java/blade/annotation/Interceptor.java b/blade-core/src/main/java/com/blade/mvc/annotation/Intercept.java similarity index 83% rename from blade-core/src/main/java/blade/annotation/Interceptor.java rename to blade-core/src/main/java/com/blade/mvc/annotation/Intercept.java index a3c0a5cb4..52bdeae56 100644 --- a/blade-core/src/main/java/blade/annotation/Interceptor.java +++ b/blade-core/src/main/java/com/blade/mvc/annotation/Intercept.java @@ -1,37 +1,41 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.annotation; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 拦截器注解,写在类上 - * 如: - *
- * @Interceptor
- * public class BaseInterceptor {...}
- * 
- * @author biezhi - * @since 1.0 - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Interceptor{ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Interceptor notes, written in the class + * e.g: + *
+ * {@link Intercept}
+ * public class BaseInterceptor {...}
+ * 
+ * @author biezhi + * @since 1.5 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Intercept{ + + String value() default "/.*"; + + int sort() default 0; } \ No newline at end of file diff --git a/blade-core/src/main/java/blade/annotation/After.java b/blade-core/src/main/java/com/blade/mvc/annotation/JSON.java similarity index 70% rename from blade-core/src/main/java/blade/annotation/After.java rename to blade-core/src/main/java/com/blade/mvc/annotation/JSON.java index a18cfeba0..6e171e242 100644 --- a/blade-core/src/main/java/blade/annotation/After.java +++ b/blade-core/src/main/java/com/blade/mvc/annotation/JSON.java @@ -1,47 +1,33 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 拦截器后置事件注解,写在方法上 - * 如: - *
- *  @After("")
- *	public void after(Request request){...}
- * 
- * - * @author biezhi - * @since 1.0 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface After { - - /** - * 后置事件要拦截的URL - */ - String value() default ""; - - /** - * 允许的accept - */ - String acceptType() default "*/*"; +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Return JSON Params + * + * @author biezhi + * @since 1.6.6 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface JSON { + } \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/annotation/MultipartParam.java b/blade-core/src/main/java/com/blade/mvc/annotation/MultipartParam.java new file mode 100644 index 000000000..5a8f0053d --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/annotation/MultipartParam.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Form Multipart ParmeterAnnotation + * + * @author biezhi + * @since 1.6.6 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MultipartParam { + + String value() default "file"; + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/annotation/PathParam.java b/blade-core/src/main/java/com/blade/mvc/annotation/PathParam.java new file mode 100644 index 000000000..16241b0b0 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/annotation/PathParam.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Request Path Parameter Annotation + * + * @author biezhi + * @since 1.6.6 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface PathParam { + + String value() default ""; + + String defaultValue() default ""; + +} diff --git a/blade-core/src/main/java/com/blade/mvc/annotation/QueryParam.java b/blade-core/src/main/java/com/blade/mvc/annotation/QueryParam.java new file mode 100644 index 000000000..1d3511884 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/annotation/QueryParam.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Request Query ParmeterAnnotation + * + * @author biezhi + * @since 1.6.6 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface QueryParam { + + String value() default ""; + + boolean required() default true; + + String defaultValue() default ""; + +} \ No newline at end of file diff --git a/blade-core/src/main/java/blade/annotation/Before.java b/blade-core/src/main/java/com/blade/mvc/annotation/RestController.java similarity index 70% rename from blade-core/src/main/java/blade/annotation/Before.java rename to blade-core/src/main/java/com/blade/mvc/annotation/RestController.java index 25a5b9726..9c445ab1d 100644 --- a/blade-core/src/main/java/blade/annotation/Before.java +++ b/blade-core/src/main/java/com/blade/mvc/annotation/RestController.java @@ -1,48 +1,46 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 拦截器前置事件注解,写在方法上 - * 如: - *
- *  @Before("")
- *	public void before(Request request){...}
- *
- * - * @author biezhi - * @since 1.0 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface Before { - - /** - * 前置事件要拦截的URL - */ - String value() default ""; - - /** - * 允许的accept - */ - String acceptType() default "*/*"; - +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Return JSON Data to client. + * + * The controller all methods have this attr. + * + * @author biezhi + * @since 1.5 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RestController { + + /** + * @return namespace + */ + String value() default "/"; + + /** + * @return route suffix + */ + String suffix() default ""; + } \ No newline at end of file diff --git a/blade-core/src/main/java/blade/annotation/Route.java b/blade-core/src/main/java/com/blade/mvc/annotation/Route.java similarity index 84% rename from blade-core/src/main/java/blade/annotation/Route.java rename to blade-core/src/main/java/com/blade/mvc/annotation/Route.java index f63c73ea4..b0f8eb1ee 100644 --- a/blade-core/src/main/java/blade/annotation/Route.java +++ b/blade-core/src/main/java/com/blade/mvc/annotation/Route.java @@ -1,64 +1,59 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import blade.route.HttpMethod; - -/** - * 方法上定义的路由注解 - * - Restful routes: -
-	==========================================================================================
-	verb    path                   action          used for
-	==========================================================================================
-	GET     /users                 index 	       display a list of all books
-	GET     /users/new_form        new_form        return an HTML form for creating a new book
-	POST    /users                 create 	       create a new book
-	GET     /users/id              show            display a specific book
-	GET     /users/id/edit_form    edit_form       return an HTML form for editing a books
-	PUT     /users/id              update          update a specific book
-	DELETE 	/users/id              destroy         delete a specific book
-	
- - * @author biezhi - * @since 1.0 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface Route { - - /** - * @return 请求url - */ - String value() default ""; - - /** - * @return 请求类型 HttpMethod - */ - HttpMethod method() default HttpMethod.ALL; - - /** - * @return 需要拦截的acceptType - */ - String acceptType() default "*/*"; - +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.blade.mvc.http.HttpMethod; + +/** + * Methods defined on the route notes + * + Restful routes: +
+	==========================================================================================
+	verb    path                   action          used for
+	==========================================================================================
+	GET     /users                 index 	       display a list of all books
+	GET     /users/new_form        new_form        return an HTML form for creating a new book
+	POST    /users                 create 	       create a new book
+	GET     /users/id              show            display a specific book
+	GET     /users/id/edit_form    edit_form       return an HTML form for editing a books
+	PUT     /users/id              update          update a specific book
+	DELETE 	/users/id              destroy         delete a specific book
+	
+ + * @author biezhi + * @since 1.5 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Route { + + /** + * @return Request url + */ + String[] value() default {"/"}; + + /** + * @return Request HttpMethod + */ + HttpMethod method() default HttpMethod.ALL; + } \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/handler/MultipartHandler.java b/blade-core/src/main/java/com/blade/mvc/handler/MultipartHandler.java new file mode 100644 index 000000000..ededf20c9 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/handler/MultipartHandler.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.handler; + +import com.blade.mvc.multipart.FileItem; + +/** + * MultipartHandler + * + * @author biezhi + * @since 1.5 + */ +public interface MultipartHandler { + + void handleFormItem(String name, String value); + + void handleFileItem(String name, FileItem fileItem); + +} diff --git a/blade-core/src/main/java/com/blade/mvc/handler/RouteHandler.java b/blade-core/src/main/java/com/blade/mvc/handler/RouteHandler.java new file mode 100644 index 000000000..f6bb27643 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/handler/RouteHandler.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.handler; + +import com.blade.mvc.http.Request; +import com.blade.mvc.http.Response; + +/** + * Route Handler + * + * @author biezhi + * @since 1.5 + */ +@FunctionalInterface +public interface RouteHandler { + + void handle(Request request, Response response); + +} diff --git a/blade-core/src/main/java/com/blade/mvc/http/HttpException.java b/blade-core/src/main/java/com/blade/mvc/http/HttpException.java new file mode 100644 index 000000000..e5c5144ab --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/http/HttpException.java @@ -0,0 +1,66 @@ +package com.blade.mvc.http; + +import java.io.IOException; + +public class HttpException extends IOException { + + /** + * Serialization marker. + */ + private static final long serialVersionUID = -505306086879848229L; + + /** + * Status code. + */ + private final transient int status; + + /** + * Ctor. + * @param code HTTP status code + */ + public HttpException(final int code) { + super(Integer.toString(code)); + this.status = code; + } + + /** + * Ctor. + * @param code HTTP status code + * @param cause Cause of the problem + */ + public HttpException(final int code, final String cause) { + super(String.format("[%03d] %s", code, cause)); + this.status = code; + } + + /** + * Ctor. + * @param cause Cause of the problem + * @param code HTTP status code + */ + public HttpException(final Throwable cause, final int code) { + super(cause); + this.status = code; + } + + /** + * Ctor. + * @param code HTTP status code + * @param msg Exception message + * @param cause Cause of the problem + */ + public HttpException(final int code, final String msg, + final Throwable cause) { + super(String.format("[%03d] %s", code, msg), cause); + this.status = code; + } + + /** + * HTTP status code. + * @return Code + */ + public final int code() { + return this.status; + } + +} diff --git a/blade-core/src/main/java/blade/route/HttpMethod.java b/blade-core/src/main/java/com/blade/mvc/http/HttpMethod.java similarity index 92% rename from blade-core/src/main/java/blade/route/HttpMethod.java rename to blade-core/src/main/java/com/blade/mvc/http/HttpMethod.java index 3d3a5d430..8a9a75799 100644 --- a/blade-core/src/main/java/blade/route/HttpMethod.java +++ b/blade-core/src/main/java/com/blade/mvc/http/HttpMethod.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.route; +package com.blade.mvc.http; /** - * HTTP请求方法 + * HTTP Request Method * * @author biezhi - * @since 1.0 + * @since 1.5 */ public enum HttpMethod { ALL, GET, POST, PUT, PATCH, DELETE, HEAD, TRACE, CONNECT, OPTIONS, BEFORE, AFTER diff --git a/blade-kit/src/main/java/blade/exception/BladeException.java b/blade-core/src/main/java/com/blade/mvc/http/HttpStatus.java similarity index 53% rename from blade-kit/src/main/java/blade/exception/BladeException.java rename to blade-core/src/main/java/com/blade/mvc/http/HttpStatus.java index 7941c81a2..4c7ba727f 100644 --- a/blade-kit/src/main/java/blade/exception/BladeException.java +++ b/blade-core/src/main/java/com/blade/mvc/http/HttpStatus.java @@ -13,37 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.exception; - +package com.blade.mvc.http; /** - * blade运行时异常基类 - * + * HTTP Status + * * @author biezhi - * @since 1.0 + * @since 1.5 */ -public class BladeException extends RuntimeException { - - private static final long serialVersionUID = 1L; +public interface HttpStatus { - public BladeException() { - super(); - } - - public BladeException(String body) { - super(body); - } - - public BladeException(String body, Throwable cause){ - super(body, cause); - } - - public BladeException(Throwable cause){ - super(cause); - } - - public Throwable fillInStackTrace() { - return null; - } - + int OK = 200; + int CREATED = 201; + int ACCEPTED = 202; + int PARTIAL_INFO = 203; + int NO_RESPONSE = 204; + int MOVED = 301; + int FOUND = 302; + int METHOD = 303; + int NOT_MODIFIED = 304; + int BAD_REQUEST = 400; + int UNAUTHORIZED = 401; + int PAYMENT_REQUIRED = 402; + int FORBIDDEN = 403; + int NOT_FOUND = 404; + int METHOD_NOT_ALLOWED = 405; + int CONFLICT = 409; + int INTERNAL_ERROR = 500; + int NOT_IMPLEMENTED = 501; + int OVERLOADED = 502; + int GATEWAY_TIMEOUT = 503; + } diff --git a/blade-core/src/main/java/blade/kit/PathKit.java b/blade-core/src/main/java/com/blade/mvc/http/Path.java similarity index 59% rename from blade-core/src/main/java/blade/kit/PathKit.java rename to blade-core/src/main/java/com/blade/mvc/http/Path.java index 168c6b7f3..e1ec7b187 100644 --- a/blade-core/src/main/java/blade/kit/PathKit.java +++ b/blade-core/src/main/java/com/blade/mvc/http/Path.java @@ -13,34 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.kit; +package com.blade.mvc.http; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; -import java.util.ArrayList; import java.util.List; -import javax.servlet.http.HttpServletRequest; +import com.blade.kit.CollectionKit; +import com.blade.kit.StringKit; /** - * 路径工具类,处理request上的路径 + * Path URL * * @author biezhi - * @since 1.0 + * @since 1.5 */ -public final class PathKit { +public final class Path { + + public static final String VAR_REGEXP = ":(\\w+)"; + public static final String VAR_REPLACE = "([^#/?]+)"; public static final String ALL_PATHS = "/*"; private static final String SLASH = "/"; - private PathKit() { - } - - public static String getRelativePath(HttpServletRequest request) { + public static String getRelativePath(String path, String contextPath) { - String path = request.getRequestURI(); - String contextPath = request.getContextPath(); - path = path.substring(contextPath.length()); if (path.length() > 0) { @@ -59,14 +56,17 @@ public static String getRelativePath(HttpServletRequest request) { } public static List convertRouteToList(String route) { - String[] pathArray = route.split("/"); - List path = new ArrayList(); - for (String p : pathArray) { - if (p.length() > 0) { - path.add(p); + String[] pathArray = StringKit.split(route, "/"); + if(null != pathArray && pathArray.length > 0){ + List path = CollectionKit.newArrayList(); + for (String p : pathArray) { + if (p.length() > 0) { + path.add(p); + } } + return path; } - return path; + return CollectionKit.newArrayList(0); } public static boolean isParam(String routePart) { @@ -77,4 +77,24 @@ public static boolean isSplat(String routePart) { return routePart.equals("*"); } + public static String fixPath(String path) { + if (path == null) { + return "/"; + } + if (!path.startsWith("/")) { + path = "/" + path; + } + if (path.length() > 1 && path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + return path; + } + + public static String cleanPath(String path){ + if (path == null) { + return null; + } + return path.replaceAll("[/]+", "/"); + } + } diff --git a/blade-core/src/main/java/com/blade/mvc/http/Request.java b/blade-core/src/main/java/com/blade/mvc/http/Request.java new file mode 100644 index 000000000..7bc3ab4a0 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/http/Request.java @@ -0,0 +1,367 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.http; + +import java.io.InputStream; +import java.io.Serializable; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletContext; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +import com.blade.mvc.http.wrapper.Session; +import com.blade.mvc.multipart.FileItem; +import com.blade.mvc.route.Route; + +/** + * HTTP Request + * + * @author biezhi + * @since 1.5 + */ +public interface Request { + + /** + * @return Return HttpServletRequest + */ + HttpServletRequest raw(); + + /** + * URL parameters on the initial route, e.g:/user/23 + * @param routePath Route URL + */ + void initPathParams(String routePath); + + /** + * @return Return client request host + */ + String host(); + + /** + * @return Return request URL + */ + String url(); + + /** + * @return Return request URI + */ + String uri(); + + /** + * @return Return UA + */ + String userAgent(); + + /** + * @return Return PathInfo + */ + String pathInfo(); + + /** + * @return Return protocol + */ + String protocol(); + + /** + * @return Return servletPath + */ + String servletPath(); + + /** + * @return Return contextPath + */ + String contextPath(); + + /** + * @return Return ServletContext + */ + ServletContext context(); + + /** + * @return Return parameters on the path Map + */ + Map pathParams(); + + /** + * Get a URL parameter + * @param name Parameter name + * @return Return parameter value + */ + String pathParam(String name); + + /** + * Get a URL parameter, and returns defaultValue if it is NULL + * @param name Parameter name + * @param defaultValue Default Value + * @return Return parameter value + */ + String pathParam(String name, String defaultValue); + + /** + * Return a URL parameter for a Int type + * @param name Parameter name + * @return Return Int parameter value + */ + Integer pathParamAsInt(String name); + + /** + * Return a URL parameter for a Long type + * @param name Parameter name + * @return Return Long parameter value + */ + Long pathParamAsLong(String name); + + /** + * Return a URL parameter for a Boolean type + * @param name Parameter name + * @return Return Boolean parameter value + */ + Boolean pathParamAsBool(String name); + + /** + * @return Return query string + */ + String queryString(); + + /** + * @return Return request query Map + */ + Map querys(); + + /** + * Get a request parameter + * + * @param name Parameter name + * @return Return request parameter value + */ + String query(String name); + + /** + * Get a request parameter, if NULL is returned to defaultValue + * + * @param name Parameter name + * @param defaultValue Default value + * @return Return request parameter values + */ + String query(String name, String defaultValue); + + /** + * Returns a request parameter for a Int type + * + * @param name Parameter name + * @return Return Int parameter values + */ + Integer queryAsInt(String name); + + /** + * Returns a request parameter for a Long type + * + * @param name Parameter name + * @return Return Long parameter values + */ + Long queryAsLong(String name); + + /** + * Returns a request parameter for a Boolean type + * + * @param name Parameter name + * @return Return Boolean parameter values + */ + Boolean queryAsBool(String name); + + /** + * Returns a request parameter for a Float type + * + * @param name Parameter name + * @return Return Float parameter values + */ + Float queryAsFloat(String name); + + /** + * Returns a request parameter for a Double type + * + * @param name Parameter name + * @return Return Double parameter values + */ + Double queryAsDouble(String name); + + /** + * @return Return request method + */ + String method(); + + /** + * @return Return HttpMethod + */ + HttpMethod httpMethod(); + + /** + * @return Return server remote address + */ + String address(); + + /** + * @return Return current session + */ + Session session(); + + /** + * Return to the current or create a session + * @param create create session + * @return Return session + */ + Session session(boolean create); + + /** + * @return Return contentType + */ + String contentType(); + + /** + * @return Return Server Port + */ + int port(); + + /** + * @return Return whether to use the SSL connection + */ + boolean isSecure(); + + /** + * @return Return current request is a AJAX request + */ + boolean isAjax(); + + /** + * @return Return Cookie Map + */ + Map cookies(); + + /** + * Get String Cookie Value + * + * @param name cookie name + * @return Return Cookie Value + */ + String cookie(String name); + + /** + * Get Cookie + * + * @param name cookie name + * @return Return Cookie + */ + Cookie cookieRaw(String name); + + /** + * @return Return header information Map + */ + Map headers(); + + /** + * Get header information + * + * @param name Parameter name + * @return Return header information + */ + String header(String name); + + /** + * Setting request encoding + * + * @param encoding coded string + */ + void encoding(String encoding); + + /** + * Setting Request Attribute + * + * @param name Parameter name + * @param value Parameter Value + */ + void attribute(String name, Object value); + + /** + * Get a Request Attribute + * @param name Parameter name + * @return Return parameter value + */ + T attribute(String name); + + /** + * @return Return all Attribute in Request + */ + Set attributes(); + + /** + * @return Return the requested file list + */ + @Deprecated + FileItem[] files(); + + Map fileItems(); + + FileItem fileItem(String name); + + /** + * @return Return request body + */ + BodyParser body(); + + /** + * Serialized form data, converted to the javabean + * + * @param slug bean slug, e.g: , the slug is person + * @param clazz bean type + * @return Return converted Bean Object + */ + T model(String slug, Class clazz); + + /** + * Setting route, execute request for use + * + * @param route route object + */ + void setRoute(Route route); + + /** + * @return Return Route of the current request + */ + Route route(); + + /** + * Abort current request + */ + void abort(); + + /** + * @return Return is abort request + */ + boolean isAbort(); + + /** + * Request body interface + * @author biezhi + */ + interface BodyParser { + String asString(); + InputStream asInputStream(); + byte[] asByte(); + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/http/Response.java b/blade-core/src/main/java/com/blade/mvc/http/Response.java new file mode 100644 index 000000000..2de72b48a --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/http/Response.java @@ -0,0 +1,255 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.http; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +import com.blade.mvc.view.ModelAndView; + +/** + * HTTP Response + * + * @author biezhi + * @since 1.5 + */ +public interface Response { + + /** + * @return Return HttpServletResponse + */ + HttpServletResponse raw(); + + /** + * @return Return HTTP Status + */ + int status(); + + /** + * Setting Response Status + * + * @param status status code + * @return Return Response + */ + Response status(int status); + + /** + * @return Setting Response Status is BadRequest and Return Response + */ + Response badRequest(); + + /** + * @return Setting Response Status is unauthorized and Return Response + */ + Response unauthorized(); + + /** + * @return Setting Response Status is notFound and Return Response + */ + Response notFound(); + + /** + * @return Setting Response Status is conflict and Return Response + */ + Response conflict(); + + /** + * @return Return Response contentType + */ + String contentType(); + + /** + * Setting Response ContentType + * + * @param contentType content type + * @return Return Response + */ + Response contentType(String contentType); + + /** + * Get header + * + * @param name Header Name + * @return Return Response + */ + String header(String name); + + /** + * Setting header + * + * @param name Header Name + * @param value Header Value + * @return Return Response + */ + Response header(String name, String value); + + /** + * Setting Cookie + * + * @param cookie Cookie Object + * @return Return Response + */ + Response cookie(Cookie cookie); + + /** + * Setting Cookie + * + * @param name Cookie Name + * @param value Cookie Value + * @return Return Response + */ + Response cookie(String name, String value); + + /** + * Setting Cookie + * + * @param name Cookie Name + * @param value Cookie Value + * @param maxAge Period of validity + * @return Return Response + */ + Response cookie(String name, String value, int maxAge); + + /** + * Setting Cookie + * + * @param name Cookie Name + * @param value Cookie Value + * @param maxAge Period of validity + * @param secured Is SSL + * @return Return Response + */ + Response cookie(String name, String value, int maxAge, boolean secured); + + /** + * Setting Cookie + * + * @param path Cookie Domain Path + * @param name Cookie Name + * @param value Cookie Value + * @param maxAge Period of validity + * @param secured Is SSL + * @return Return Response + */ + Response cookie(String path, String name, String value, int maxAge, boolean secured); + + /** + * Remove Cookie + * + * @param cookie Cookie Object + * @return Return Response + */ + Response removeCookie(Cookie cookie); + + /** + * Rmove Cookie By Name + * + * @param name Cookie Name + * @return Return Response + */ + Response removeCookie(String name); + + /** + * Render by text + * + * @param text text content + * @return Return Response + */ + Response text(String text); + + /** + * Render by html + * + * @param html html content + * @return Return Response + */ + Response html(String html); + + /** + * Render by json + * + * @param json json content + * @return Return Response + */ + Response json(String json); + + /** + * Render by json + * @param bean + * @return + */ + Response json(Object bean); + + /** + * Render by xml + * + * @param xml xml content + * @return Return Response + */ + Response xml(String xml); + + /** + * @return Return OutputStream + * @throws IOException IOException + */ + OutputStream outputStream() throws IOException; + + /** + * @return Return ResponseWriter Stream + * @throws IOException + */ + PrintWriter writer() throws IOException; + + /** + * Render view + * + * @param view view page + * @return Return Response + */ + Response render(String view); + + /** + * Render view And Setting Data + * + * @param modelAndView ModelAndView object + * @return Return Response + */ + Response render(ModelAndView modelAndView); + + /** + * Redirect to Path + * + * @param path location path + */ + void redirect(String path); + + /** + * Go to Path, Under contextpath + * + * @param path redirect location + */ + void go(String path); + + /** + * @return Return Response is Write + */ + boolean isWritten(); + +} diff --git a/blade-core/src/main/java/com/blade/mvc/http/package-info.java b/blade-core/src/main/java/com/blade/mvc/http/package-info.java new file mode 100644 index 000000000..1910acdfe --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/http/package-info.java @@ -0,0 +1,4 @@ +/** + * Request And Response + */ +package com.blade.mvc.http; \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/http/wrapper/ServletRequest.java b/blade-core/src/main/java/com/blade/mvc/http/wrapper/ServletRequest.java new file mode 100644 index 000000000..2678b660a --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/http/wrapper/ServletRequest.java @@ -0,0 +1,573 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.http.wrapper; + +import com.blade.exception.BladeException; +import com.blade.kit.CollectionKit; +import com.blade.kit.IOKit; +import com.blade.kit.ObjectKit; +import com.blade.kit.StringKit; +import com.blade.mvc.handler.MultipartHandler; +import com.blade.mvc.http.HttpMethod; +import com.blade.mvc.http.Path; +import com.blade.mvc.http.Request; +import com.blade.mvc.multipart.FileItem; +import com.blade.mvc.multipart.Multipart; +import com.blade.mvc.multipart.MultipartException; +import com.blade.mvc.route.Route; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletContext; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.io.*; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * ServletRequest + * + * @author biezhi + * @since 1.5 + */ +public class ServletRequest implements Request { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServletRequest.class); + + private static final String USER_AGENT = "user-agent"; + + protected Route route; + + private HttpServletRequest request; + + /** + * path parameter eg: /user/12 + */ + private Map pathParams = null; + + /** + * query parameter eg: /user?name=jack + */ + private Map queryParams = null; + + private Map fileItems = null; + + private Session session = null; + + private boolean isAbort = false; + + public ServletRequest(HttpServletRequest request) throws MultipartException, IOException { + this.request = request; + this.pathParams = new HashMap(8); + this.queryParams = new HashMap(16); + this.fileItems = new HashMap(8); + this.init(); + } + + public void init() throws IOException, MultipartException { + /** + * retrieve multipart/form-data parameters + */ + if (Multipart.isMultipartContent(request)) { + Multipart multipart = new Multipart(); + multipart.parse(request, new MultipartHandler() { + @Override + public void handleFormItem(String name, String value) { + queryParams.put( name, value ); + } + @Override + public void handleFileItem(String name, FileItem fileItem) { + fileItems.put(name, fileItem); + } + }); + } + } + + private String join(String[] arr) { + StringBuffer ret = new StringBuffer(); + for (String item : arr) { + ret.append(',').append(item); + } + if (ret.length() > 0) { + return ret.substring(1); + } + return ret.toString(); + } + + @Override + public void initPathParams(String routePath) { + this.pathParams.clear(); + + List variables = getPathParam(routePath); + String regexPath = routePath.replaceAll(Path.VAR_REGEXP, Path.VAR_REPLACE); + + String uri = Path.getRelativePath(uri(), contextPath()); + + Matcher matcher = Pattern.compile("(?i)" + regexPath).matcher(uri); + + if(matcher.matches()){ + // start index at 1 as group(0) always stands for the entire expression + for (int i=1, len = variables.size(); i <= len; i++) { + String value = matcher.group(i); + this.pathParams.put(variables.get(i-1), value); + } + } + } + + private List getPathParam(String routePath) { + List variables = new ArrayList(8); + Matcher matcher = Pattern.compile(Path.VAR_REGEXP).matcher(routePath); + while (matcher.find()) { + variables.add(matcher.group(1)); + } + return variables; + } + + @Override + public HttpServletRequest raw() { + return request; + } + + @Override + public String host() { + return request.getServerName(); + } + + @Override + public String url() { + return request.getRequestURL().toString(); + } + + @Override + public String uri() { + return Path.fixPath(request.getRequestURI()); + } + + @Override + public String userAgent() { + return request.getHeader(USER_AGENT); + } + + @Override + public String pathInfo() { + return request.getPathInfo(); + } + + @Override + public String protocol() { + return request.getProtocol(); + } + + @Override + public String servletPath() { + return request.getServletPath(); + } + + @Override + public String contextPath() { + return request.getContextPath(); + } + + @Override + public ServletContext context() { + return request.getServletContext(); + } + + @Override + public Map pathParams() { + return pathParams; + } + + @Override + public String pathParam(String name) { + return pathParams.get(name); + } + + @Override + public String pathParam(String name, String defaultValue) { + String val = pathParams.get(name); + if(null == val){ + val = defaultValue; + } + return val; + } + + @Override + public Integer pathParamAsInt(String name) { + String value = pathParam(name); + if (StringKit.isNotBlank(value)) { + return Integer.parseInt(value); + } + return null; + } + + @Override + public Long pathParamAsLong(String name) { + String value = pathParam(name); + if (StringKit.isNotBlank(value)) { + return Long.parseLong(value); + } + return null; + } + + @Override + public Boolean pathParamAsBool(String name) { + String value = pathParam(name); + if (StringKit.isNotBlank(value)) { + return Boolean.parseBoolean(value); + } + return null; + } + + @Override + public String queryString() { + return request.getQueryString(); + } + + @Override + public Map querys() { + Map params = CollectionKit.newHashMap(8); + Map requestParams = request.getParameterMap(); + for (Map.Entry entry : requestParams.entrySet()) { + params.put( entry.getKey(), join(entry.getValue()) ); + } + params.putAll(queryParams); + return Collections.unmodifiableMap(params); + } + + @Override + public String query(String name) { + return request.getParameter(name); + } + + @Override + public String query(String name, String defaultValue) { + String val = this.query(name); + if(null == val){ + val = defaultValue; + } + return val; + } + + @Override + public Integer queryAsInt(String name) { + try { + String value = query(name); + if(StringKit.isBlank(value)){ + return null; + } + return Integer.valueOf(value); + } catch (Exception e){ + throw new BladeException(e.getMessage()); + } + } + + @Override + public Long queryAsLong(String name) { + try { + String value = query(name); + if(StringKit.isBlank(value)){ + return null; + } + return Long.valueOf(value); + } catch (Exception e){ + throw new BladeException(e.getMessage()); + } + } + + @Override + public Boolean queryAsBool(String name) { + String value = query(name); + if(StringKit.isBlank(value)){ + return null; + } + if(StringKit.isBoolean(value)){ + return Boolean.valueOf(value); + } else { + throw new BladeException("for string \"" + value + "\""); + } + } + + @Override + public Float queryAsFloat(String name) { + try { + String value = query(name); + if(StringKit.isBlank(value)){ + return null; + } + return Float.valueOf(value); + } catch (Exception e){ + throw new BladeException(e.getMessage()); + } + } + + @Override + public Double queryAsDouble(String name) { + try { + String value = query(name); + if(StringKit.isBlank(value)){ + return null; + } + return Double.valueOf(value); + } catch (Exception e){ + throw new BladeException(e.getMessage()); + } + } + + @Override + public String method() { + return request.getMethod(); + } + + @Override + public HttpMethod httpMethod() { + return HttpMethod.valueOf(request.getMethod().toUpperCase()); + } + + @Override + public String address() { + return request.getRemoteAddr(); + } + + @Override + public Session session() { + if (session == null) { + session = new Session(request.getSession()); + } + return session; + } + + @Override + public Session session(boolean create) { + if (session == null) { + HttpSession httpSession = request.getSession(create); + if (httpSession != null) { + session = new Session(httpSession); + } + } + return session; + } + + @Override + public void attribute(String name, Object value) { + request.setAttribute(name, value); + } + + @SuppressWarnings("unchecked") + @Override + public T attribute(String name) { + Object object = request.getAttribute(name); + if(null != object){ + return (T) object; + } + return null; + } + + @Override + public Set attributes() { + Set attrList = new HashSet(8); + Enumeration attributes = request.getAttributeNames(); + while (attributes.hasMoreElements()) { + attrList.add(attributes.nextElement()); + } + return attrList; + } + + @Override + public String contentType() { + return request.getContentType(); + } + + @Override + public int port() { + return request.getServerPort(); + } + + @Override + public boolean isSecure() { + return request.isSecure(); + } + + @Override + public boolean isAjax() { + return null != header("x-requested-with") && "XMLHttpRequest".equals(header("x-requested-with")); + } + + @Override + public Map cookies() { + javax.servlet.http.Cookie[] servletCookies = request.getCookies(); + Map cookies = CollectionKit.newHashMap(8); + for (javax.servlet.http.Cookie c : servletCookies) { + cookies.put( c.getName(), map(c) ); + } + return Collections.unmodifiableMap(cookies); + } + + private Cookie map(Cookie servletCookie) { + Cookie cookie = new Cookie(servletCookie.getName(), servletCookie.getValue()); + cookie.setMaxAge(servletCookie.getMaxAge()); + cookie.setHttpOnly(servletCookie.isHttpOnly()); + String path = servletCookie.getPath(); + if(null != path){ + cookie.setPath(path); + } + String domain = servletCookie.getDomain(); + if(null != domain){ + cookie.setDomain(domain); + } + cookie.setSecure(servletCookie.getSecure()); + return cookie; + } + + @Override + public String cookie(String name) { + Cookie cookie = cookieRaw(name); + if(null != cookie){ + return cookie.getValue(); + } + return null; + } + + @Override + public Cookie cookieRaw(String name) { + javax.servlet.http.Cookie[] servletCookies = request.getCookies(); + if (servletCookies == null) { + return null; + } + for (javax.servlet.http.Cookie c : servletCookies) { + if (c.getName().equals(name)) { + return map(c); + } + } + return null; + } + + @Override + public Map headers() { + Enumeration servletHeaders = request.getHeaderNames(); + Map headers = new HashMap(16); + while(servletHeaders.hasMoreElements()) { + String headerName = servletHeaders.nextElement(); + headers.put(headerName, request.getHeader(headerName)); + } + return headers; + } + + @Override + public String header(String name) { + return request.getHeader(name); + } + + @Override + public void encoding(String encoding) { + try { + request.setCharacterEncoding(encoding); + } catch (UnsupportedEncodingException e) { + LOGGER.error(e.getMessage(), e); + } + } + + @Override + public void setRoute(Route route) { + this.route = route; + initPathParams(route.getPath()); + } + + @Override + public Route route() { + return this.route; + } + + @Override + public void abort() { + this.isAbort = true; + } + + @Override + public boolean isAbort() { + return this.isAbort; + } + + @Override + public T model(String slug, Class clazz) { + if(StringKit.isNotBlank(slug) && null != clazz){ + return ObjectKit.model(slug, clazz, querys()); + } + return null; + } + + @Override + public FileItem[] files() { + return this.fileItems.values().toArray(new FileItem[fileItems.size()]); + } + + @Override + public Map fileItems() { + return this.fileItems; + } + + @Override + public FileItem fileItem(String name) { + return this.fileItems.get(name); + } + + @Override + public BodyParser body() { + return new BodyParser() { + @Override + public String asString() { + try { + BufferedReader reader = new BufferedReader( new InputStreamReader(request.getInputStream()) ); + StringBuilder sb = new StringBuilder(); + String line = reader.readLine(); + while (null != line) { + sb.append(line).append("\r\n"); + line = reader.readLine(); + } + reader.close(); + return sb.toString(); + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + } + return null; + } + + @Override + public InputStream asInputStream() { + try { + return request.getInputStream(); + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + } + return null; + } + + @Override + public byte[] asByte() { + try { + return IOKit.toByteArray(request.getInputStream()); + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + } + return null; + } + }; + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/http/wrapper/ServletResponse.java b/blade-core/src/main/java/com/blade/mvc/http/wrapper/ServletResponse.java new file mode 100644 index 000000000..0b512c72b --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/http/wrapper/ServletResponse.java @@ -0,0 +1,325 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.http.wrapper; + +import com.blade.context.WebContextHolder; +import com.blade.kit.Assert; +import com.blade.kit.DispatchKit; +import com.blade.mvc.http.HttpStatus; +import com.blade.mvc.http.Path; +import com.blade.mvc.http.Request; +import com.blade.mvc.http.Response; +import com.blade.mvc.view.ModelAndView; +import com.blade.mvc.view.ViewSettings; +import com.blade.mvc.view.resolve.JSONParser; +import com.blade.mvc.view.template.TemplateEngine; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; + +/** + * ServletResponse + * + * @author biezhi + * @since 1.5 + */ +public class ServletResponse implements Response { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServletResponse.class); + + private HttpServletResponse response; + + private boolean written = false; + + private ViewSettings viewSettings; + + private TemplateEngine templateEngine; + + private JSONParser jsonParser; + + public ServletResponse(HttpServletResponse response) { + this.response = response; + this.viewSettings = ViewSettings.$(); + this.templateEngine = viewSettings.templateEngine(); + this.jsonParser = viewSettings.JSONParser(); + } + + @Override + public HttpServletResponse raw() { + return response; + } + + @Override + public int status() { + return response.getStatus(); + } + + @Override + public Response status(int status) { + response.setStatus(status); + return this; + } + + @Override + public Response badRequest() { + response.setStatus(HttpStatus.BAD_REQUEST); + return this; + } + + @Override + public Response unauthorized() { + response.setStatus(HttpStatus.UNAUTHORIZED); + return this; + } + + @Override + public Response notFound() { + response.setStatus(HttpStatus.NOT_FOUND); + return this; + } + + @Override + public Response conflict() { + response.setStatus(HttpStatus.CONFLICT); + return this; + } + + @Override + public String contentType() { + return response.getContentType(); + } + + @Override + public Response contentType(String contentType) { + response.setContentType(contentType); + return this; + } + + @Override + public String header(String name) { + return response.getHeader(name); + } + + @Override + public Response header(String name, String value) { + response.setHeader(name, value); + return this; + } + + @Override + public Response cookie(Cookie cookie) { + response.addCookie(cookie); + return this; + } + + @Override + public Response cookie(String name, String value) { + return cookie(name, value); + } + + @Override + public Response cookie(String name, String value, int maxAge) { + return cookie(name, value, maxAge, false); + } + + @Override + public Response cookie(String name, String value, int maxAge, boolean secured) { + return cookie(null, name, value, maxAge, secured); + } + + @Override + public Response cookie(String path, String name, String value, int maxAge, boolean secured) { + Cookie cookie = new Cookie(name, value); + if(null != path){ + cookie.setPath(path); + } + cookie.setMaxAge(maxAge); + cookie.setSecure(secured); + response.addCookie(cookie); + return this; + } + + @Override + public Response removeCookie(Cookie cookie) { + cookie.setMaxAge(0); + response.addCookie(map(cookie)); + return this; + } + + javax.servlet.http.Cookie map(Cookie cookie) { + javax.servlet.http.Cookie servletCookie = new javax.servlet.http.Cookie(cookie.getName(), cookie.getValue()); + servletCookie.setMaxAge(cookie.getMaxAge()); + if (null != cookie.getPath()) { + servletCookie.setPath(cookie.getPath()); + } + if (null != cookie.getDomain()) { + servletCookie.setDomain(cookie.getDomain()); + } + servletCookie.setHttpOnly(cookie.isHttpOnly()); + servletCookie.setSecure(cookie.getSecure()); + return servletCookie; + } + + @Override + public Response removeCookie(String name) { + Cookie cookie = new Cookie(name, ""); + cookie.setMaxAge(0); + response.addCookie(cookie); + return this; + } + + @Override + public Response text(String text) { + try { + this.header("Cache-Control", "no-cache"); + this.contentType("text/plain;charset=utf-8"); + DispatchKit.print(text, response.getWriter()); + this.written = true; + return this; + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + } + return null; + } + + @Override + public Response html(String html) { + try { + this.header("Cache-Control", "no-cache"); + this.contentType("text/html;charset=utf-8"); + + DispatchKit.print(html, response.getWriter()); + this.written = true; + return this; + } catch (UnsupportedEncodingException e) { + LOGGER.error(e.getMessage(), e); + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + } + return null; + } + + @Override + public Response json(String json) { + Request request = WebContextHolder.request(); + String userAgent = request.userAgent(); + if (userAgent.contains("MSIE")) { + this.contentType("text/html;charset=utf-8"); + } else { + this.contentType("application/json;charset=utf-8"); + } + try { + this.header("Cache-Control", "no-cache"); + DispatchKit.print(json, response.getWriter()); + this.written = true; + return this; + } catch (UnsupportedEncodingException e) { + LOGGER.error(e.getMessage(), e); + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + } + return null; + } + + @Override + public Response json(Object bean) { + return this.json(jsonParser.toJSONSting(bean)); + } + + @Override + public Response xml(String xml) { + try { + this.header("Cache-Control", "no-cache"); + this.contentType("text/xml;charset=utf-8"); + DispatchKit.print(xml, response.getWriter()); + this.written = true; + return this; + } catch (UnsupportedEncodingException e) { + LOGGER.error(e.getMessage(), e); + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + } + return null; + } + + @Override + public ServletOutputStream outputStream() throws IOException { + return response.getOutputStream(); + } + + @Override + public PrintWriter writer() throws IOException { + return response.getWriter(); + } + + @Override + public Response render(String view) { + String viewPath = Path.cleanPath(view); + ModelAndView modelAndView = new ModelAndView(viewPath); + try { + templateEngine.render(modelAndView, response.getWriter()); + } catch (Exception e){ + LOGGER.error("", e); + } + return this; + } + + @Override + public Response render(ModelAndView modelAndView) { + Assert.notBlank(modelAndView.getView(), "view not is null"); + + String viewPath = Path.cleanPath(modelAndView.getView()); + modelAndView.setView(viewPath); + try { + templateEngine.render(modelAndView, response.getWriter()); + } catch (Exception e){ + LOGGER.error("", e); + } + return this; + } + + @Override + public void redirect(String path) { + try { + response.sendRedirect(path); + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + } + } + + @Override + public void go(String path) { + try { + String ctx = WebContextHolder.servletContext().getContextPath(); + String location = Path.fixPath(ctx + path); + response.sendRedirect(location); + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + } + } + + @Override + public boolean isWritten() { + return written; + } + +} diff --git a/blade-core/src/main/java/blade/servlet/Session.java b/blade-core/src/main/java/com/blade/mvc/http/wrapper/Session.java similarity index 95% rename from blade-core/src/main/java/blade/servlet/Session.java rename to blade-core/src/main/java/com/blade/mvc/http/wrapper/Session.java index 1942acd2a..3ad5d5409 100644 --- a/blade-core/src/main/java/blade/servlet/Session.java +++ b/blade-core/src/main/java/com/blade/mvc/http/wrapper/Session.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.servlet; +package com.blade.mvc.http.wrapper; import java.util.Enumeration; import java.util.Set; @@ -22,16 +22,16 @@ import javax.servlet.http.HttpSession; /** - * HttpSession包装 + * HttpSession Wrapper * * @author biezhi - * @since 1.0 + * @since 1.5 */ public class Session { private HttpSession session; - Session(HttpSession session) { + public Session(HttpSession session) { if (session == null) { throw new IllegalArgumentException("session cannot be null"); } diff --git a/blade-core/src/main/java/com/blade/mvc/http/wrapper/package-info.java b/blade-core/src/main/java/com/blade/mvc/http/wrapper/package-info.java new file mode 100644 index 000000000..f4c0c1203 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/http/wrapper/package-info.java @@ -0,0 +1,4 @@ +/** + * Servlet Wrapper + */ +package com.blade.mvc.http.wrapper; \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/interceptor/Interceptor.java b/blade-core/src/main/java/com/blade/mvc/interceptor/Interceptor.java new file mode 100644 index 000000000..0d8a52724 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/interceptor/Interceptor.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.interceptor; + +import com.blade.mvc.http.Request; +import com.blade.mvc.http.Response; + +/** + * Interceptor, In the routing block before and after the execution. + * + * @author biezhi + * @since 1.5 + */ +public interface Interceptor { + + boolean before(Request request, Response response); + + boolean after(Request request, Response response); + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/interceptor/package-info.java b/blade-core/src/main/java/com/blade/mvc/interceptor/package-info.java new file mode 100644 index 000000000..c4eac9118 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/interceptor/package-info.java @@ -0,0 +1,4 @@ +/** + * Blade Interceptor + */ +package com.blade.mvc.interceptor; \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/multipart/FileItem.java b/blade-core/src/main/java/com/blade/mvc/multipart/FileItem.java new file mode 100644 index 000000000..90840f794 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/multipart/FileItem.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.multipart; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +/** + * HTTP multipart/form-data Request + * + * @author biezhi + * @since 1.5 + */ +public class FileItem { + + private String name; + + private String fileName; + + private String contentType; + + private long contentLength; + + private File file; + + private Map headers; + + public FileItem(String fieldName, String fileName, String contentType, long contentLength, File file, Map headers) { + + this.fileName = fileName; + this.contentType = contentType; + this.contentLength = contentLength; + this.file = file; + this.headers = headers; + if (headers == null) { + this.headers = new HashMap(); + } + } + + + public String name() { + return name; + } + + public String fileName() { + return fileName; + } + + public String contentType() { + return contentType; + } + + public long contentLength() { + return contentLength; + } + + public File file() { + return file; + } + + public Map headers() { + return headers; + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/multipart/Multipart.java b/blade-core/src/main/java/com/blade/mvc/multipart/Multipart.java new file mode 100644 index 000000000..cb8ee5d34 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/multipart/Multipart.java @@ -0,0 +1,328 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.multipart; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import com.blade.kit.IOKit; +import com.blade.mvc.handler.MultipartHandler; + +/** + * Multipart + * + * @author biezhi + * @since 1.5 + */ +public class Multipart { + + public static final String CONTENT_TYPE = "Content-Type"; + + public static final String CONTENT_DISPOSITION = "Content-Disposition"; + + public static final String CONTENT_LENGTH = "Content-Length"; + + public static final String FORM_DATA = "form-data"; + + public static final String ATTACHMENT = "attachment"; + + public static final String MULTIPART = "multipart/"; + + public static final String MULTIPART_MIXED = "multipart/mixed"; + + public static boolean isMultipartContent(HttpServletRequest request) { + if (!"post".equals(request.getMethod().toLowerCase())) { + return false; + } + + String contentType = request.getContentType(); + if (contentType == null) { + return false; + } + if (contentType.toLowerCase().startsWith(MULTIPART)) { + return true; + } + + return false; + } + + public void parse(HttpServletRequest request, MultipartHandler partHandler) throws IOException, MultipartException { + if (!isMultipartContent(request)) { + throw new MultipartException("Not a multipart content. The HTTP method should be 'POST' and the " + + "Content-Type 'multipart/form-data' or 'multipart/mixed'."); + } + + InputStream inputStream = request.getInputStream(); + + String contentType = request.getContentType(); + String charEncoding = request.getCharacterEncoding(); + + byte[] boundary = getBoundary(contentType); + if (boundary == null) { + throw new MultipartException("the request was rejected because no multipart boundary was found"); + } + + // create a multipart reader + MultipartReader multipartReader = new MultipartReader(inputStream, boundary); + multipartReader.setHeaderEncoding(charEncoding); + + String currentFieldName = null; + boolean skipPreamble = true; + + for (;;) { + boolean nextPart; + if (skipPreamble) { + nextPart = multipartReader.skipPreamble(); + } else { + nextPart = multipartReader.readBoundary(); + } + if (!nextPart) { + if (currentFieldName == null) { + // outer multipart terminated -> no more data + return; + } + // inner multipart terminated -> return to parsing the outer + multipartReader.setBoundary(boundary); + currentFieldName = null; + continue; + } + + String headersString = multipartReader.readHeaders(); + Map headers = getHeadersMap(headersString); + + if (currentFieldName == null) { + + // we're parsing the outer multipart + String fieldName = getFieldName( headers.get(CONTENT_DISPOSITION) ); + if (fieldName != null) { + + String partContentType = headers.get(CONTENT_TYPE); + if (partContentType != null && partContentType.toLowerCase().startsWith(MULTIPART_MIXED)) { + + // multiple files associated with this field name + currentFieldName = fieldName; + multipartReader.setBoundary( getBoundary(partContentType)); + skipPreamble = true; + + continue; + } + + String fileName = getFileName( headers.get(CONTENT_DISPOSITION) ); + if (fileName == null) { + // call the part handler + String value = IOKit.toString(multipartReader.newInputStream()); + partHandler.handleFormItem(fieldName, value); + } else { + + // create the temp file + File tempFile = createTempFile(multipartReader); + + // call the part handler + FileItem fileItem = new FileItem(fieldName, fileName, partContentType, tempFile.length(), tempFile, headers); + partHandler.handleFileItem(fieldName, fileItem); + } + + continue; + } + } else { + String fileName = getFileName( headers.get(CONTENT_DISPOSITION) ); + String partContentType = headers.get(CONTENT_TYPE); + if (fileName != null) { + + // create the temp file + File tempFile = createTempFile(multipartReader); + + // call the part handler + FileItem fileItem = new FileItem(currentFieldName, fileName, partContentType, tempFile.length(), + tempFile, headers); + partHandler.handleFileItem(currentFieldName, fileItem); + continue; + } + } + multipartReader.discardBodyData(); + } + + } + + private File createTempFile(MultipartReader multipartReader) throws IOException { + File tempFile = File.createTempFile("com.blade.file_", null); + FileOutputStream outputStream = null; + try { + outputStream = new FileOutputStream(tempFile); + copy( multipartReader.newInputStream(), outputStream ); + } finally { + if (outputStream != null) { + try { outputStream.close(); } catch (Exception e) {} + } + } + + return tempFile; + } + + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + private static final int EOF = -1; + + private long copy(InputStream input, OutputStream output) throws IOException { + long count = 0; + int n = 0; + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + protected Map getHeadersMap(String headerPart) { + final int len = headerPart.length(); + final Map headers = new HashMap(); + + int start = 0; + for (;;) { + int end = parseEndOfLine(headerPart, start); + if (start == end) { + break; + } + String header = headerPart.substring(start, end); + start = end + 2; + while (start < len) { + int nonWs = start; + while (nonWs < len) { + char c = headerPart.charAt(nonWs); + if (c != ' ' && c != '\t') { + break; + } + ++nonWs; + } + if (nonWs == start) { + break; + } + // continuation line found + end = parseEndOfLine(headerPart, nonWs); + header += " " + headerPart.substring(nonWs, end); + start = end + 2; + } + + // parse header line + final int colonOffset = header.indexOf(':'); + if (colonOffset == -1) { + // this header line is malformed, skip it. + continue; + } + String headerName = header.substring(0, colonOffset).trim(); + String headerValue = header.substring(header.indexOf(':') + 1).trim(); + + if (headers.containsKey(headerName)) { + headers.put( headerName, headers.get(headerName) + "," + headerValue ); + } else { + headers.put(headerName, headerValue); + } + } + + return headers; + } + + private int parseEndOfLine(String headerPart, int end) { + int index = end; + for (;;) { + int offset = headerPart.indexOf('\r', index); + if (offset == -1 || offset + 1 >= headerPart.length()) { + throw new IllegalStateException("Expected headers to be terminated by an empty line."); + } + if (headerPart.charAt(offset + 1) == '\n') { + return offset; + } + index = offset + 1; + } + } + + private String getFieldName(String contentDisposition) { + String fieldName = null; + + if (contentDisposition != null && contentDisposition.toLowerCase().startsWith(FORM_DATA)) { + ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + + // parameter parser can handle null input + Map params = parser.parse(contentDisposition, ';'); + fieldName = (String) params.get("name"); + if (fieldName != null) { + fieldName = fieldName.trim(); + } + } + + return fieldName; + } + + protected byte[] getBoundary(String contentType) { + ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + // Parameter parser can handle null input + Map params = parser.parse(contentType, new char[] {';', ','}); + String boundaryStr = (String) params.get("boundary"); + + if (boundaryStr == null) { + return null; + } + + byte[] boundary; + try { + boundary = boundaryStr.getBytes("ISO-8859-1"); + } catch (UnsupportedEncodingException e) { + boundary = boundaryStr.getBytes(); + } + return boundary; + } + + private String getFileName(String contentDisposition) { + String fileName = null; + + if (contentDisposition != null) { + String cdl = contentDisposition.toLowerCase(); + + if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) { + + ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + + // parameter parser can handle null input + Map params = parser.parse(contentDisposition, ';'); + if (params.containsKey("filename")) { + fileName = (String) params.get("filename"); + if (fileName != null) { + fileName = fileName.trim(); + } else { + // even if there is no value, the parameter is present, + // so we return an empty file name rather than no file + // name. + fileName = ""; + } + } + } + } + + return fileName; + } + +} diff --git a/blade-core/src/main/java/blade/ioc/SingleBean.java b/blade-core/src/main/java/com/blade/mvc/multipart/MultipartException.java similarity index 62% rename from blade-core/src/main/java/blade/ioc/SingleBean.java rename to blade-core/src/main/java/com/blade/mvc/multipart/MultipartException.java index 5e0695d7a..5244857bd 100644 --- a/blade-core/src/main/java/blade/ioc/SingleBean.java +++ b/blade-core/src/main/java/com/blade/mvc/multipart/MultipartException.java @@ -1,36 +1,44 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.ioc; - -/** - * 单例bean对象,容器中只存在一个bean实例 - * - * @author biezhi - * @since 1.0 - */ -public class SingleBean extends AbstractBeanFactory { - - @Override - public Object getBean(String className) { - return container.getBean(className, Scope.SINGLE); - } - - @Override - public Object getBean(Class clazz) { - return container.getBean(clazz, Scope.SINGLE); - } - -} \ No newline at end of file +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.multipart; + +/** + * + * MultipartException + * + * @author biezhi + * @since 1.5 + */ +public class MultipartException extends Exception { + + private static final long serialVersionUID = 1L; + + public MultipartException() { + } + + public MultipartException(String message) { + super(message); + } + + public MultipartException(Throwable cause) { + super(cause); + } + + public MultipartException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/multipart/MultipartReader.java b/blade-core/src/main/java/com/blade/mvc/multipart/MultipartReader.java new file mode 100644 index 000000000..0214d0b92 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/multipart/MultipartReader.java @@ -0,0 +1,768 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package com.blade.mvc.multipart; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +/** + *

+ * Low level API for processing file uploads. + * + *

+ * This class can be used to process data streams conforming to MIME 'multipart' format as defined in RFC 1867. Arbitrarily large amounts of data in the stream can be + * processed under constant memory usage. + * + *

+ * The format of the stream is defined in the following way:
+ * + * + * multipart-body := preamble 1*encapsulation close-delimiter epilogue
+ * encapsulation := delimiter body CRLF
+ * delimiter := "--" boundary CRLF
+ * close-delimiter := "--" boudary "--"
+ * preamble := <ignore>
+ * epilogue := <ignore>
+ * body := header-part CRLF body-part
+ * header-part := 1*header CRLF
+ * header := header-name ":" header-value
+ * header-name := <printable ascii characters except ":">
+ * header-value := <any ascii characters except CR & LF>
+ * body-data := <arbitrary data>
+ *
+ * + *

+ * Note that body-data can contain another mulipart entity. There is limited support for single pass processing of such + * nested streams. The nested stream is required to have a boundary token of the same length as the + * parent stream (see {@link #setBoundary(byte[])}). + * + *

+ * Here is an example of usage of this class.
+ * + *

+ * try {
+ * 	MultipartStream multipartStream = new MultipartStream(input, boundary);
+ * 	boolean nextPart = multipartStream.skipPreamble();
+ * 	OutputStream output;
+ * 	while (nextPart) {
+ * 		String header = multipartStream.readHeaders();
+ * 		// process headers
+ * 		// create some output stream
+ * 		multipartStream.readBodyData(output);
+ * 		nextPart = multipartStream.readBoundary();
+ * 	}
+ * } catch (MultipartStream.MalformedStreamException e) {
+ * 	// the stream failed to follow required syntax
+ * } catch (IOException e) {
+ * 	// a read or write error occurred
+ * }
+ * 
+ * + * @author Rafal Krzewski + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @version $Id: MultipartStream.java 735374 2009-01-18 02:18:45Z jochen $ + */ +public class MultipartReader { + + /** + * The Carriage Return ASCII character value. + */ + public static final byte CR = 0x0D; + + /** + * The Line Feed ASCII character value. + */ + public static final byte LF = 0x0A; + + /** + * The dash (-) ASCII character value. + */ + public static final byte DASH = 0x2D; + + /** + * The maximum length of header-part that will be processed (10 kilobytes = 10240 bytes.). + */ + public static final int HEADER_PART_SIZE_MAX = 10240; + + /** + * The default length of the buffer used for processing a request. + */ + protected static final int DEFAULT_BUFSIZE = 4096; + + /** + * A byte sequence that marks the end of header-part (CRLFCRLF). + */ + protected static final byte[] HEADER_SEPARATOR = { CR, LF, CR, LF }; + + /** + * A byte sequence that that follows a delimiter that will be followed by an encapsulation (CRLF). + */ + protected static final byte[] FIELD_SEPARATOR = { CR, LF }; + + /** + * A byte sequence that that follows a delimiter of the last encapsulation in the stream (--). + */ + protected static final byte[] STREAM_TERMINATOR = { DASH, DASH }; + + /** + * A byte sequence that precedes a boundary (CRLF--). + */ + protected static final byte[] BOUNDARY_PREFIX = { CR, LF, DASH, DASH }; + + /** + * The input stream from which data is read. + */ + private final InputStream input; + + /** + * The length of the boundary token plus the leading CRLF--. + */ + private int boundaryLength; + + /** + * The amount of data, in bytes, that must be kept in the buffer in order to detect delimiters reliably. + */ + private int keepRegion; + + /** + * The byte sequence that partitions the stream. + */ + private byte[] boundary; + + /** + * The length of the buffer used for processing the request. + */ + private final int bufSize; + + /** + * The buffer used for processing the request. + */ + private final byte[] buffer; + + /** + * The index of first valid character in the buffer.
+ * 0 <= head < bufSize + */ + private int head; + + /** + * The index of last valid characer in the buffer + 1.
+ * 0 <= tail <= bufSize + */ + private int tail; + + /** + * The content encoding to use when reading headers. + */ + private String headerEncoding; + + /** + *

+ * Constructs a MultipartStream with a custom size buffer. + * + *

+ * Note that the buffer must be at least big enough to contain the boundary string, plus 4 characters for CR/LF and + * double dash, plus at least one byte of data. Too small a buffer size setting will degrade performance. + * + * @param input The InputStream to serve as a data source. + * @param boundary The token used for dividing the stream into encapsulations. + * @param bufSize The size of the buffer to be used, in bytes. + * + */ + public MultipartReader(InputStream input, byte[] boundary, int bufSize) { + this.input = input; + this.bufSize = bufSize; + this.buffer = new byte[bufSize]; + + // prepend CR/LF to the boundary to chop trailing CR/LF from body-data tokens. + this.boundary = new byte[boundary.length + BOUNDARY_PREFIX.length]; + this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length; + this.keepRegion = this.boundary.length; + System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0, BOUNDARY_PREFIX.length); + System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length); + + head = 0; + tail = 0; + } + + /** + * Constructs a MultipartStream with a default size buffer. + * + * @param input The InputStream to serve as a data source. + * @param boundary The token used for dividing the stream into encapsulations. + */ + public MultipartReader(InputStream input, byte[] boundary) { + this(input, boundary, DEFAULT_BUFSIZE); + } + + /** + * Retrieves the character encoding used when reading the headers of an individual part. When not specified, or + * null, the platform default encoding is used. + * + * @return The encoding used to read part headers. + */ + public String getHeaderEncoding() { + return headerEncoding; + } + + /** + * Specifies the character encoding to be used when reading the headers of individual parts. When not specified, or + * null, the platform default encoding is used. + * + * @param encoding The encoding used to read part headers. + */ + public void setHeaderEncoding(String encoding) { + headerEncoding = encoding; + } + + /** + * Reads a byte from the buffer, and refills it as necessary. + * + * @return The next byte from the input stream. + * @throws IOException if there is no more data available. + */ + public byte readByte() throws IOException { + + // buffer depleted ? + if (head == tail) { + head = 0; + // Refill. + tail = input.read(buffer, head, bufSize); + if (tail == -1) { + // no more data available. + throw new IOException("No more data is available"); + } + } + + return buffer[head++]; + } + + /** + * Skips a boundary token, and checks whether more encapsulations are contained in the + * stream. + * + * @return true if there are more encapsulations in this stream; false otherwise. + * + * @throws MalformedStreamException if the stream ends unexpecetedly or fails to follow required syntax. + */ + public boolean readBoundary() throws MalformedStreamException { + byte[] marker = new byte[2]; + boolean nextChunk = false; + + head += boundaryLength; + try { + marker[0] = readByte(); + marker[1] = readByte(); + if (arrayequals(marker, STREAM_TERMINATOR, 2)) { + nextChunk = false; + } else if (arrayequals(marker, FIELD_SEPARATOR, 2)) { + nextChunk = true; + } else { + throw new MalformedStreamException("Unexpected characters follow a boundary"); + } + } catch (IOException e) { + throw new MalformedStreamException("Stream ended unexpectedly"); + } + return nextChunk; + } + + /** + *

Changes the boundary token used for partitioning the stream.

+ * + *

This method allows single pass processing of nested multipart streams.

+ * + *

The boundary token of the nested stream is required to be of the same length as the boundary token + * in parent stream.

+ * + *

Restoring the parent stream boundary token after processing of a nested stream is left to the application.

+ * + * @param boundary The boundary to be used for parsing of the nested stream. + * + * @throws IllegalBoundaryException if the boundary has a different length than the one being currently + * parsed. + */ + public void setBoundary(byte[] boundary) throws IllegalBoundaryException { + + if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) { + throw new IllegalBoundaryException("The length of a boundary token can not be changed"); + } + + System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length); + } + + /** + *

Reads the header-part of the current encapsulation.

+ * + *

Headers are returned verbatim to the input stream, including the trailing CRLF marker. Parsing is + * left to the application.

+ * + * @return The header-part of the current encapsulation. + * @throws MalformedStreamException if the stream ends unexpecetedly. + */ + public String readHeaders() throws MalformedStreamException { + int i = 0; + byte b; + // to support multi-byte characters + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int size = 0; + while (i < HEADER_SEPARATOR.length) { + try { + b = readByte(); + } catch (IOException e) { + throw new MalformedStreamException("Stream ended unexpectedly"); + } + if (++size > HEADER_PART_SIZE_MAX) { + throw new MalformedStreamException("Header section has more than " + HEADER_PART_SIZE_MAX + + " bytes (maybe it is not properly terminated)"); + } + if (b == HEADER_SEPARATOR[i]) { + i++; + } else { + i = 0; + } + baos.write(b); + } + + String headers = null; + if (headerEncoding != null) { + try { + headers = baos.toString(headerEncoding); + } catch (UnsupportedEncodingException e) { + // fall back to platform default if specified encoding is not supported. + headers = baos.toString(); + } + } else { + headers = baos.toString(); + } + + return headers; + } + + /** + *

Reads body-data from the current encapsulation and writes its contents into the output + * Stream.

+ * + *

Arbitrary large amounts of data can be processed by this method using a constant size buffer.

+ * + * @param output The Stream to write data into. May be null, in which case this method is equivalent to + * {@link #discardBodyData()}. + * + * @return the amount of data written. + * + * @throws MalformedStreamException if the stream ends unexpectedly. + * @throws IOException if an i/o error occurs. + */ + public int readBodyData(OutputStream output) throws MalformedStreamException, IOException { + final InputStream istream = newInputStream(); + return (int) Streams.copy(istream, output, false); + } + + /** + * Creates a new {@link ItemInputStream}. + * + * @return A new instance of {@link ItemInputStream}. + */ + protected ItemInputStream newInputStream() { + return new ItemInputStream(); + } + + /** + *

Reads body-data from the current encapsulation and discards it.

+ * + *

Use this method to skip encapsulations you don't need or don't understand.

+ * + * @return The amount of data discarded. + * @throws MalformedStreamException if the stream ends unexpectedly. + * @throws IOException if an i/o error occurs. + */ + public int discardBodyData() throws MalformedStreamException, IOException { + return readBodyData(null); + } + + /** + * Finds the beginning of the first encapsulation. + * + * @return true if an encapsulation was found in the stream. + * @throws IOException if an i/o error occurs. + */ + public boolean skipPreamble() throws IOException { + + // first delimiter may be not preceded with a CRLF. + System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2); + boundaryLength = boundary.length - 2; + try { + // discard all data up to the delimiter. + discardBodyData(); + + // read boundary - if succeded, the stream contains an encapsulation. + return readBoundary(); + } catch (MalformedStreamException e) { + return false; + } finally { + // restore delimiter. + System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2); + boundaryLength = boundary.length; + boundary[0] = CR; + boundary[1] = LF; + } + } + + /** + * Compares count first bytes in the arrays a and b. + * + * @param a The first array to compare. + * @param b The second array to compare. + * @param count How many bytes should be compared. + * + * @return true if count first bytes in arrays a and b are + * equal. + */ + public static boolean arrayequals(byte[] a, byte[] b, int count) { + for (int i = 0; i < count; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } + + /** + * Searches for a byte of specified value in the buffer, starting at the specified + * position. + * + * @param value The value to find. + * @param pos The starting position for searching. + * + * @return The position of byte found, counting from beginning of the buffer, or -1 if not + * found. + */ + protected int findByte(byte value, int pos) { + for (int i = pos; i < tail; i++) { + if (buffer[i] == value) { + return i; + } + } + + return -1; + } + + /** + * Searches for the boundary in the buffer region delimited by head and + * tail. + * + * @return The position of the boundary found, counting from the beginning of the buffer, or + * -1 if not found. + */ + protected int findSeparator() { + int first; + int match = 0; + int maxpos = tail - boundaryLength; + for (first = head; (first <= maxpos) && (match != boundaryLength); first++) { + first = findByte(boundary[0], first); + if (first == -1 || (first > maxpos)) { + return -1; + } + for (match = 1; match < boundaryLength; match++) { + if (buffer[first + match] != boundary[match]) { + break; + } + } + } + if (match == boundaryLength) { + return first - 1; + } + return -1; + } + + /** + * Thrown to indicate that the input stream fails to follow the required syntax. + */ + @SuppressWarnings("serial") + public static class MalformedStreamException extends IOException { + + /** + * Constructs a MalformedStreamException with no detail message. + */ + public MalformedStreamException() { + super(); + } + + /** + * Constructs an MalformedStreamException with the specified detail message. + * + * @param message The detail message. + */ + public MalformedStreamException(String message) { + super(message); + } + } + + /** + * Thrown upon attempt of setting an invalid boundary token. + */ + @SuppressWarnings("serial") + public static class IllegalBoundaryException extends IOException { + + /** + * Constructs an IllegalBoundaryException with no detail message. + */ + public IllegalBoundaryException() { + super(); + } + + /** + * Constructs an IllegalBoundaryException with the specified detail message. + * + * @param message The detail message. + */ + public IllegalBoundaryException(String message) { + super(message); + } + + } + + /** + * An {@link InputStream} for reading an items contents. + */ + public class ItemInputStream extends InputStream implements Closeable { + + /* The number of bytes, which have been read so far */ + private long total; + + /* The number of bytes, which must be hold, because they might be a part of the boundary */ + private int pad; + + /* The current offset in the buffer */ + private int pos; + + /* Whether the stream is already closed. */ + private boolean closed; + + /** + * Creates a new instance. + */ + ItemInputStream() { + findSeparator(); + } + + /** + * Called for finding the separator. + */ + private void findSeparator() { + pos = MultipartReader.this.findSeparator(); + if (pos == -1) { + if (tail - head > keepRegion) { + pad = keepRegion; + } else { + pad = tail - head; + } + } + } + + /** + * Returns the number of bytes, which have been read by the stream. + * + * @return Number of bytes, which have been read so far. + */ + public long getBytesRead() { + return total; + } + + /** + * Returns the number of bytes, which are currently available, without blocking. + * + * @return Number of bytes in the buffer. + * @throws IOException An I/O error occurs. + */ + public int available() throws IOException { + if (pos == -1) { + return tail - head - pad; + } + return pos - head; + } + + /* Offset when converting negative bytes to integers */ + private static final int BYTE_POSITIVE_OFFSET = 256; + + /** + * Returns the next byte in the stream. + * + * @return The next byte in the stream, as a non-negative integer, or -1 for EOF. + * @throws IOException An I/O error occurred. + */ + public int read() throws IOException { + if (closed) { + throw new IOException("The stream is closed."); + } + if (available() == 0) { + if (makeAvailable() == 0) { + return -1; + } + } + ++total; + int b = buffer[head++]; + if (b >= 0) { + return b; + } + return b + BYTE_POSITIVE_OFFSET; + } + + /** + * Reads bytes into the given buffer. + * + * @param b The destination buffer, where to write to. + * @param off Offset of the first byte in the buffer. + * @param len Maximum number of bytes to read. + * + * @return Number of bytes, which have been actually read, or -1 for EOF. + * @throws IOException An I/O error occurred. + */ + public int read(byte[] b, int off, int len) throws IOException { + if (closed) { + throw new IOException("The stream is closed."); + } + if (len == 0) { + return 0; + } + int res = available(); + if (res == 0) { + res = makeAvailable(); + if (res == 0) { + return -1; + } + } + res = Math.min(res, len); + System.arraycopy(buffer, head, b, off, res); + head += res; + total += res; + return res; + } + + /** + * Closes the input stream. + * + * @throws IOException An I/O error occurred. + */ + public void close() throws IOException { + close(false); + } + + /** + * Closes the input stream. + * + * @param pCloseUnderlying Whether to close the underlying stream (hard close). + * + * @throws IOException An I/O error occurred. + */ + public void close(boolean pCloseUnderlying) throws IOException { + if (closed) { + return; + } + if (pCloseUnderlying) { + closed = true; + input.close(); + } else { + for (;;) { + int av = available(); + if (av == 0) { + av = makeAvailable(); + if (av == 0) { + break; + } + } + skip(av); + } + } + closed = true; + } + + /** + * Skips the given number of bytes. + * + * @param bytes Number of bytes to skip. + * + * @return The number of bytes, which have actually been skipped. + * @throws IOException An I/O error occurred. + */ + public long skip(long bytes) throws IOException { + if (closed) { + throw new IOException("The stream is closed."); + } + int av = available(); + if (av == 0) { + av = makeAvailable(); + if (av == 0) { + return 0; + } + } + long res = Math.min(av, bytes); + head += res; + return res; + } + + /** + * Attempts to read more data. + * + * @return Number of available bytes + * @throws IOException An I/O error occurred. + */ + private int makeAvailable() throws IOException { + if (pos != -1) { + return 0; + } + + // move the data to the beginning of the buffer + total += tail - head - pad; + System.arraycopy(buffer, tail - pad, buffer, 0, pad); + + // refill buffer with new data + head = 0; + tail = pad; + + for (;;) { + int bytesRead = input.read(buffer, tail, bufSize - tail); + if (bytesRead == -1) { + // the last pad amount is left in the buffer + // boundary can't be in there so signal an error condition. + final String msg = "Stream ended unexpectedly"; + throw new MalformedStreamException(msg); + } + + tail += bytesRead; + + findSeparator(); + int av = available(); + + if (av > 0 || pos != -1) { + return av; + } + } + } + + /** + * Returns, whether the stream is closed. + * + * @return true, if the stream is closed, otherwise false. + */ + public boolean isClosed() { + return closed; + } + } +} diff --git a/blade-core/src/main/java/com/blade/mvc/multipart/ParameterParser.java b/blade-core/src/main/java/com/blade/mvc/multipart/ParameterParser.java new file mode 100644 index 000000000..1fe0b3f72 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/multipart/ParameterParser.java @@ -0,0 +1,298 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package com.blade.mvc.multipart; + +import java.util.HashMap; +import java.util.Map; + +/** + * A simple parser intended to parse sequences of name/value pairs. Parameter values are expected to be enclosed in + * quotes if they contain unsafe characters, such as '=' characters or separators. Parameter values are optional and can + * be omitted. + * + *

+ * param1 = value; param2 = "anything goes; really"; param3 + *

+ * + * @author Oleg Kalnichevski + */ +public class ParameterParser { + /** + * String to be parsed. + */ + private char[] chars = null; + + /** + * Current position in the string. + */ + private int pos = 0; + + /** + * Maximum position in the string. + */ + private int len = 0; + + /** + * Start of a token. + */ + private int i1 = 0; + + /** + * End of a token. + */ + private int i2 = 0; + + /** + * Whether names stored in the map should be converted to lower case. + */ + private boolean lowerCaseNames = false; + + /** + * Default ParameterParser constructor. + */ + public ParameterParser() { + super(); + } + + /** + * Are there any characters left to parse? + * + * @return true if there are unparsed characters, false otherwise. + */ + private boolean hasChar() { + return this.pos < this.len; + } + + /** + * A helper method to process the parsed token. This method removes leading and trailing blanks as well as enclosing + * quotation marks, when necessary. + * + * @param quoted true if quotation marks are expected, false otherwise. + * @return the token + */ + private String getToken(boolean quoted) { + // Trim leading white spaces + while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) { + i1++; + } + // Trim trailing white spaces + while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) { + i2--; + } + // Strip away quotation marks if necessary + if (quoted) { + if (((i2 - i1) >= 2) && (chars[i1] == '"') && (chars[i2 - 1] == '"')) { + i1++; + i2--; + } + } + String result = null; + if (i2 > i1) { + result = new String(chars, i1, i2 - i1); + } + return result; + } + + /** + * Tests if the given character is present in the array of characters. + * + * @param ch the character to test for presense in the array of characters + * @param charray the array of characters to test against + * + * @return true if the character is present in the array of characters, false otherwise. + */ + private boolean isOneOf(char ch, final char[] charray) { + boolean result = false; + for (int i = 0; i < charray.length; i++) { + if (ch == charray[i]) { + result = true; + break; + } + } + return result; + } + + /** + * Parses out a token until any of the given terminators is encountered. + * + * @param terminators the array of terminating characters. Any of these characters when encountered signify the end + * of the token + * + * @return the token + */ + private String parseToken(final char[] terminators) { + char ch; + i1 = pos; + i2 = pos; + while (hasChar()) { + ch = chars[pos]; + if (isOneOf(ch, terminators)) { + break; + } + i2++; + pos++; + } + return getToken(false); + } + + /** + * Parses out a token until any of the given terminators is encountered outside the quotation marks. + * + * @param terminators the array of terminating characters. Any of these characters when encountered outside the + * quotation marks signify the end of the token + * + * @return the token + */ + private String parseQuotedToken(final char[] terminators) { + char ch; + i1 = pos; + i2 = pos; + boolean quoted = false; + boolean charEscaped = false; + while (hasChar()) { + ch = chars[pos]; + if (!quoted && isOneOf(ch, terminators)) { + break; + } + if (!charEscaped && ch == '"') { + quoted = !quoted; + } + charEscaped = (!charEscaped && ch == '\\'); + i2++; + pos++; + + } + return getToken(true); + } + + /** + * Returns true if parameter names are to be converted to lower case when name/value pairs are parsed. + * + * @return true if parameter names are to be converted to lower case when name/value pairs are parsed. + * Otherwise returns false + */ + public boolean isLowerCaseNames() { + return this.lowerCaseNames; + } + + /** + * Sets the flag if parameter names are to be converted to lower case when name/value pairs are parsed. + * + * @param b true if parameter names are to be converted to lower case when name/value pairs are parsed. + * false otherwise. + */ + public void setLowerCaseNames(boolean b) { + this.lowerCaseNames = b; + } + + /** + * Extracts a map of name/value pairs from the given string. Names are expected to be unique. Multiple separators + * may be specified and the earliest found in the input string is used. + * + * @param str the string that contains a sequence of name/value pairs + * @param separators the name/value pairs separators + * + * @return a map of name/value pairs + */ + public Map parse(final String str, char[] separators) { + if (separators == null || separators.length == 0) { + return new HashMap(); + } + char separator = separators[0]; + if (str != null) { + int idx = str.length(); + for (int i = 0; i < separators.length; i++) { + int tmp = str.indexOf(separators[i]); + if (tmp != -1) { + if (tmp < idx) { + idx = tmp; + separator = separators[i]; + } + } + } + } + return parse(str, separator); + } + + /** + * Extracts a map of name/value pairs from the given string. Names are expected to be unique. + * + * @param str the string that contains a sequence of name/value pairs + * @param separator the name/value pairs separator + * + * @return a map of name/value pairs + */ + public Map parse(final String str, char separator) { + if (str == null) { + return new HashMap(); + } + return parse(str.toCharArray(), separator); + } + + /** + * Extracts a map of name/value pairs from the given array of characters. Names are expected to be unique. + * + * @param chars the array of characters that contains a sequence of name/value pairs + * @param separator the name/value pairs separator + * + * @return a map of name/value pairs + */ + public Map parse(final char[] chars, char separator) { + if (chars == null) { + return new HashMap(); + } + return parse(chars, 0, chars.length, separator); + } + + /** + * Extracts a map of name/value pairs from the given array of characters. Names are expected to be unique. + * + * @param chars the array of characters that contains a sequence of name/value pairs + * @param offset - the initial offset. + * @param length - the length. + * @param separator the name/value pairs separator + * + * @return a map of name/value pairs + */ + public Map parse(final char[] chars, int offset, int length, char separator) { + + if (chars == null) { + return new HashMap(); + } + HashMap params = new HashMap(); + this.chars = chars; + this.pos = offset; + this.len = length; + + String paramName = null; + String paramValue = null; + while (hasChar()) { + paramName = parseToken(new char[] { '=', separator }); + paramValue = null; + if (hasChar() && (chars[pos] == '=')) { + pos++; // skip '=' + paramValue = parseQuotedToken(new char[] { separator }); + } + if (hasChar() && (chars[pos] == separator)) { + pos++; // skip separator + } + if ((paramName != null) && (paramName.length() > 0)) { + if (this.lowerCaseNames) { + paramName = paramName.toLowerCase(); + } + params.put(paramName, paramValue); + } + } + return params; + } +} diff --git a/blade-core/src/main/java/com/blade/mvc/multipart/Streams.java b/blade-core/src/main/java/com/blade/mvc/multipart/Streams.java new file mode 100644 index 000000000..ffffdd9e2 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/multipart/Streams.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package com.blade.mvc.multipart; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Utility class for working with streams. + */ +public final class Streams { + + /** + * Private constructor, to prevent instantiation. This class has only static methods. + */ + private Streams() { + // does nothing + } + + /** + * Default buffer size for use in {@link #copy(InputStream, OutputStream, boolean)}. + */ + private static final int DEFAULT_BUFFER_SIZE = 8192; + + /** + * Copies the contents of the given {@link InputStream} to the given {@link OutputStream}. Shortcut for + * + *
copy(pInputStream, pOutputStream, new byte[8192]);
+ * + * @param pInputStream The input stream, which is being read. It is guaranteed, that {@link InputStream#close()} is + * called on the stream. + * @param pOutputStream The output stream, to which data should be written. May be null, in which case the input + * streams contents are simply discarded. + * @param pClose True guarantees, that {@link OutputStream#close()} is called on the stream. False indicates, that + * only {@link OutputStream#flush()} should be called finally. + * + * @return Number of bytes, which have been copied. + * @throws IOException An I/O error occurred. + */ + public static long copy(InputStream pInputStream, OutputStream pOutputStream, boolean pClose) throws IOException { + return copy(pInputStream, pOutputStream, pClose, new byte[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copies the contents of the given {@link InputStream} to the given {@link OutputStream}. + * + * @param pIn The input stream, which is being read. It is guaranteed, that {@link InputStream#close()} is called on + * the stream. + * @param pOut The output stream, to which data should be written. May be null, in which case the input streams + * contents are simply discarded. + * @param pClose True guarantees, that {@link OutputStream#close()} is called on the stream. False indicates, that + * only {@link OutputStream#flush()} should be called finally. + * @param pBuffer Temporary buffer, which is to be used for copying data. + * + * @return Number of bytes, which have been copied. + * @throws IOException An I/O error occurred. + */ + public static long copy(InputStream pIn, OutputStream pOut, boolean pClose, byte[] pBuffer) throws IOException { + OutputStream out = pOut; + InputStream in = pIn; + try { + long total = 0; + for (;;) { + int res = in.read(pBuffer); + if (res == -1) { + break; + } + if (res > 0) { + total += res; + if (out != null) { + out.write(pBuffer, 0, res); + } + } + } + if (out != null) { + if (pClose) { + out.close(); + } else { + out.flush(); + } + out = null; + } + in.close(); + in = null; + return total; + } finally { + if (in != null) { + try { + in.close(); + } catch (Throwable t) { + /* Ignore me */ + } + } + if (pClose && out != null) { + try { + out.close(); + } catch (Throwable t) { + /* ignore me */ + } + } + } + } + + /** + * This convenience method allows to read a org.apache.commons.fileupload.FileItemStream's content into a + * string. The platform's default character encoding is used for converting bytes into characters. + * + * @param pStream The input stream to read. + * @see #asString(InputStream, String) + * + * @return The streams contents, as a string. + * @throws IOException An I/O error occurred. + */ + public static String asString(InputStream pStream) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + copy(pStream, baos, true); + return baos.toString(); + } + + /** + * This convenience method allows to read a org.apache.commons.fileupload.FileItemStream's content into a + * string, using the given character encoding. + * + * @param pStream The input stream to read. + * @param pEncoding The character encoding, typically "UTF-8". + * @see #asString(InputStream) + * + * @return The streams contents, as a string. + * @throws IOException An I/O error occurred. + */ + public static String asString(InputStream pStream, String pEncoding) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + copy(pStream, baos, true); + return baos.toString(pEncoding); + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/multipart/package-info.java b/blade-core/src/main/java/com/blade/mvc/multipart/package-info.java new file mode 100644 index 000000000..b8d5dba81 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/multipart/package-info.java @@ -0,0 +1,4 @@ +/** + * Servlet Multipart + */ +package com.blade.mvc.multipart; \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/package-info.java b/blade-core/src/main/java/com/blade/mvc/package-info.java new file mode 100644 index 000000000..5c3d64b65 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/package-info.java @@ -0,0 +1,4 @@ +/** + * Blade Web Core + */ +package com.blade.mvc; \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/route/Route.java b/blade-core/src/main/java/com/blade/mvc/route/Route.java new file mode 100644 index 000000000..d8fbb8cf7 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/route/Route.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.route; + +import java.lang.reflect.Method; + +import com.blade.mvc.http.HttpMethod; +import com.blade.mvc.http.Path; + +/** + * Route Bean + * + * @author biezhi + * @since 1.5 + */ +public class Route { + + /** + * HTTP Request Method + */ + private HttpMethod httpMethod; + + /** + * Route path + */ + private String path; + + /** + * Logical controller object + */ + private Object target; + + /** + * Controller Class Type + */ + private Class targetType; + + /** + * Implementation logic controller method + */ + private Method action; + + public Route() { + } + + public Route(HttpMethod httpMethod, String path, Object target, Class targetType, Method action) { + super(); + this.httpMethod = httpMethod; + this.path = Path.fixPath(path); + this.target = target; + this.targetType = targetType; + this.action = action; + } + + public HttpMethod getHttpMethod() { + return httpMethod; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public Object getTarget() { + return target; + } + + public void setTarget(Object target) { + this.target = target; + } + + public Method getAction() { + return action; + } + + public Class getTargetType() { + return targetType; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((action == null) ? 0 : action.hashCode()); + result = prime * result + ((httpMethod == null) ? 0 : httpMethod.hashCode()); + result = prime * result + ((path == null) ? 0 : path.hashCode()); + result = prime * result + ((target == null) ? 0 : target.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Route other = (Route) obj; + if (httpMethod != other.httpMethod) + return false; + if (path == null) { + if (other.path != null) + return false; + } else if (!path.equals(other.path)) + return false; + return true; + } + + @Override + public String toString() { + return httpMethod + "\t" + path; + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/route/RouteBuilder.java b/blade-core/src/main/java/com/blade/mvc/route/RouteBuilder.java new file mode 100644 index 000000000..7d497aa47 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/route/RouteBuilder.java @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.route; + +import com.blade.kit.reflect.ReflectKit; +import com.blade.mvc.annotation.Controller; +import com.blade.mvc.annotation.Intercept; +import com.blade.mvc.annotation.RestController; +import com.blade.mvc.annotation.Route; +import com.blade.mvc.http.HttpMethod; +import com.blade.mvc.http.Request; +import com.blade.mvc.http.Response; +import com.blade.mvc.interceptor.Interceptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +/** + * Route builder + * + * @author biezhi + * @since 1.5 + */ +public class RouteBuilder { + + private static final Logger LOGGER = LoggerFactory.getLogger(RouteBuilder.class); + + private Routers routers; + + public RouteBuilder(Routers routers) { + this.routers = routers; + } + + /** + * Parse Interceptor + * + * @param interceptor resolve the interceptor class + */ + public void addInterceptor(final Class interceptor){ + + boolean hasInterface = ReflectKit.hasInterface(interceptor, Interceptor.class); + if(null == interceptor || !hasInterface){ + return; + } + + Intercept intercept = interceptor.getAnnotation(Intercept.class); + String partten = "/.*"; + if(null != intercept){ + partten = intercept.value(); + } + + try { + Method before = interceptor.getMethod("before", Request.class, Response.class); + Method after = interceptor.getMethod("after", Request.class, Response.class); + buildInterceptor(partten, interceptor, before, HttpMethod.BEFORE); + buildInterceptor(partten, interceptor, after, HttpMethod.AFTER); + } catch (Exception e) { + LOGGER.error("", e); + } + } + + /** + * Parse all routing in a controller + * + * @param router resolve the routing class + */ + public void addRouter(final Class router){ + + Method[] methods = router.getMethods(); + if(null == methods || methods.length == 0){ + return; + } + String nameSpace = null, suffix = null; + + if(null != router.getAnnotation(Controller.class)){ + nameSpace = router.getAnnotation(Controller.class).value(); + suffix = router.getAnnotation(Controller.class).suffix(); + } + + if(null != router.getAnnotation(RestController.class)){ + nameSpace = router.getAnnotation(RestController.class).value(); + suffix = router.getAnnotation(RestController.class).suffix(); + } + + if(null == nameSpace){ + LOGGER.warn("Route [{}] not controller annotation", router.getName()); + return; + } + for (Method method : methods) { + Route mapping = method.getAnnotation(Route.class); + //route method + if (null != mapping) { + // build multiple route + HttpMethod methodType = mapping.method(); + String[] paths = mapping.value(); + if (paths.length > 0) { + for (String path : paths) { + String pathV = getRoutePath(path, nameSpace, suffix); + this.buildRoute(router, method, pathV, methodType); + } + } + } + } + } + + private String getRoutePath(String value, String nameSpace, String suffix){ + String path = value.startsWith("/") ? value : "/" + value; + + nameSpace = nameSpace.startsWith("/") ? nameSpace : "/" + nameSpace; + path = nameSpace + path; + + path = path.replaceAll("[/]+", "/"); + + path = path.length() > 1 && path.endsWith("/") ? path.substring(0, path.length() - 1) : path; + + path = path + suffix; + + return path; + } + + /** + * Build a route + * + * @param clazz route target execution class + * @param execMethod route execution method + * @param path route path + * @param method route httpmethod + */ + private void buildRoute(Class clazz, Method execMethod, String path, HttpMethod method){ + routers.buildRoute(path, clazz, execMethod, method); + } + + /** + * Build a route + * + * @param path route path + * @param clazz route target execution class + * @param execMethod route execution method + * @param method route httpmethod + */ + private void buildInterceptor(String path, Class clazz, Method execMethod, HttpMethod method){ + routers.buildRoute(path, clazz, execMethod, method); + } + +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/route/RouteGroup.java b/blade-core/src/main/java/com/blade/mvc/route/RouteGroup.java new file mode 100644 index 000000000..7369f94a2 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/route/RouteGroup.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.route; + +import com.blade.Blade; +import com.blade.mvc.handler.RouteHandler; + +/** + * Route Group. + * + * @author biezhi + * @since 1.5 + */ +public class RouteGroup { + + private Blade blade; + private String prefix; + + public RouteGroup(Blade blade, String prefix) { + this.blade = blade; + this.prefix = prefix; + } + + public RouteGroup get(RouteHandler routeHandler){ + blade.get(prefix, routeHandler); + return this; + } + + public RouteGroup get(String path, RouteHandler routeHandler){ + blade.get(prefix + path, routeHandler); + return this; + } + + public RouteGroup post(RouteHandler routeHandler){ + blade.post(prefix, routeHandler); + return this; + } + + public RouteGroup post(String path, RouteHandler routeHandler){ + blade.post(prefix + path, routeHandler); + return this; + } + + public RouteGroup delete(RouteHandler routeHandler){ + blade.delete(prefix, routeHandler); + return this; + } + + public RouteGroup delete(String path, RouteHandler routeHandler){ + blade.delete(prefix + path, routeHandler); + return this; + } + + public RouteGroup put(RouteHandler routeHandler){ + blade.put(prefix, routeHandler); + return this; + } + + public RouteGroup put(String path, RouteHandler routeHandler){ + blade.put(prefix + path, routeHandler); + return this; + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/route/RouteMatcher.java b/blade-core/src/main/java/com/blade/mvc/route/RouteMatcher.java new file mode 100644 index 000000000..8ae19f0e8 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/route/RouteMatcher.java @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.route; + +import com.blade.kit.CollectionKit; +import com.blade.kit.StringKit; +import com.blade.mvc.http.HttpMethod; +import com.blade.mvc.http.Path; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; + +/** + * Default Route Matcher + * + * @author biezhi + * @since 1.5 + */ +public class RouteMatcher { + +// private static final Logger LOGGER = Logger.getLogger(SampleRouteMatcher.class); + + // Storage URL and route + private Map routes = null; + private Map interceptors = null; + + // Storage Map Key + private Set routeKeys = null; + + private List interceptorRoutes = CollectionKit.newArrayList(8); + + public RouteMatcher(Routers routers) { + this.routes = routers.getRoutes(); + this.interceptors = routers.getInterceptors(); + this.routeKeys = routes.keySet(); + Collection inters = interceptors.values(); + if (!inters.isEmpty()) { + this.interceptorRoutes.addAll(inters); + } + } + + /** + * Find a route + * @param httpMethod httpMethod + * @param path request path + * @return return route object + */ + public Route getRoute(String httpMethod, String path) { + String cleanPath = parsePath(path); + + String routeKey = path + '#' + httpMethod.toUpperCase(); + final Route[] route = {routes.get(routeKey)}; + if(null != route[0]){ + return route[0]; + } + route[0] = routes.get(path + "#ALL"); + if(null != route[0]){ + return route[0]; + } + + List matchRoutes = CollectionKit.newArrayList(); + + routeKeys.forEach(key -> { + String[] keyArr = StringKit.split(key, '#'); + if (matchesPath(keyArr[0], cleanPath)) { + route[0] = routes.get(key); + matchRoutes.add(route[0]); + } + }); + + // Priority matching principle + this.giveMatch(path, matchRoutes); + + return matchRoutes.isEmpty() ? null : matchRoutes.get(0); + } + + /** + * Find all in before of the interceptor + * @param path request path + * @return return interceptor list + */ + public List getBefore(String path) { + List befores = CollectionKit.newArrayList(); + String cleanPath = parsePath(path); + interceptorRoutes.forEach(route -> { + if(matchesPath(route.getPath(), cleanPath) && route.getHttpMethod() == HttpMethod.BEFORE){ + befores.add(route); + } + }); + this.giveMatch(path, befores); + return befores; + } + + /** + * Find all in after of the interceptor + * @param path request path + * @return return interceptor list + */ + public List getAfter(String path) { + List afters = CollectionKit.newArrayList(); + String cleanPath = parsePath(path); + interceptorRoutes.forEach(route -> { + if(matchesPath(route.getPath(), cleanPath) && route.getHttpMethod() == HttpMethod.AFTER){ + afters.add(route); + } + }); + this.giveMatch(path, afters); + return afters; + } + + /** + * Sort of path + * @param uri request uri + * @param routes route list + */ + private void giveMatch(final String uri, List routes) { + Collections.sort(routes, (o1, o2) -> { + if(o2.getPath().equals(uri)){ + return o2.getPath().indexOf(uri); + } + return -1; + }); + } + + /** + * Matching path + * + * @param routePath route path + * @param pathToMatch match path + * @return return match is success + */ + private boolean matchesPath(String routePath, String pathToMatch) { + routePath = routePath.replaceAll(Path.VAR_REGEXP, Path.VAR_REPLACE); + return pathToMatch.matches("(?i)" + routePath); + } + + /** + * Parse Path + * + * @param path route path + * @return return parsed path + */ + private String parsePath(String path) { + path = Path.fixPath(path); + try { + URI uri = new URI(path); + return uri.getPath(); + } catch (URISyntaxException e) { + return null; + } + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/route/Routers.java b/blade-core/src/main/java/com/blade/mvc/route/Routers.java new file mode 100644 index 000000000..fd75b43a0 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/route/Routers.java @@ -0,0 +1,180 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.route; + +import com.blade.exception.BladeException; +import com.blade.kit.Assert; +import com.blade.kit.CollectionKit; +import com.blade.kit.reflect.ReflectKit; +import com.blade.mvc.handler.RouteHandler; +import com.blade.mvc.http.HttpMethod; +import com.blade.mvc.http.Request; +import com.blade.mvc.http.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +/** + * Registration, management route + * + * @author biezhi + * @since 1.5 + */ +public class Routers { + + private static final Logger LOGGER = LoggerFactory.getLogger(Routers.class); + + private Map routes = null; + + private Map interceptors = null; + + private static final String METHOD_NAME = "handle"; + + public Routers() { + this.routes = CollectionKit.newHashMap(); + this.interceptors = CollectionKit.newHashMap(); + } + + public Map getRoutes() { + return routes; + } + + public Map getInterceptors() { + return interceptors; + } + + public void addRoute(Route route) { + String path = route.getPath(); + HttpMethod httpMethod = route.getHttpMethod(); + String key = path + "#" + httpMethod.toString(); + + // existent + if (null != this.routes.get(key)) { + LOGGER.warn("\tRoute {} -> {} has exist", path, httpMethod.toString()); + } + + if(httpMethod == HttpMethod.BEFORE || httpMethod == HttpMethod.AFTER){ + if (null != this.interceptors.get(key)) { + LOGGER.warn("\tInterceptor {} -> {} has exist", path, httpMethod.toString()); + } + this.interceptors.put(key, route); + LOGGER.debug("Add Interceptor => {}", route); + } else { + this.routes.put(key, route); + LOGGER.debug("Add Route => {}", route); + } + } + + public void addRoutes(List routes) { + Assert.notNull(routes); + routes.forEach(this::addRoute); + } + + public void addRoute(HttpMethod httpMethod, String path, RouteHandler handler, String methodName) throws NoSuchMethodException { + Class handleType = handler.getClass(); + Method method = handleType.getMethod(methodName, Request.class, Response.class); + addRoute(httpMethod, path, handler, RouteHandler.class, method); + } + + public void addRoute(HttpMethod httpMethod, String path, Object controller, Class controllerType, Method method) { + + Assert.notBlank(path); + + String key = path + "#" + httpMethod.toString(); + // existent + if (null != this.routes.get(key)) { + LOGGER.warn("\tRoute {} -> {} has exist", path, httpMethod.toString()); + } + + Route route = new Route(httpMethod, path, controller, controllerType, method); + if(httpMethod == HttpMethod.BEFORE || httpMethod == HttpMethod.AFTER){ + if (null != this.interceptors.get(key)) { + LOGGER.warn("\tInterceptor {} -> {} has exist", path, httpMethod.toString()); + } + this.interceptors.put(key, route); + LOGGER.info("Add Interceptor: {}", route); + } else { + this.routes.put(key, route); + LOGGER.info("Add Route => {}", route); + } + + } + + public void route(String path, RouteHandler handler, HttpMethod httpMethod) { + try { + addRoute(httpMethod, path, handler, METHOD_NAME); + } catch (NoSuchMethodException e) { + throw new BladeException(e); + } + } + + public void route(String[] paths, RouteHandler handler, HttpMethod httpMethod) { + for (String path : paths) { + route(path, handler, httpMethod); + } + } + + private Map classMethosPool = CollectionKit.newHashMap(16); + private Map, Object> controllerPool = CollectionKit.newHashMap(16); + + public void route(String path, Class clazz, String methodName) { + Assert.notNull(methodName, "Method name not is null"); + HttpMethod httpMethod = HttpMethod.ALL; + if(methodName.contains(":")){ + String[] methodArr = methodName.split(":"); + httpMethod = HttpMethod.valueOf(methodArr[0].toUpperCase()); + methodName = methodArr[1]; + } + this.route(path, clazz, methodName, httpMethod); + } + + public void route(String path, Class clazz, String methodName, HttpMethod httpMethod) { + try { + Assert.notNull(path, "Route path not is null!"); + Assert.notNull(clazz, "Class Type not is null!"); + Assert.notNull(methodName, "Method name not is null"); + Assert.notNull(httpMethod, "Request Method not is null"); + + Method[] methods = classMethosPool.get(clazz.getName()); + if(null == methods){ + methods = clazz.getMethods(); + classMethosPool.put(clazz.getName(), methods); + } + if(null != methods){ + for (Method method : methods) { + if (method.getName().equals(methodName)) { + Object controller = controllerPool.get(clazz); + if(null == controller){ + controller = ReflectKit.newInstance(clazz); + controllerPool.put(clazz, controller); + } + addRoute(httpMethod, path, controller, clazz, method); + } + } + } + } catch (Exception e) { + LOGGER.error("", e); + } + } + + public void buildRoute(String path, Class clazz, Method method, HttpMethod httpMethod) { + addRoute(httpMethod, path, null, clazz, method); + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/route/loader/AbstractFileRouteLoader.java b/blade-core/src/main/java/com/blade/mvc/route/loader/AbstractFileRouteLoader.java new file mode 100644 index 000000000..7a2834dea --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/route/loader/AbstractFileRouteLoader.java @@ -0,0 +1,222 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.route.loader; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import com.blade.kit.IOKit; +import com.blade.mvc.http.HttpMethod; +import com.blade.mvc.http.Request; +import com.blade.mvc.http.Response; +import com.blade.mvc.route.Route; +import com.blade.exception.RouteException; + +/** + * Abstract loader implementation + * + * @author biezhi + * @since 1.5 + */ +public abstract class AbstractFileRouteLoader implements RouteLoader { + + private ControllerLoader controllerLoader = new ClassPathControllerLoader(); + + protected abstract InputStream getInputStream() throws Exception; + + @Override + public List load() throws ParseException, RouteException { + InputStream inputStream = null; + try { + inputStream = getInputStream(); + } catch (Exception e) { + throw new RouteException("Loading the route config file error: " + e.getMessage(), e); + } + try { + return load(inputStream); + } catch (IOException e) { + throw new RouteException("Loading the route config file error: " + e.getMessage(), e); + } + } + + /** + * Load Route + * + * @param inputStream route inputstream + * @return return route list + * @throws ParseException parse exception + * @throws IOException io exception + */ + private List load(InputStream inputStream) throws ParseException, IOException { + int line = 0; + List routes = new ArrayList(); + BufferedReader in = null; + try { + in = new BufferedReader(new InputStreamReader(inputStream)); + String input; + while ( (input = in.readLine()) != null ) { + line++; + + input = input.trim(); + + if (!input.equals("") && !input.startsWith(".")) { + Route route = parse(input, line); + routes.add(route); + } + } + } finally { + IOKit.closeQuietly(in); + } + return routes; + } + + private Route parse(String input, int line) throws ParseException { + StringTokenizer st = new StringTokenizer(input, " \t"); + if (st.countTokens() != 3) { + throw new ParseException("Unrecognized format", line); + } + + // Verify HTTP request + String httpMethod = validateHttpMethod( st.nextToken().trim(), line ); + + String path = validatePath( st.nextToken().trim(), line ); + String controllerAndMethod = validateControllerAndMethod( st.nextToken().trim(), line ); + + int hashPos = controllerAndMethod.indexOf("."); + String controllerName = controllerAndMethod.substring(0, hashPos); + + // Acquisition controller method + String controllerMethod = controllerAndMethod.substring(hashPos + 1); + + return buildRoute(httpMethod, path, controllerName, controllerMethod); + } + + private String validateHttpMethod(String httpMethod, int line) throws ParseException { + if (!httpMethod.equalsIgnoreCase("GET") && + !httpMethod.equalsIgnoreCase("POST") && + !httpMethod.equalsIgnoreCase("PUT") && + !httpMethod.equalsIgnoreCase("DELETE")) { + throw new ParseException("Unrecognized HTTP method: " + httpMethod, line); + } + return httpMethod; + } + + private String validatePath(String path, int line) throws ParseException { + if (!path.startsWith("/")) { + throw new ParseException("Path must start with '/'", line); + } + + boolean openedKey = false; + for (int i=0; i < path.length(); i++) { + + boolean validChar = isValidCharForPath(path.charAt(i), openedKey); + if (!validChar) { + throw new ParseException(path, i); + } + + if (path.charAt(i) == '{') { + openedKey = true; + } + + if (path.charAt(i) == '}') { + openedKey = false; + } + } + return path; + } + + private boolean isValidCharForPath(char c, boolean openedKey) { + char[] invalidChars = { '?', '.', ' ' }; + for (char invalidChar : invalidChars) { + if (c == invalidChar) { + return false; + } + } + + if (openedKey) { + char[] moreInvalidChars = { '/', '{' }; + for (char invalidChar : moreInvalidChars) { + if (c == invalidChar) { + return false; + } + } + } + + return true; + } + + /** + * Verification controller method + * + * @param beanAndMethod controller and method, using. + * @param line line number + * @return return a string that is verified after the verification. + * @throws ParseException + */ + private String validateControllerAndMethod(String beanAndMethod, int line) throws ParseException { + int hashPos = beanAndMethod.indexOf("."); + if (hashPos == -1) { + throw new ParseException("Unrecognized format for '" + beanAndMethod + "'", line); + } + + return beanAndMethod; + } + + /** + * Construct a routing object + * + * @param httpMethod request httpMethod + * @param path route path + * @param controllerName controller name + * @param methodName method name + * @return return route object + * @throws RouteException + */ + private Route buildRoute(String httpMethod, String path, String controllerName, String methodName) throws RouteException { + Object controller = controllerLoader.load(controllerName); + Class controllerType = controller.getClass(); + Method method = getMethod(controllerType, methodName); + return new Route(HttpMethod.valueOf(httpMethod.toUpperCase()), path, controller, controllerType, method); + } + + private Method getMethod(Class controllerType, String methodName) throws RouteException { + try { + return controllerType.getMethod(methodName, Request.class, Response.class); + } catch (Exception e) { + throw new RouteException(e); + } + } + + public void setBasePackage(String basePackage) { + this.controllerLoader = new ClassPathControllerLoader(basePackage); + } + + public ControllerLoader getControllerLoader() { + return controllerLoader; + } + + public void setControllerLoader(ControllerLoader controllerLoader) { + this.controllerLoader = controllerLoader; + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/route/loader/ClassPathControllerLoader.java b/blade-core/src/main/java/com/blade/mvc/route/loader/ClassPathControllerLoader.java new file mode 100644 index 000000000..4cd7427d3 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/route/loader/ClassPathControllerLoader.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.route.loader; + +import com.blade.Blade; +import com.blade.ioc.Ioc; +import com.blade.kit.StringKit; +import com.blade.exception.RouteException; + +/** + * ClassPath controller of loader + * + * @author biezhi + * @since 1.5 + */ +public class ClassPathControllerLoader implements ControllerLoader { + + private String basePackage; + + private ClassLoader classLoader = ClassPathControllerLoader.class.getClassLoader(); + + private Ioc ioc = Blade.$().ioc(); + + public ClassPathControllerLoader() { + this(""); + } + + public ClassPathControllerLoader(String basePackage) { + this.basePackage = basePackage; + + if (StringKit.isNotBlank(basePackage)) { + if (!this.basePackage.endsWith(".")) { + this.basePackage += '.'; + } + } + } + + @Override + public Object load(String controllerName) throws RouteException { + String className = basePackage + controllerName; + + try { + // Load controller instance + Class controllerClass = classLoader.loadClass(className); + + Object controller = ioc.getBean(controllerClass); + if(null == controller){ + ioc.addBean(controllerClass); + controller = ioc.getBean(controllerClass); + } + return controller; + } catch (Exception e) { + throw new RouteException(e); + } + } + + public String getBasePackage() { + return basePackage; + } + + public void setBasePackage(String basePackage) { + this.basePackage = basePackage; + } + + public ClassLoader getClassLoader() { + return classLoader; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/route/loader/ClassPathRouteLoader.java b/blade-core/src/main/java/com/blade/mvc/route/loader/ClassPathRouteLoader.java new file mode 100644 index 000000000..90bb7a555 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/route/loader/ClassPathRouteLoader.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.route.loader; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; + +/** + * Route loader based on ClassPath + * + * @author biezhi + * @since 1.5 + */ +public class ClassPathRouteLoader extends AbstractFileRouteLoader { + + private File file; + + private InputStream inputStream; + + public ClassPathRouteLoader() { + } + + public ClassPathRouteLoader(String filePath) { + this(new File(filePath)); + } + + public ClassPathRouteLoader(File file) { + this.file = file; + } + + public ClassPathRouteLoader(InputStream inputStream) { + this.inputStream = inputStream; + } + + @Override + protected InputStream getInputStream() throws Exception { + if(null != this.inputStream){ + return this.inputStream; + } + return new FileInputStream(file); + } + + protected void setInputStream(InputStream inputStream) { + this.inputStream = inputStream; + } + + public void setFile(File file) { + this.file = file; + } + + public void setFilePath(String filePath) { + this.file = new File(filePath); + } +} diff --git a/blade-core/src/main/java/blade/ioc/Scope.java b/blade-core/src/main/java/com/blade/mvc/route/loader/ControllerLoader.java similarity index 75% rename from blade-core/src/main/java/blade/ioc/Scope.java rename to blade-core/src/main/java/com/blade/mvc/route/loader/ControllerLoader.java index 46f853a0a..82ff2a5b8 100644 --- a/blade-core/src/main/java/blade/ioc/Scope.java +++ b/blade-core/src/main/java/com/blade/mvc/route/loader/ControllerLoader.java @@ -13,17 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.ioc; +package com.blade.mvc.route.loader; + +import com.blade.exception.RouteException; /** - * Bean创建类型 - *

- * SINGLE:单例,PROTOTYPE:原生 - *

+ * Controller loading interface * * @author biezhi - * @since 1.0 + * @since 1.5 */ -public enum Scope { - SINGLE, PROTOTYPE -} \ No newline at end of file +public interface ControllerLoader { + + Object load(String controllerName) throws RouteException; + +} diff --git a/blade-core/src/main/java/com/blade/mvc/route/loader/FileSystemRouteLoader.java b/blade-core/src/main/java/com/blade/mvc/route/loader/FileSystemRouteLoader.java new file mode 100644 index 000000000..81b37391d --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/route/loader/FileSystemRouteLoader.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.route.loader; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; + +/** + * The file system based on Routing + * + * @author biezhi + * @since 1.5 + */ +public class FileSystemRouteLoader extends AbstractFileRouteLoader { + + private File file; + + public FileSystemRouteLoader() { + } + + public FileSystemRouteLoader(String filePath) { + this(new File(filePath)); + } + + public FileSystemRouteLoader(File file) { + this.file = file; + } + + @Override + protected InputStream getInputStream() throws Exception { + return new FileInputStream(file); + } + + public void setFile(File file) { + this.file = file; + } + + public void setFilePath(String filePath) { + this.file = new File(filePath); + } +} diff --git a/blade-core/src/main/java/blade/ioc/PrototypeBean.java b/blade-core/src/main/java/com/blade/mvc/route/loader/RouteLoader.java similarity index 66% rename from blade-core/src/main/java/blade/ioc/PrototypeBean.java rename to blade-core/src/main/java/com/blade/mvc/route/loader/RouteLoader.java index b0de1fa01..be621fd24 100644 --- a/blade-core/src/main/java/blade/ioc/PrototypeBean.java +++ b/blade-core/src/main/java/com/blade/mvc/route/loader/RouteLoader.java @@ -1,36 +1,34 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.ioc; - -/** - * 原生bean对象 - * - * @author biezhi - * @since 1.0 - */ -public class PrototypeBean extends AbstractBeanFactory { - - @Override - public Object getBean(String className) { - return container.getBean(className, Scope.PROTOTYPE); - } - - @Override - public Object getBean(Class clazz) { - return container.getBean(clazz, Scope.PROTOTYPE); - } - +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.route.loader; + +import java.text.ParseException; +import java.util.List; + +import com.blade.mvc.route.Route; +import com.blade.exception.RouteException; + +/** + * Route loader + * + * @author biezhi + * @since 1.5 + */ +public interface RouteLoader { + + List load() throws ParseException, RouteException; + } \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/route/package-info.java b/blade-core/src/main/java/com/blade/mvc/route/package-info.java new file mode 100644 index 000000000..17a9be773 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/route/package-info.java @@ -0,0 +1,4 @@ +/** + * Blade Route + */ +package com.blade.mvc.route; \ No newline at end of file diff --git a/blade-core/src/main/java/blade/render/ModelAndView.java b/blade-core/src/main/java/com/blade/mvc/view/ModelAndView.java similarity index 61% rename from blade-core/src/main/java/blade/render/ModelAndView.java rename to blade-core/src/main/java/com/blade/mvc/view/ModelAndView.java index aa3a6d9fd..d6206fa87 100644 --- a/blade-core/src/main/java/blade/render/ModelAndView.java +++ b/blade-core/src/main/java/com/blade/mvc/view/ModelAndView.java @@ -13,45 +13,50 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.render; +package com.blade.mvc.view; import java.util.HashMap; import java.util.Map; +import com.blade.kit.CollectionKit; + /** - * ModelAndView使用模型和视图来渲染 + * ModelAndView, Using templates and data * * @author biezhi - * @since 1.0 + * @since 1.5 */ public class ModelAndView { /** - * 数据对象,该对象被放在httprequest的attribute中 + * Data object, the object is placed in the attribute httprequest */ - private Map model; + private Map model = new HashMap(5); /** - * 视图 + * View Page */ private String view; + public ModelAndView() { + } + /** - * 创建一个空视图 + * Create an empty view * - * @param view 视图 + * @param view view page */ public ModelAndView(String view) { super(); - this.model = new HashMap(); + this.model = CollectionKit.newHashMap(); this.view = view; } /** - * 创建一个带数据的模型视图对象 + * Create a model view object with data * - * @param model 数据模型 - * @param view 视图 + * @param model model data + * @param view view page */ public ModelAndView(Map model, String view) { super(); @@ -60,19 +65,19 @@ public ModelAndView(Map model, String view) { } /** - * 添加数据 + * Add data to model * - * @param key 数据键 - * @param value 数据值 + * @param key key + * @param value value */ public void add(String key, Object value){ this.model.put(key, value); } /** - * 移除一个数据 + * Remove model data * - * @param key 模型的键 + * @param key key */ public void remove(String key){ this.model.remove(key); @@ -80,19 +85,37 @@ public void remove(String key){ /** * - * @return 获取视图 + * @return Return view page */ public String getView() { return view; } + + /** + * Setting view page + * + * @param view view page + */ + public void setView(String view) { + this.view = view; + } /** - * @return 获取模型 + * @return Return model map */ public Map getModel() { return model; } + /** + * Setting model + * + * @param model Storage data map + */ + public void setModel(Map model) { + this.model = model; + } + @Override public String toString() { return "view = " + view + ", model = " + model; diff --git a/blade-core/src/main/java/com/blade/mvc/view/ModelMap.java b/blade-core/src/main/java/com/blade/mvc/view/ModelMap.java new file mode 100644 index 000000000..e25cbc902 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/view/ModelMap.java @@ -0,0 +1,30 @@ +package com.blade.mvc.view; + +import com.blade.kit.Assert; +import com.blade.mvc.http.Request; + +public class ModelMap { + + private Request request; + + public ModelMap(Request request) { + this.request = request; + } + + public void addAttribute(String name, Object value) { + Assert.notNull(name, "Model attribute name must not be null"); + request.attribute(name, value); + } + + public void attribute(String name, Object value) { + addAttribute(name, value); + } + + public void attr(String name, Object value) { + addAttribute(name, value); + } + + public void put(String name, Object value) { + addAttribute(name, value); + } +} diff --git a/blade-core/src/main/java/com/blade/mvc/view/RestResponse.java b/blade-core/src/main/java/com/blade/mvc/view/RestResponse.java new file mode 100644 index 000000000..e271706f4 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/view/RestResponse.java @@ -0,0 +1,109 @@ +package com.blade.mvc.view; + +/** + * rest返回对象 + * + * @param + */ +public class RestResponse { + + /** + * 服务器响应数据 + */ + private T payload; + + /** + * 请求是否成功 + */ + private boolean success; + + /** + * 错误信息 + */ + private String msg; + + /** + * 服务器响应时间 + */ + private long timestamp; + + public static RestResponse build(T data){ + RestResponse r = new RestResponse(); + r.setPayload(data); + r.setSuccess(true); + return r; + } + public RestResponse() { + this.timestamp = System.currentTimeMillis() / 1000; + } + + public RestResponse(T payload) { + this.success = true; + this.payload = payload; + this.timestamp = System.currentTimeMillis() / 1000; + } + + public RestResponse(boolean success) { + this.success = success; + this.timestamp = System.currentTimeMillis() / 1000; + } + + public RestResponse(String msg) { + this.success = false; + this.msg = msg; + this.timestamp = System.currentTimeMillis() / 1000; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean isSuccess() { + return success; + } + + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public T getPayload() { + return payload; + } + + public void setPayload(T payload) { + this.setPayload(payload, true); + } + + public void setPayload(T payload, boolean success) { + this.payload = payload; + this.success = success; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + @Override + public String toString() { + return "RestResponse{" + + "payload=" + payload + + ", success=" + success + + ", msg=" + msg + + ", timestamp=" + timestamp + + '}'; + } + + public void error(String msg) { + this.msg = msg; + this.success = false; + } +} \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/mvc/view/ViewSettings.java b/blade-core/src/main/java/com/blade/mvc/view/ViewSettings.java new file mode 100644 index 000000000..25f6b7179 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/view/ViewSettings.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.view; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.blade.kit.Assert; +import com.blade.mvc.view.resolve.DefaultJSONParser; +import com.blade.mvc.view.resolve.JSONParser; +import com.blade.mvc.view.template.DefaultEngine; +import com.blade.mvc.view.template.TemplateEngine; + +/** + * ViewSettings + * + * @author biezhi + * @since 1.6.6 + */ +public final class ViewSettings { + + private static final Logger LOGGER = LoggerFactory.getLogger(ViewSettings.class); + + private JSONParser jsonParser = new DefaultJSONParser(); + private TemplateEngine templateEngine = new DefaultEngine(); + private String view404 = "404.html"; + private String view500 = "500.html"; + + private ViewSettings() { + } + + static final class ViewSettingsHolder { + private static final ViewSettings $ = new ViewSettings(); + } + + public static ViewSettings $() { + return ViewSettingsHolder.$; + } + + public ViewSettings JSONParser(JSONParser jsonParser) { + Assert.notNull(jsonParser); + LOGGER.debug("Switch JSONParser With [{}]", jsonParser); + this.jsonParser = jsonParser; + return this; + } + + public JSONParser JSONParser() { + return this.jsonParser; + } + + public String toJSONString(Object object) { + return jsonParser.toJSONSting(object); + } + + /** + * Setting Render Engin, Default is static file render + * + * @param templateEngine + * Render engine object + * @return return blade + */ + public ViewSettings templateEngine(TemplateEngine templateEngine) { + Assert.notNull(templateEngine); + LOGGER.debug("Switch TemplateEngine With [{}]", templateEngine); + this.templateEngine = templateEngine; + return this; + } + + /** + * @return Return Current TemplateEngine + */ + public TemplateEngine templateEngine() { + return this.templateEngine; + } + + public String getView404() { + return view404; + } + + public ViewSettings setView404(String view404) { + this.view404 = view404; + return this; + } + + public String getView500() { + return view500; + } + + public ViewSettings setView500(String view500) { + this.view500 = view500; + return this; + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/view/resolve/DefaultJSONParser.java b/blade-core/src/main/java/com/blade/mvc/view/resolve/DefaultJSONParser.java new file mode 100644 index 000000000..06c7f63ab --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/view/resolve/DefaultJSONParser.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.view.resolve; + +import com.blade.kit.json.JSONKit; + +/** + * Default json parser implment + * + * @author biezhi + * @since 1.6.6 + */ +public class DefaultJSONParser implements JSONParser { + + @Override + public String toJSONSting(Object object) { + return JSONKit.toJSONString(object); + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/view/resolve/JSONParser.java b/blade-core/src/main/java/com/blade/mvc/view/resolve/JSONParser.java new file mode 100644 index 000000000..ca05b55e6 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/view/resolve/JSONParser.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.view.resolve; + +/** + * Route render json parser + * + * @author biezhi + * @since 1.6.6 + */ +public interface JSONParser { + + String toJSONSting(Object object); + +} diff --git a/blade-core/src/main/java/com/blade/mvc/view/resolve/MethodArgument.java b/blade-core/src/main/java/com/blade/mvc/view/resolve/MethodArgument.java new file mode 100644 index 000000000..dfd4ecd23 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/view/resolve/MethodArgument.java @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.view.resolve; + +import com.blade.exception.NotFoundException; +import com.blade.kit.AsmKit; +import com.blade.kit.StringKit; +import com.blade.mvc.annotation.*; +import com.blade.mvc.http.Request; +import com.blade.mvc.http.Response; +import com.blade.mvc.multipart.FileItem; +import com.blade.mvc.view.ModelAndView; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Map; + +public final class MethodArgument { + + public static Object[] getArgs(Request request, Response response, Method actionMethod) throws Exception{ + + Class[] parameters = actionMethod.getParameterTypes(); + Annotation[][] annotations = actionMethod.getParameterAnnotations(); + + Object[] args = new Object[parameters.length]; + + actionMethod.setAccessible(true); + + String[] paramaterNames = AsmKit.getMethodParamNames(actionMethod); + + for (int i = 0, len = parameters.length; i < len; i++) { + + Class argType = parameters[i]; + if (argType == Request.class) { + args[i] = request; + continue; + } + + if (argType == Response.class) { + args[i] = response; + continue; + } + + if (argType == ModelAndView.class) { + args[i] = new ModelAndView(); + continue; + } + + if (argType == Map.class) { + args[i] = request.querys(); + continue; + } + + Annotation annotation = annotations[i][0]; + if(null != annotation){ + // query param + if(annotation.annotationType() == QueryParam.class){ + QueryParam queryParam = (QueryParam) annotation; + String paramName = queryParam.value(); + String val = request.query(paramName); + + if (StringKit.isBlank(paramName)) { + assert paramaterNames != null; + paramName = paramaterNames[i]; + val = request.query(paramName); + } + if (StringKit.isBlank(val)) { + val = queryParam.defaultValue(); + } + args[i] = getRequestParam(argType, val); + continue; + } + + // header param + if(annotation.annotationType() == HeaderParam.class){ + HeaderParam headerParam = (HeaderParam) annotation; + String paramName = headerParam.value(); + String val = request.header(paramName); + + if (StringKit.isBlank(paramName)) { + assert paramaterNames != null; + paramName = paramaterNames[i]; + val = request.header(paramName); + } + args[i] = getRequestParam(argType, val); + continue; + } + + // cookie param + if(annotation.annotationType() == CookieParam.class){ + CookieParam cookieParam = (CookieParam) annotation; + String paramName = cookieParam.value(); + String val = request.cookie(paramName); + + if (StringKit.isBlank(paramName)) { + assert paramaterNames != null; + paramName = paramaterNames[i]; + val = request.cookie(paramName); + } + args[i] = getRequestParam(argType, val); + continue; + } + + // form multipart + if(annotation.annotationType() == MultipartParam.class && argType == FileItem.class){ + MultipartParam multipartParam = (MultipartParam) annotation; + String paramName = multipartParam.value(); + FileItem val = request.fileItem(paramName); + + if (StringKit.isBlank(paramName)) { + assert paramaterNames != null; + paramName = paramaterNames[i]; + val = request.fileItem(paramName); + } + args[i] = val; + continue; + } + + // path param + if(annotation.annotationType() == PathParam.class){ + PathParam pathParam = (PathParam) annotation; + String paramName = pathParam.value(); + String val = request.pathParam(paramName); + if (StringKit.isBlank(paramName)) { + assert paramaterNames != null; + paramName = paramaterNames[i]; + val = request.pathParam(paramName); + } + if (StringKit.isBlank(val)) { + val = pathParam.defaultValue(); + } + args[i] = getRequestParam(argType, val); + } + } + } + return args; + } + + public static Object getRequestParam(Class parameterType, String val) { + Object result = null; + if (parameterType.equals(String.class)) { + result = val; + } else if (parameterType.equals(Integer.class) && StringKit.isNotBlank(val)) { + result = Integer.parseInt(val); + } else if (parameterType.equals(int.class) && StringKit.isNotBlank(val)) { + if(StringKit.isBlank(val)){ + result = 0; + } else { + result = Integer.parseInt(val); + } + } else if (parameterType.equals(Long.class) && StringKit.isNotBlank(val)) { + result = Long.parseLong(val); + } else if (parameterType.equals(long.class)) { + if(StringKit.isBlank(val)){ + result = 0L; + } else { + result = Integer.parseInt(val); + } + } + return result; + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/view/resolve/MethodArgumentN.java b/blade-core/src/main/java/com/blade/mvc/view/resolve/MethodArgumentN.java new file mode 100644 index 000000000..8dc1fd9bd --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/view/resolve/MethodArgumentN.java @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.view.resolve; + +import com.blade.exception.NotFoundException; +import com.blade.kit.StringKit; +import com.blade.mvc.annotation.*; +import com.blade.mvc.http.Request; +import com.blade.mvc.http.Response; +import com.blade.mvc.multipart.FileItem; +import com.blade.mvc.view.ModelAndView; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.util.Map; + +public final class MethodArgumentN { + + private static boolean isParameterFinal(final Parameter parameter) + { + return Modifier.isFinal(parameter.getModifiers()); + } + + public static Object[] getArgs(Request request, Response response, Method actionMethod) throws Exception{ + + int parameterCount = 0; + for (final Parameter parameter : actionMethod.getParameters()) + { + System.out.println( + "\targ" + parameterCount++ + ": " + + (parameter.isNamePresent() ? parameter.getName() : "Parameter Name not provided,") + + (isParameterFinal(parameter) ? " IS " : " is NOT ") + + "final, type " + parameter.getType().getCanonicalName() + + ", and parameterized type of " + parameter.getParameterizedType() + + " and " + (parameter.isVarArgs() ? "IS " : "is NOT ") + + "variable." ); + } + + Parameter[] parameters = actionMethod.getParameters(); + Object[] args = new Object[parameters.length]; + actionMethod.setAccessible(true); + + for (int i = 0, len = parameters.length; i < len; i++) { + + Parameter parameter = parameters[i]; + + Class argType = parameter.getType(); + if (argType == Request.class) { + args[i] = request; + continue; + } + + if (argType == Response.class) { + args[i] = response; + continue; + } + + if (argType == ModelAndView.class) { + args[i] = new ModelAndView(); + continue; + } + + if (argType == Map.class) { + args[i] = request.querys(); + continue; + } + + QueryParam queryParam = parameter.getAnnotation(QueryParam.class); + if(null != queryParam){ + String paramName = queryParam.value(); + String val = request.query(paramName); + + if (StringKit.isBlank(paramName)) { + paramName = parameter.getName(); + val = request.query(paramName); + } + if (StringKit.isBlank(val)) { + val = queryParam.defaultValue(); + } + args[i] = MethodArgument.getRequestParam(argType, val); + } + + PathParam pathParam = parameter.getAnnotation(PathParam.class); + if(null != pathParam){ + String paramName = pathParam.value(); + String val = request.pathParam(paramName); + if (StringKit.isBlank(paramName)) { + paramName = parameter.getName(); + val = request.pathParam(paramName); + } + if (StringKit.isBlank(val)) { + throw new NotFoundException("path param [" + paramName + "] is null"); + } + args[i] = MethodArgument.getRequestParam(argType, val); + } + + HeaderParam headerParam = parameter.getAnnotation(HeaderParam.class); + if(null != headerParam){ + String paramName = headerParam.value(); + String val = request.header(paramName); + + if (StringKit.isBlank(paramName)) { + paramName = parameter.getName(); + val = request.header(paramName); + } + args[i] = MethodArgument.getRequestParam(argType, val); + } + + CookieParam cookieParam = parameter.getAnnotation(CookieParam.class); + if(null != cookieParam){ + String paramName = cookieParam.value(); + String val = request.cookie(paramName); + + if (StringKit.isBlank(paramName)) { + paramName = parameter.getName(); + val = request.cookie(paramName); + } + args[i] = MethodArgument.getRequestParam(argType, val); + } + + MultipartParam multipartParam = parameter.getAnnotation(MultipartParam.class); + if(null != multipartParam && argType == FileItem.class){ + String paramName = multipartParam.value(); + FileItem val = request.fileItem(paramName); + + if (StringKit.isBlank(paramName)) { + paramName = parameter.getName(); + val = request.fileItem(paramName); + } + args[i] = val; + } + } + return args; + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/view/resolve/RouteViewResolve.java b/blade-core/src/main/java/com/blade/mvc/view/resolve/RouteViewResolve.java new file mode 100644 index 000000000..f615812c1 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/view/resolve/RouteViewResolve.java @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.view.resolve; + +import java.lang.reflect.Method; + +import com.blade.exception.BladeException; +import com.blade.ioc.Ioc; +import com.blade.kit.reflect.ReflectKit; +import com.blade.mvc.annotation.JSON; +import com.blade.mvc.annotation.RestController; +import com.blade.mvc.http.Request; +import com.blade.mvc.http.Response; +import com.blade.mvc.route.Route; +import com.blade.mvc.view.ModelAndView; +import com.blade.mvc.view.ViewSettings; + +public class RouteViewResolve { + + private Ioc ioc; + private ViewSettings viewSettings; + public RouteViewResolve(Ioc ioc) { + this.ioc = ioc; + this.viewSettings = ViewSettings.$(); + } + + public void handle(Request request, Response response, Route route) throws Exception { + try { + Method actionMethod = route.getAction(); + Object target = route.getTarget(); + + int len = actionMethod.getParameterTypes().length; + Object returnParam; + if (len > 0) { + Object[] args = MethodArgument.getArgs(request, response, actionMethod); + returnParam = ReflectKit.invokeMehod(target, actionMethod, args); + } else { + returnParam = ReflectKit.invokeMehod(target, actionMethod); + } + + if (null != returnParam) { + Class returnType = returnParam.getClass(); + RestController restController = target.getClass().getAnnotation(RestController.class); + JSON json = actionMethod.getAnnotation(JSON.class); + if(null != restController || null != json){ + response.json(viewSettings.toJSONString(returnParam)); + } else{ + if (returnType == String.class) { + response.render(returnParam.toString()); + } else if (returnType == ModelAndView.class) { + ModelAndView modelAndView = (ModelAndView) returnParam; + response.render(modelAndView); + } + } + } + } catch (Exception e){ + throw new BladeException(e); + } + } + + public boolean intercept(Request request, Response response, Route route) throws BladeException { + Method actionMethod = route.getAction(); + Object target = route.getTarget(); + + if (null == target) { + Class clazz = route.getAction().getDeclaringClass(); + target = ioc.getBean(clazz); + route.setTarget(target); + } + + // execute + int len = actionMethod.getParameterTypes().length; + actionMethod.setAccessible(true); + try { + Object returnParam; + if (len > 0) { + Object[] args = MethodArgument.getArgs(request, response, actionMethod); + returnParam = ReflectKit.invokeMehod(target, actionMethod, args); + } else { + returnParam = ReflectKit.invokeMehod(target, actionMethod); + } + + if (null != returnParam) { + Class returnType = returnParam.getClass(); + if (returnType == Boolean.class || returnType == boolean.class) { + return (Boolean) returnParam; + } + } + return true; + } catch (Exception e) { + throw new BladeException(e); + } + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/view/template/DefaultEngine.java b/blade-core/src/main/java/com/blade/mvc/view/template/DefaultEngine.java new file mode 100644 index 000000000..00df9fec5 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/view/template/DefaultEngine.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.view.template; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Writer; + +import javax.servlet.http.HttpServletResponse; + +import com.blade.Blade; +import com.blade.context.WebContextHolder; +import com.blade.kit.StreamKit; +import com.blade.mvc.view.ModelAndView; + + +/** + * JSP Render, Default Render + * + * @author biezhi + * @since 1.6.6 + */ +public final class DefaultEngine implements TemplateEngine { + + private String templatePath = "/templates/"; + + public DefaultEngine() { + } + + public DefaultEngine(String templatePath) { + this.templatePath = templatePath; + } + + @Override + public void render(ModelAndView modelAndView, Writer writer) throws TemplateException { + try { + HttpServletResponse servletResponse = WebContextHolder.response().raw(); + servletResponse.setContentType("text/html;charset=utf-8"); + String realPath = new File(Blade.$().webRoot() + File.separatorChar + templatePath + File.separatorChar + modelAndView.getView()).getPath(); + String content = StreamKit.readText(new BufferedReader(new FileReader(new File(realPath)))); + servletResponse.getWriter().print(content); + } catch (IOException e) { + throw new TemplateException(e.getMessage()); + } + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/view/template/JspEngine.java b/blade-core/src/main/java/com/blade/mvc/view/template/JspEngine.java new file mode 100644 index 000000000..1b0d1d5a2 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/view/template/JspEngine.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.view.template; + +import java.io.IOException; +import java.io.Writer; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.blade.context.WebContextHolder; +import com.blade.mvc.view.ModelAndView; + + +/** + * JSP Render, Default Render + * + * @author biezhi + * @since 1.5 + */ +public final class JspEngine implements TemplateEngine { + + private String viewPath = "/views/"; + private String suffix = ".jsp"; + + public JspEngine() { + } + + public JspEngine(String viewPath) { + this.viewPath = viewPath; + } + + public String getViewPath() { + return viewPath; + } + + public void setViewPath(String viewPath) { + this.viewPath = viewPath; + } + + public String getSuffix() { + return suffix; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } + + @Override + public void render(ModelAndView modelAndView, Writer writer) throws TemplateException { + HttpServletRequest request = WebContextHolder.request().raw(); + HttpServletResponse response = WebContextHolder.response().raw(); + try { + Map model = modelAndView.getModel(); + String realPath = viewPath + modelAndView.getView() + suffix; + + if (!model.isEmpty()) { + Set keys = model.keySet(); + for (String key : keys) { + request.setAttribute(key, model.get(key)); + } + } + request.getRequestDispatcher(realPath).forward(request, response); + } catch (ServletException e) { + throw new TemplateException(e); + } catch (IOException e) { + throw new TemplateException(e); + } catch (Exception e) { + throw new TemplateException(e); + } + } + +} diff --git a/blade-kit/src/main/java/blade/kit/timw/TimwManager.java b/blade-core/src/main/java/com/blade/mvc/view/template/TemplateEngine.java similarity index 68% rename from blade-kit/src/main/java/blade/kit/timw/TimwManager.java rename to blade-core/src/main/java/com/blade/mvc/view/template/TemplateEngine.java index 6ba736297..d5a6b27c1 100644 --- a/blade-kit/src/main/java/blade/kit/timw/TimwManager.java +++ b/blade-core/src/main/java/com/blade/mvc/view/template/TemplateEngine.java @@ -1,39 +1,32 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.kit.timw; - -/** - * 计时管理器 - *

- * 用于创建计时器 - *

- * - * @author biezhi - * @since 1.0 - */ -public final class TimwManager { - - private TimwManager() { - } - - /** - * @return 返回一个计时器对象 - */ - public static TimwMonitor getTimerMonitor(){ - return new TimwMonitor(); - } - -} +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.view.template; + +import java.io.Writer; + +import com.blade.mvc.view.ModelAndView; + +/** + * TemplateEngine Interface, For view layer to display data + * + * @author biezhi + * @since 1.5 + */ +public interface TemplateEngine { + + void render(ModelAndView modelAndView, Writer writer) throws TemplateException; + +} diff --git a/blade-core/src/main/java/com/blade/mvc/view/template/TemplateException.java b/blade-core/src/main/java/com/blade/mvc/view/template/TemplateException.java new file mode 100644 index 000000000..dfcccb9ea --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/view/template/TemplateException.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.mvc.view.template; + +/** + * TemplateException + * + * @author biezhi + * @since 1.5 + */ +public class TemplateException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public TemplateException() { + super(); + } + + public TemplateException(String message, Throwable cause) { + super(message, cause); + } + + public TemplateException(String message) { + super(message); + } + + public TemplateException(Throwable cause) { + super(cause); + } + +} diff --git a/blade-core/src/main/java/com/blade/mvc/view/template/package-info.java b/blade-core/src/main/java/com/blade/mvc/view/template/package-info.java new file mode 100644 index 000000000..349821df7 --- /dev/null +++ b/blade-core/src/main/java/com/blade/mvc/view/template/package-info.java @@ -0,0 +1,4 @@ +/** + * Blade Render + */ +package com.blade.mvc.view.template; \ No newline at end of file diff --git a/blade-core/src/main/java/com/blade/package-info.java b/blade-core/src/main/java/com/blade/package-info.java new file mode 100644 index 000000000..a81c553be --- /dev/null +++ b/blade-core/src/main/java/com/blade/package-info.java @@ -0,0 +1,4 @@ +/** + * Blade + */ +package com.blade; \ No newline at end of file diff --git a/blade-core/src/main/java/blade/plugin/Plugin.java b/blade-core/src/main/java/com/blade/plugin/Plugin.java similarity index 79% rename from blade-core/src/main/java/blade/plugin/Plugin.java rename to blade-core/src/main/java/com/blade/plugin/Plugin.java index c194a60b4..aac6866d8 100644 --- a/blade-core/src/main/java/blade/plugin/Plugin.java +++ b/blade-core/src/main/java/com/blade/plugin/Plugin.java @@ -13,19 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.plugin; +package com.blade.plugin; /** - * 插件顶层接口 + * Plugin Interface * * @author biezhi - * @since 1.0 + * @since 1.5 */ public interface Plugin { /** - * 要执行的方法 + * Start Plugin */ - void run(); + void startup(); + /** + * Destroy, release resources + */ + void shutdown(); } diff --git a/blade-core/src/main/java/com/blade/plugin/package-info.java b/blade-core/src/main/java/com/blade/plugin/package-info.java new file mode 100644 index 000000000..9ef5ba337 --- /dev/null +++ b/blade-core/src/main/java/com/blade/plugin/package-info.java @@ -0,0 +1,4 @@ +/** + * Blade Plugin + */ +package com.blade.plugin; \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/exception/ClassReaderException.java b/blade-core/src/main/java/com/blade/websocket/WebSocketException.java similarity index 54% rename from blade-kit/src/main/java/blade/exception/ClassReaderException.java rename to blade-core/src/main/java/com/blade/websocket/WebSocketException.java index 93f49cb46..2748cbdce 100644 --- a/blade-kit/src/main/java/blade/exception/ClassReaderException.java +++ b/blade-core/src/main/java/com/blade/websocket/WebSocketException.java @@ -1,47 +1,49 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.exception; - -/** - * 类读取异常 - * - * @author biezhi - * @since 1.0 - */ -public class ClassReaderException extends RuntimeException { - - private static final long serialVersionUID = -1L; - - public ClassReaderException() { - super(); - } - - public ClassReaderException(Exception e) { - super(e); - } - - public ClassReaderException(String msg) { - super(msg); - } - - public ClassReaderException(String msg, Exception e) { - super(msg, e); - } - - public Throwable fillInStackTrace() { - return null; - } -} \ No newline at end of file +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.websocket; + +import com.blade.exception.BladeException; + +/** + * WebSocketException + * + * @author biezhi + * @since 1.6.6 + */ +public class WebSocketException extends BladeException { + + /** + * + */ + private static final long serialVersionUID = -5109944185187744851L; + + public WebSocketException() { + super(); + } + + public WebSocketException(String message, Throwable cause) { + super(message, cause); + } + + public WebSocketException(String message) { + super(message); + } + + public WebSocketException(Throwable cause) { + super(cause); + } + +} diff --git a/blade-core/src/main/java/com/blade/websocket/WebSocketServer.java b/blade-core/src/main/java/com/blade/websocket/WebSocketServer.java new file mode 100644 index 000000000..19b7a2407 --- /dev/null +++ b/blade-core/src/main/java/com/blade/websocket/WebSocketServer.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.websocket; + +/** + * WebSocket API + * + * @author biezhi + * @since 1.6.6 + */ +public interface WebSocketServer { + + void start(int port) throws WebSocketException; + + void start(int port, String context) throws WebSocketException; + + void join() throws WebSocketException; + + void stop() throws WebSocketException; + + void addEndpoints(Class... endPoints); + +} \ No newline at end of file diff --git a/blade-core/src/main/java/org/objectweb/asm/AnnotationVisitor.java b/blade-core/src/main/java/org/objectweb/asm/AnnotationVisitor.java new file mode 100644 index 000000000..b64408372 --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/AnnotationVisitor.java @@ -0,0 +1,169 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * A visitor to visit a Java annotation. The methods of this class must be + * called in the following order: ( visit | visitEnum | + * visitAnnotation | visitArray )* visitEnd. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public abstract class AnnotationVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + protected final int api; + + /** + * The annotation visitor to which this visitor must delegate method calls. + * May be null. + */ + protected AnnotationVisitor av; + + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + public AnnotationVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + * @param av + * the annotation visitor to which this visitor must delegate + * method calls. May be null. + */ + public AnnotationVisitor(final int api, final AnnotationVisitor av) { + if (api != Opcodes.ASM4 && api != Opcodes.ASM5) { + throw new IllegalArgumentException(); + } + this.api = api; + this.av = av; + } + + /** + * Visits a primitive value of the annotation. + * + * @param name + * the value name. + * @param value + * the actual value, whose type must be {@link Byte}, + * {@link Boolean}, {@link Character}, {@link Short}, + * {@link Integer} , {@link Long}, {@link Float}, {@link Double}, + * {@link String} or {@link Type} or OBJECT or ARRAY sort. This + * value can also be an array of byte, boolean, short, char, int, + * long, float or double values (this is equivalent to using + * {@link #visitArray visitArray} and visiting each array element + * in turn, but is more convenient). + */ + public void visit(String name, Object value) { + if (av != null) { + av.visit(name, value); + } + } + + /** + * Visits an enumeration value of the annotation. + * + * @param name + * the value name. + * @param desc + * the class descriptor of the enumeration class. + * @param value + * the actual enumeration value. + */ + public void visitEnum(String name, String desc, String value) { + if (av != null) { + av.visitEnum(name, desc, value); + } + } + + /** + * Visits a nested annotation value of the annotation. + * + * @param name + * the value name. + * @param desc + * the class descriptor of the nested annotation class. + * @return a visitor to visit the actual nested annotation value, or + * null if this visitor is not interested in visiting this + * nested annotation. The nested annotation value must be fully + * visited before calling other methods on this annotation + * visitor. + */ + public AnnotationVisitor visitAnnotation(String name, String desc) { + if (av != null) { + return av.visitAnnotation(name, desc); + } + return null; + } + + /** + * Visits an array value of the annotation. Note that arrays of primitive + * types (such as byte, boolean, short, char, int, long, float or double) + * can be passed as value to {@link #visit visit}. This is what + * {@link ClassReader} does. + * + * @param name + * the value name. + * @return a visitor to visit the actual array value elements, or + * null if this visitor is not interested in visiting these + * values. The 'name' parameters passed to the methods of this + * visitor are ignored. All the array values must be visited + * before calling other methods on this annotation visitor. + */ + public AnnotationVisitor visitArray(String name) { + if (av != null) { + return av.visitArray(name); + } + return null; + } + + /** + * Visits the end of the annotation. + */ + public void visitEnd() { + if (av != null) { + av.visitEnd(); + } + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/AnnotationWriter.java b/blade-core/src/main/java/org/objectweb/asm/AnnotationWriter.java new file mode 100644 index 000000000..6b95608ac --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/AnnotationWriter.java @@ -0,0 +1,371 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * An {@link AnnotationVisitor} that generates annotations in bytecode form. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +final class AnnotationWriter extends AnnotationVisitor { + + /** + * The class writer to which this annotation must be added. + */ + private final ClassWriter cw; + + /** + * The number of values in this annotation. + */ + private int size; + + /** + * true if values are named, false otherwise. Annotation + * writers used for annotation default and annotation arrays use unnamed + * values. + */ + private final boolean named; + + /** + * The annotation values in bytecode form. This byte vector only contains + * the values themselves, i.e. the number of values must be stored as a + * unsigned short just before these bytes. + */ + private final ByteVector bv; + + /** + * The byte vector to be used to store the number of values of this + * annotation. See {@link #bv}. + */ + private final ByteVector parent; + + /** + * Where the number of values of this annotation must be stored in + * {@link #parent}. + */ + private final int offset; + + /** + * Next annotation writer. This field is used to store annotation lists. + */ + AnnotationWriter next; + + /** + * Previous annotation writer. This field is used to store annotation lists. + */ + AnnotationWriter prev; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link AnnotationWriter}. + * + * @param cw + * the class writer to which this annotation must be added. + * @param named + * true if values are named, false otherwise. + * @param bv + * where the annotation values must be stored. + * @param parent + * where the number of annotation values must be stored. + * @param offset + * where in parent the number of annotation values must + * be stored. + */ + AnnotationWriter(final ClassWriter cw, final boolean named, + final ByteVector bv, final ByteVector parent, final int offset) { + super(Opcodes.ASM5); + this.cw = cw; + this.named = named; + this.bv = bv; + this.parent = parent; + this.offset = offset; + } + + // ------------------------------------------------------------------------ + // Implementation of the AnnotationVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public void visit(final String name, final Object value) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + if (value instanceof String) { + bv.put12('s', cw.newUTF8((String) value)); + } else if (value instanceof Byte) { + bv.put12('B', cw.newInteger(((Byte) value).byteValue()).index); + } else if (value instanceof Boolean) { + int v = ((Boolean) value).booleanValue() ? 1 : 0; + bv.put12('Z', cw.newInteger(v).index); + } else if (value instanceof Character) { + bv.put12('C', cw.newInteger(((Character) value).charValue()).index); + } else if (value instanceof Short) { + bv.put12('S', cw.newInteger(((Short) value).shortValue()).index); + } else if (value instanceof Type) { + bv.put12('c', cw.newUTF8(((Type) value).getDescriptor())); + } else if (value instanceof byte[]) { + byte[] v = (byte[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('B', cw.newInteger(v[i]).index); + } + } else if (value instanceof boolean[]) { + boolean[] v = (boolean[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('Z', cw.newInteger(v[i] ? 1 : 0).index); + } + } else if (value instanceof short[]) { + short[] v = (short[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('S', cw.newInteger(v[i]).index); + } + } else if (value instanceof char[]) { + char[] v = (char[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('C', cw.newInteger(v[i]).index); + } + } else if (value instanceof int[]) { + int[] v = (int[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('I', cw.newInteger(v[i]).index); + } + } else if (value instanceof long[]) { + long[] v = (long[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('J', cw.newLong(v[i]).index); + } + } else if (value instanceof float[]) { + float[] v = (float[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('F', cw.newFloat(v[i]).index); + } + } else if (value instanceof double[]) { + double[] v = (double[]) value; + bv.put12('[', v.length); + for (int i = 0; i < v.length; i++) { + bv.put12('D', cw.newDouble(v[i]).index); + } + } else { + Item i = cw.newConstItem(value); + bv.put12(".s.IFJDCS".charAt(i.type), i.index); + } + } + + @Override + public void visitEnum(final String name, final String desc, + final String value) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + bv.put12('e', cw.newUTF8(desc)).putShort(cw.newUTF8(value)); + } + + @Override + public AnnotationVisitor visitAnnotation(final String name, + final String desc) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + // write tag and type, and reserve space for values count + bv.put12('@', cw.newUTF8(desc)).putShort(0); + return new AnnotationWriter(cw, true, bv, bv, bv.length - 2); + } + + @Override + public AnnotationVisitor visitArray(final String name) { + ++size; + if (named) { + bv.putShort(cw.newUTF8(name)); + } + // write tag, and reserve space for array size + bv.put12('[', 0); + return new AnnotationWriter(cw, false, bv, bv, bv.length - 2); + } + + @Override + public void visitEnd() { + if (parent != null) { + byte[] data = parent.data; + data[offset] = (byte) (size >>> 8); + data[offset + 1] = (byte) size; + } + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Returns the size of this annotation writer list. + * + * @return the size of this annotation writer list. + */ + int getSize() { + int size = 0; + AnnotationWriter aw = this; + while (aw != null) { + size += aw.bv.length; + aw = aw.next; + } + return size; + } + + /** + * Puts the annotations of this annotation writer list into the given byte + * vector. + * + * @param out + * where the annotations must be put. + */ + void put(final ByteVector out) { + int n = 0; + int size = 2; + AnnotationWriter aw = this; + AnnotationWriter last = null; + while (aw != null) { + ++n; + size += aw.bv.length; + aw.visitEnd(); // in case user forgot to call visitEnd + aw.prev = last; + last = aw; + aw = aw.next; + } + out.putInt(size); + out.putShort(n); + aw = last; + while (aw != null) { + out.putByteArray(aw.bv.data, 0, aw.bv.length); + aw = aw.prev; + } + } + + /** + * Puts the given annotation lists into the given byte vector. + * + * @param panns + * an array of annotation writer lists. + * @param off + * index of the first annotation to be written. + * @param out + * where the annotations must be put. + */ + static void put(final AnnotationWriter[] panns, final int off, + final ByteVector out) { + int size = 1 + 2 * (panns.length - off); + for (int i = off; i < panns.length; ++i) { + size += panns[i] == null ? 0 : panns[i].getSize(); + } + out.putInt(size).putByte(panns.length - off); + for (int i = off; i < panns.length; ++i) { + AnnotationWriter aw = panns[i]; + AnnotationWriter last = null; + int n = 0; + while (aw != null) { + ++n; + aw.visitEnd(); // in case user forgot to call visitEnd + aw.prev = last; + last = aw; + aw = aw.next; + } + out.putShort(n); + aw = last; + while (aw != null) { + out.putByteArray(aw.bv.data, 0, aw.bv.length); + aw = aw.prev; + } + } + } + + /** + * Puts the given type reference and type path into the given bytevector. + * LOCAL_VARIABLE and RESOURCE_VARIABLE target types are not supported. + * + * @param typeRef + * a reference to the annotated type. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + * @param out + * where the type reference and type path must be put. + */ + static void putTarget(int typeRef, TypePath typePath, ByteVector out) { + switch (typeRef >>> 24) { + case 0x00: // CLASS_TYPE_PARAMETER + case 0x01: // METHOD_TYPE_PARAMETER + case 0x16: // METHOD_FORMAL_PARAMETER + out.putShort(typeRef >>> 16); + break; + case 0x13: // FIELD + case 0x14: // METHOD_RETURN + case 0x15: // METHOD_RECEIVER + out.putByte(typeRef >>> 24); + break; + case 0x47: // CAST + case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT + case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT + out.putInt(typeRef); + break; + // case 0x10: // CLASS_EXTENDS + // case 0x11: // CLASS_TYPE_PARAMETER_BOUND + // case 0x12: // METHOD_TYPE_PARAMETER_BOUND + // case 0x17: // THROWS + // case 0x42: // EXCEPTION_PARAMETER + // case 0x43: // INSTANCEOF + // case 0x44: // NEW + // case 0x45: // CONSTRUCTOR_REFERENCE + // case 0x46: // METHOD_REFERENCE + default: + out.put12(typeRef >>> 24, (typeRef & 0xFFFF00) >> 8); + break; + } + if (typePath == null) { + out.putByte(0); + } else { + int length = typePath.b[typePath.offset] * 2 + 1; + out.putByteArray(typePath.b, typePath.offset, length); + } + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/Attribute.java b/blade-core/src/main/java/org/objectweb/asm/Attribute.java new file mode 100644 index 000000000..8a2a882f8 --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/Attribute.java @@ -0,0 +1,255 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * A non standard class, field, method or code attribute. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class Attribute { + + /** + * The type of this attribute. + */ + public final String type; + + /** + * The raw value of this attribute, used only for unknown attributes. + */ + byte[] value; + + /** + * The next attribute in this attribute list. May be null. + */ + Attribute next; + + /** + * Constructs a new empty attribute. + * + * @param type + * the type of the attribute. + */ + protected Attribute(final String type) { + this.type = type; + } + + /** + * Returns true if this type of attribute is unknown. The default + * implementation of this method always returns true. + * + * @return true if this type of attribute is unknown. + */ + public boolean isUnknown() { + return true; + } + + /** + * Returns true if this type of attribute is a code attribute. + * + * @return true if this type of attribute is a code attribute. + */ + public boolean isCodeAttribute() { + return false; + } + + /** + * Returns the labels corresponding to this attribute. + * + * @return the labels corresponding to this attribute, or null if + * this attribute is not a code attribute that contains labels. + */ + protected Label[] getLabels() { + return null; + } + + /** + * Reads a {@link #type type} attribute. This method must return a + * new {@link Attribute} object, of type {@link #type type}, + * corresponding to the len bytes starting at the given offset, in + * the given class reader. + * + * @param cr + * the class that contains the attribute to be read. + * @param off + * index of the first byte of the attribute's content in + * {@link ClassReader#b cr.b}. The 6 attribute header bytes, + * containing the type and the length of the attribute, are not + * taken into account here. + * @param len + * the length of the attribute's content. + * @param buf + * buffer to be used to call {@link ClassReader#readUTF8 + * readUTF8}, {@link ClassReader#readClass(int,char[]) readClass} + * or {@link ClassReader#readConst readConst}. + * @param codeOff + * index of the first byte of code's attribute content in + * {@link ClassReader#b cr.b}, or -1 if the attribute to be read + * is not a code attribute. The 6 attribute header bytes, + * containing the type and the length of the attribute, are not + * taken into account here. + * @param labels + * the labels of the method's code, or null if the + * attribute to be read is not a code attribute. + * @return a new {@link Attribute} object corresponding to the given + * bytes. + */ + protected Attribute read(final ClassReader cr, final int off, + final int len, final char[] buf, final int codeOff, + final Label[] labels) { + Attribute attr = new Attribute(type); + attr.value = new byte[len]; + System.arraycopy(cr.b, off, attr.value, 0, len); + return attr; + } + + /** + * Returns the byte array form of this attribute. + * + * @param cw + * the class to which this attribute must be added. This + * parameter can be used to add to the constant pool of this + * class the items that corresponds to this attribute. + * @param code + * the bytecode of the method corresponding to this code + * attribute, or null if this attribute is not a code + * attributes. + * @param len + * the length of the bytecode of the method corresponding to this + * code attribute, or null if this attribute is not a + * code attribute. + * @param maxStack + * the maximum stack size of the method corresponding to this + * code attribute, or -1 if this attribute is not a code + * attribute. + * @param maxLocals + * the maximum number of local variables of the method + * corresponding to this code attribute, or -1 if this attribute + * is not a code attribute. + * @return the byte array form of this attribute. + */ + protected ByteVector write(final ClassWriter cw, final byte[] code, + final int len, final int maxStack, final int maxLocals) { + ByteVector v = new ByteVector(); + v.data = value; + v.length = value.length; + return v; + } + + /** + * Returns the length of the attribute list that begins with this attribute. + * + * @return the length of the attribute list that begins with this attribute. + */ + final int getCount() { + int count = 0; + Attribute attr = this; + while (attr != null) { + count += 1; + attr = attr.next; + } + return count; + } + + /** + * Returns the size of all the attributes in this attribute list. + * + * @param cw + * the class writer to be used to convert the attributes into + * byte arrays, with the {@link #write write} method. + * @param code + * the bytecode of the method corresponding to these code + * attributes, or null if these attributes are not code + * attributes. + * @param len + * the length of the bytecode of the method corresponding to + * these code attributes, or null if these attributes + * are not code attributes. + * @param maxStack + * the maximum stack size of the method corresponding to these + * code attributes, or -1 if these attributes are not code + * attributes. + * @param maxLocals + * the maximum number of local variables of the method + * corresponding to these code attributes, or -1 if these + * attributes are not code attributes. + * @return the size of all the attributes in this attribute list. This size + * includes the size of the attribute headers. + */ + final int getSize(final ClassWriter cw, final byte[] code, final int len, + final int maxStack, final int maxLocals) { + Attribute attr = this; + int size = 0; + while (attr != null) { + cw.newUTF8(attr.type); + size += attr.write(cw, code, len, maxStack, maxLocals).length + 6; + attr = attr.next; + } + return size; + } + + /** + * Writes all the attributes of this attribute list in the given byte + * vector. + * + * @param cw + * the class writer to be used to convert the attributes into + * byte arrays, with the {@link #write write} method. + * @param code + * the bytecode of the method corresponding to these code + * attributes, or null if these attributes are not code + * attributes. + * @param len + * the length of the bytecode of the method corresponding to + * these code attributes, or null if these attributes + * are not code attributes. + * @param maxStack + * the maximum stack size of the method corresponding to these + * code attributes, or -1 if these attributes are not code + * attributes. + * @param maxLocals + * the maximum number of local variables of the method + * corresponding to these code attributes, or -1 if these + * attributes are not code attributes. + * @param out + * where the attributes must be written. + */ + final void put(final ClassWriter cw, final byte[] code, final int len, + final int maxStack, final int maxLocals, final ByteVector out) { + Attribute attr = this; + while (attr != null) { + ByteVector b = attr.write(cw, code, len, maxStack, maxLocals); + out.putShort(cw.newUTF8(attr.type)).putInt(b.length); + out.putByteArray(b.data, 0, b.length); + attr = attr.next; + } + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/ByteVector.java b/blade-core/src/main/java/org/objectweb/asm/ByteVector.java new file mode 100644 index 000000000..9c532be78 --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/ByteVector.java @@ -0,0 +1,339 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * A dynamically extensible vector of bytes. This class is roughly equivalent to + * a DataOutputStream on top of a ByteArrayOutputStream, but is more efficient. + * + * @author Eric Bruneton + */ +public class ByteVector { + + /** + * The content of this vector. + */ + byte[] data; + + /** + * Actual number of bytes in this vector. + */ + int length; + + /** + * Constructs a new {@link ByteVector ByteVector} with a default initial + * size. + */ + public ByteVector() { + data = new byte[64]; + } + + /** + * Constructs a new {@link ByteVector ByteVector} with the given initial + * size. + * + * @param initialSize + * the initial size of the byte vector to be constructed. + */ + public ByteVector(final int initialSize) { + data = new byte[initialSize]; + } + + /** + * Puts a byte into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param b + * a byte. + * @return this byte vector. + */ + public ByteVector putByte(final int b) { + int length = this.length; + if (length + 1 > data.length) { + enlarge(1); + } + data[length++] = (byte) b; + this.length = length; + return this; + } + + /** + * Puts two bytes into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param b1 + * a byte. + * @param b2 + * another byte. + * @return this byte vector. + */ + ByteVector put11(final int b1, final int b2) { + int length = this.length; + if (length + 2 > data.length) { + enlarge(2); + } + byte[] data = this.data; + data[length++] = (byte) b1; + data[length++] = (byte) b2; + this.length = length; + return this; + } + + /** + * Puts a short into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param s + * a short. + * @return this byte vector. + */ + public ByteVector putShort(final int s) { + int length = this.length; + if (length + 2 > data.length) { + enlarge(2); + } + byte[] data = this.data; + data[length++] = (byte) (s >>> 8); + data[length++] = (byte) s; + this.length = length; + return this; + } + + /** + * Puts a byte and a short into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param b + * a byte. + * @param s + * a short. + * @return this byte vector. + */ + ByteVector put12(final int b, final int s) { + int length = this.length; + if (length + 3 > data.length) { + enlarge(3); + } + byte[] data = this.data; + data[length++] = (byte) b; + data[length++] = (byte) (s >>> 8); + data[length++] = (byte) s; + this.length = length; + return this; + } + + /** + * Puts an int into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param i + * an int. + * @return this byte vector. + */ + public ByteVector putInt(final int i) { + int length = this.length; + if (length + 4 > data.length) { + enlarge(4); + } + byte[] data = this.data; + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + this.length = length; + return this; + } + + /** + * Puts a long into this byte vector. The byte vector is automatically + * enlarged if necessary. + * + * @param l + * a long. + * @return this byte vector. + */ + public ByteVector putLong(final long l) { + int length = this.length; + if (length + 8 > data.length) { + enlarge(8); + } + byte[] data = this.data; + int i = (int) (l >>> 32); + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + i = (int) l; + data[length++] = (byte) (i >>> 24); + data[length++] = (byte) (i >>> 16); + data[length++] = (byte) (i >>> 8); + data[length++] = (byte) i; + this.length = length; + return this; + } + + /** + * Puts an UTF8 string into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param s + * a String whose UTF8 encoded length must be less than 65536. + * @return this byte vector. + */ + public ByteVector putUTF8(final String s) { + int charLength = s.length(); + if (charLength > 65535) { + throw new IllegalArgumentException(); + } + int len = length; + if (len + 2 + charLength > data.length) { + enlarge(2 + charLength); + } + byte[] data = this.data; + // optimistic algorithm: instead of computing the byte length and then + // serializing the string (which requires two loops), we assume the byte + // length is equal to char length (which is the most frequent case), and + // we start serializing the string right away. During the serialization, + // if we find that this assumption is wrong, we continue with the + // general method. + data[len++] = (byte) (charLength >>> 8); + data[len++] = (byte) charLength; + for (int i = 0; i < charLength; ++i) { + char c = s.charAt(i); + if (c >= '\001' && c <= '\177') { + data[len++] = (byte) c; + } else { + length = len; + return encodeUTF8(s, i, 65535); + } + } + length = len; + return this; + } + + /** + * Puts an UTF8 string into this byte vector. The byte vector is + * automatically enlarged if necessary. The string length is encoded in two + * bytes before the encoded characters, if there is space for that (i.e. if + * this.length - i - 2 >= 0). + * + * @param s + * the String to encode. + * @param i + * the index of the first character to encode. The previous + * characters are supposed to have already been encoded, using + * only one byte per character. + * @param maxByteLength + * the maximum byte length of the encoded string, including the + * already encoded characters. + * @return this byte vector. + */ + ByteVector encodeUTF8(final String s, int i, int maxByteLength) { + int charLength = s.length(); + int byteLength = i; + char c; + for (int j = i; j < charLength; ++j) { + c = s.charAt(j); + if (c >= '\001' && c <= '\177') { + byteLength++; + } else if (c > '\u07FF') { + byteLength += 3; + } else { + byteLength += 2; + } + } + if (byteLength > maxByteLength) { + throw new IllegalArgumentException(); + } + int start = length - i - 2; + if (start >= 0) { + data[start] = (byte) (byteLength >>> 8); + data[start + 1] = (byte) byteLength; + } + if (length + byteLength - i > data.length) { + enlarge(byteLength - i); + } + int len = length; + for (int j = i; j < charLength; ++j) { + c = s.charAt(j); + if (c >= '\001' && c <= '\177') { + data[len++] = (byte) c; + } else if (c > '\u07FF') { + data[len++] = (byte) (0xE0 | c >> 12 & 0xF); + data[len++] = (byte) (0x80 | c >> 6 & 0x3F); + data[len++] = (byte) (0x80 | c & 0x3F); + } else { + data[len++] = (byte) (0xC0 | c >> 6 & 0x1F); + data[len++] = (byte) (0x80 | c & 0x3F); + } + } + length = len; + return this; + } + + /** + * Puts an array of bytes into this byte vector. The byte vector is + * automatically enlarged if necessary. + * + * @param b + * an array of bytes. May be null to put len + * null bytes into this byte vector. + * @param off + * index of the fist byte of b that must be copied. + * @param len + * number of bytes of b that must be copied. + * @return this byte vector. + */ + public ByteVector putByteArray(final byte[] b, final int off, final int len) { + if (length + len > data.length) { + enlarge(len); + } + if (b != null) { + System.arraycopy(b, off, data, length, len); + } + length += len; + return this; + } + + /** + * Enlarge this byte vector so that it can receive n more bytes. + * + * @param size + * number of additional bytes that this byte vector should be + * able to receive. + */ + private void enlarge(final int size) { + int length1 = 2 * data.length; + int length2 = length + size; + byte[] newData = new byte[length1 > length2 ? length1 : length2]; + System.arraycopy(data, 0, newData, 0, length); + data = newData; + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/ClassReader.java b/blade-core/src/main/java/org/objectweb/asm/ClassReader.java new file mode 100644 index 000000000..76b1600ee --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/ClassReader.java @@ -0,0 +1,2507 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +import java.io.IOException; +import java.io.InputStream; + +/** + * A Java class parser to make a {@link ClassVisitor} visit an existing class. + * This class parses a byte array conforming to the Java class file format and + * calls the appropriate visit methods of a given class visitor for each field, + * method and bytecode instruction encountered. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class ClassReader { + + /** + * True to enable signatures support. + */ + static final boolean SIGNATURES = true; + + /** + * True to enable annotations support. + */ + static final boolean ANNOTATIONS = true; + + /** + * True to enable stack map frames support. + */ + static final boolean FRAMES = true; + + /** + * True to enable bytecode writing support. + */ + static final boolean WRITER = true; + + /** + * True to enable JSR_W and GOTO_W support. + */ + static final boolean RESIZE = true; + + /** + * Flag to skip method code. If this class is set CODE + * attribute won't be visited. This can be used, for example, to retrieve + * annotations for methods and method parameters. + */ + public static final int SKIP_CODE = 1; + + /** + * Flag to skip the debug information in the class. If this flag is set the + * debug information of the class is not visited, i.e. the + * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and + * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be + * called. + */ + public static final int SKIP_DEBUG = 2; + + /** + * Flag to skip the stack map frames in the class. If this flag is set the + * stack map frames of the class is not visited, i.e. the + * {@link MethodVisitor#visitFrame visitFrame} method will not be called. + * This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is + * used: it avoids visiting frames that will be ignored and recomputed from + * scratch in the class writer. + */ + public static final int SKIP_FRAMES = 4; + + /** + * Flag to expand the stack map frames. By default stack map frames are + * visited in their original format (i.e. "expanded" for classes whose + * version is less than V1_6, and "compressed" for the other classes). If + * this flag is set, stack map frames are always visited in expanded format + * (this option adds a decompression/recompression step in ClassReader and + * ClassWriter which degrades performances quite a lot). + */ + public static final int EXPAND_FRAMES = 8; + + /** + * The class to be parsed. The content of this array must not be + * modified. This field is intended for {@link Attribute} sub classes, and + * is normally not needed by class generators or adapters. + */ + public final byte[] b; + + /** + * The start index of each constant pool item in {@link #b b}, plus one. The + * one byte offset skips the constant pool item tag that indicates its type. + */ + private final int[] items; + + /** + * The String objects corresponding to the CONSTANT_Utf8 items. This cache + * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item, + * which GREATLY improves performances (by a factor 2 to 3). This caching + * strategy could be extended to all constant pool items, but its benefit + * would not be so great for these items (because they are much less + * expensive to parse than CONSTANT_Utf8 items). + */ + private final String[] strings; + + /** + * Maximum length of the strings contained in the constant pool of the + * class. + */ + private final int maxStringLength; + + /** + * Start index of the class header information (access, name...) in + * {@link #b b}. + */ + public final int header; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link ClassReader} object. + * + * @param b + * the bytecode of the class to be read. + */ + public ClassReader(final byte[] b) { + this(b, 0, b.length); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param b + * the bytecode of the class to be read. + * @param off + * the start offset of the class data. + * @param len + * the length of the class data. + */ + public ClassReader(final byte[] b, final int off, final int len) { + this.b = b; + // checks the class version + if (readShort(off + 6) > Opcodes.V1_8) { + throw new IllegalArgumentException(); + } + // parses the constant pool + items = new int[readUnsignedShort(off + 8)]; + int n = items.length; + strings = new String[n]; + int max = 0; + int index = off + 10; + for (int i = 1; i < n; ++i) { + items[i] = index + 1; + int size; + switch (b[index]) { + case ClassWriter.FIELD: + case ClassWriter.METH: + case ClassWriter.IMETH: + case ClassWriter.INT: + case ClassWriter.FLOAT: + case ClassWriter.NAME_TYPE: + case ClassWriter.INDY: + size = 5; + break; + case ClassWriter.LONG: + case ClassWriter.DOUBLE: + size = 9; + ++i; + break; + case ClassWriter.UTF8: + size = 3 + readUnsignedShort(index + 1); + if (size > max) { + max = size; + } + break; + case ClassWriter.HANDLE: + size = 4; + break; + // case ClassWriter.CLASS: + // case ClassWriter.STR: + // case ClassWriter.MTYPE + default: + size = 3; + break; + } + index += size; + } + maxStringLength = max; + // the class header information starts just after the constant pool + header = index; + } + + /** + * Returns the class's access flags (see {@link Opcodes}). This value may + * not reflect Deprecated and Synthetic flags when bytecode is before 1.5 + * and those flags are represented by attributes. + * + * @return the class access flags + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public int getAccess() { + return readUnsignedShort(header); + } + + /** + * Returns the internal name of the class (see + * {@link Type#getInternalName() getInternalName}). + * + * @return the internal class name + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getClassName() { + return readClass(header + 2, new char[maxStringLength]); + } + + /** + * Returns the internal of name of the super class (see + * {@link Type#getInternalName() getInternalName}). For interfaces, the + * super class is {@link Object}. + * + * @return the internal name of super class, or null for + * {@link Object} class. + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getSuperName() { + return readClass(header + 4, new char[maxStringLength]); + } + + /** + * Returns the internal names of the class's interfaces (see + * {@link Type#getInternalName() getInternalName}). + * + * @return the array of internal names for all implemented interfaces or + * null. + * + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String[] getInterfaces() { + int index = header + 6; + int n = readUnsignedShort(index); + String[] interfaces = new String[n]; + if (n > 0) { + char[] buf = new char[maxStringLength]; + for (int i = 0; i < n; ++i) { + index += 2; + interfaces[i] = readClass(index, buf); + } + } + return interfaces; + } + + /** + * Copies the constant pool data into the given {@link ClassWriter}. Should + * be called before the {@link #accept(ClassVisitor,int)} method. + * + * @param classWriter + * the {@link ClassWriter} to copy constant pool into. + */ + void copyPool(final ClassWriter classWriter) { + char[] buf = new char[maxStringLength]; + int ll = items.length; + Item[] items2 = new Item[ll]; + for (int i = 1; i < ll; i++) { + int index = items[i]; + int tag = b[index - 1]; + Item item = new Item(i); + int nameType; + switch (tag) { + case ClassWriter.FIELD: + case ClassWriter.METH: + case ClassWriter.IMETH: + nameType = items[readUnsignedShort(index + 2)]; + item.set(tag, readClass(index, buf), readUTF8(nameType, buf), + readUTF8(nameType + 2, buf)); + break; + case ClassWriter.INT: + item.set(readInt(index)); + break; + case ClassWriter.FLOAT: + item.set(Float.intBitsToFloat(readInt(index))); + break; + case ClassWriter.NAME_TYPE: + item.set(tag, readUTF8(index, buf), readUTF8(index + 2, buf), + null); + break; + case ClassWriter.LONG: + item.set(readLong(index)); + ++i; + break; + case ClassWriter.DOUBLE: + item.set(Double.longBitsToDouble(readLong(index))); + ++i; + break; + case ClassWriter.UTF8: { + String s = strings[i]; + if (s == null) { + index = items[i]; + s = strings[i] = readUTF(index + 2, + readUnsignedShort(index), buf); + } + item.set(tag, s, null, null); + break; + } + case ClassWriter.HANDLE: { + int fieldOrMethodRef = items[readUnsignedShort(index + 1)]; + nameType = items[readUnsignedShort(fieldOrMethodRef + 2)]; + item.set(ClassWriter.HANDLE_BASE + readByte(index), + readClass(fieldOrMethodRef, buf), + readUTF8(nameType, buf), readUTF8(nameType + 2, buf)); + break; + } + case ClassWriter.INDY: + if (classWriter.bootstrapMethods == null) { + copyBootstrapMethods(classWriter, items2, buf); + } + nameType = items[readUnsignedShort(index + 2)]; + item.set(readUTF8(nameType, buf), readUTF8(nameType + 2, buf), + readUnsignedShort(index)); + break; + // case ClassWriter.STR: + // case ClassWriter.CLASS: + // case ClassWriter.MTYPE + default: + item.set(tag, readUTF8(index, buf), null, null); + break; + } + + int index2 = item.hashCode % items2.length; + item.next = items2[index2]; + items2[index2] = item; + } + + int off = items[1] - 1; + classWriter.pool.putByteArray(b, off, header - off); + classWriter.items = items2; + classWriter.threshold = (int) (0.75d * ll); + classWriter.index = ll; + } + + /** + * Copies the bootstrap method data into the given {@link ClassWriter}. + * Should be called before the {@link #accept(ClassVisitor,int)} method. + * + * @param classWriter + * the {@link ClassWriter} to copy bootstrap methods into. + */ + private void copyBootstrapMethods(final ClassWriter classWriter, + final Item[] items, final char[] c) { + // finds the "BootstrapMethods" attribute + int u = getAttributes(); + boolean found = false; + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + if ("BootstrapMethods".equals(attrName)) { + found = true; + break; + } + u += 6 + readInt(u + 4); + } + if (!found) { + return; + } + // copies the bootstrap methods in the class writer + int boostrapMethodCount = readUnsignedShort(u + 8); + for (int j = 0, v = u + 10; j < boostrapMethodCount; j++) { + int position = v - u - 10; + int hashCode = readConst(readUnsignedShort(v), c).hashCode(); + for (int k = readUnsignedShort(v + 2); k > 0; --k) { + hashCode ^= readConst(readUnsignedShort(v + 4), c).hashCode(); + v += 2; + } + v += 4; + Item item = new Item(j); + item.set(position, hashCode & 0x7FFFFFFF); + int index = item.hashCode % items.length; + item.next = items[index]; + items[index] = item; + } + int attrSize = readInt(u + 4); + ByteVector bootstrapMethods = new ByteVector(attrSize + 62); + bootstrapMethods.putByteArray(b, u + 10, attrSize - 2); + classWriter.bootstrapMethodsCount = boostrapMethodCount; + classWriter.bootstrapMethods = bootstrapMethods; + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param is + * an input stream from which to read the class. + * @throws IOException + * if a problem occurs during reading. + */ + public ClassReader(final InputStream is) throws IOException { + this(readClass(is, false)); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param name + * the binary qualified name of the class to be read. + * @throws IOException + * if an exception occurs during reading. + */ + public ClassReader(final String name) throws IOException { + this(readClass( + ClassLoader.getSystemResourceAsStream(name.replace('.', '/') + + ".class"), true)); + } + + /** + * Reads the bytecode of a class. + * + * @param is + * an input stream from which to read the class. + * @param close + * true to close the input stream after reading. + * @return the bytecode read from the given input stream. + * @throws IOException + * if a problem occurs during reading. + */ + private static byte[] readClass(final InputStream is, boolean close) + throws IOException { + if (is == null) { + throw new IOException("Class not found"); + } + try { + byte[] b = new byte[is.available()]; + int len = 0; + while (true) { + int n = is.read(b, len, b.length - len); + if (n == -1) { + if (len < b.length) { + byte[] c = new byte[len]; + System.arraycopy(b, 0, c, 0, len); + b = c; + } + return b; + } + len += n; + if (len == b.length) { + int last = is.read(); + if (last < 0) { + return b; + } + byte[] c = new byte[b.length + 1000]; + System.arraycopy(b, 0, c, 0, len); + c[len++] = (byte) last; + b = c; + } + } + } finally { + if (close) { + is.close(); + } + } + } + + // ------------------------------------------------------------------------ + // Public methods + // ------------------------------------------------------------------------ + + /** + * Makes the given visitor visit the Java class of this {@link ClassReader} + * . This class is the one specified in the constructor (see + * {@link #ClassReader(byte[]) ClassReader}). + * + * @param classVisitor + * the visitor that must visit this class. + * @param flags + * option flags that can be used to modify the default behavior + * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES} + * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. + */ + public void accept(final ClassVisitor classVisitor, final int flags) { + accept(classVisitor, new Attribute[0], flags); + } + + /** + * Makes the given visitor visit the Java class of this {@link ClassReader}. + * This class is the one specified in the constructor (see + * {@link #ClassReader(byte[]) ClassReader}). + * + * @param classVisitor + * the visitor that must visit this class. + * @param attrs + * prototypes of the attributes that must be parsed during the + * visit of the class. Any attribute whose type is not equal to + * the type of one the prototypes will not be parsed: its byte + * array value will be passed unchanged to the ClassWriter. + * This may corrupt it if this value contains references to + * the constant pool, or has syntactic or semantic links with a + * class element that has been transformed by a class adapter + * between the reader and the writer. + * @param flags + * option flags that can be used to modify the default behavior + * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES} + * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}. + */ + public void accept(final ClassVisitor classVisitor, + final Attribute[] attrs, final int flags) { + int u = header; // current offset in the class file + char[] c = new char[maxStringLength]; // buffer used to read strings + + Context context = new Context(); + context.attrs = attrs; + context.flags = flags; + context.buffer = c; + + // reads the class declaration + int access = readUnsignedShort(u); + String name = readClass(u + 2, c); + String superClass = readClass(u + 4, c); + String[] interfaces = new String[readUnsignedShort(u + 6)]; + u += 8; + for (int i = 0; i < interfaces.length; ++i) { + interfaces[i] = readClass(u, c); + u += 2; + } + + // reads the class attributes + String signature = null; + String sourceFile = null; + String sourceDebug = null; + String enclosingOwner = null; + String enclosingName = null; + String enclosingDesc = null; + int anns = 0; + int ianns = 0; + int tanns = 0; + int itanns = 0; + int innerClasses = 0; + Attribute attributes = null; + + u = getAttributes(); + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("SourceFile".equals(attrName)) { + sourceFile = readUTF8(u + 8, c); + } else if ("InnerClasses".equals(attrName)) { + innerClasses = u + 8; + } else if ("EnclosingMethod".equals(attrName)) { + enclosingOwner = readClass(u + 8, c); + int item = readUnsignedShort(u + 10); + if (item != 0) { + enclosingName = readUTF8(items[item], c); + enclosingDesc = readUTF8(items[item] + 2, c); + } + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(u + 8, c); + } else if (ANNOTATIONS + && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = u + 8; + } else if (ANNOTATIONS + && "RuntimeVisibleTypeAnnotations".equals(attrName)) { + tanns = u + 8; + } else if ("Deprecated".equals(attrName)) { + access |= Opcodes.ACC_DEPRECATED; + } else if ("Synthetic".equals(attrName)) { + access |= Opcodes.ACC_SYNTHETIC + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if ("SourceDebugExtension".equals(attrName)) { + int len = readInt(u + 4); + sourceDebug = readUTF(u + 8, len, new char[len]); + } else if (ANNOTATIONS + && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleTypeAnnotations".equals(attrName)) { + itanns = u + 8; + } else if ("BootstrapMethods".equals(attrName)) { + int[] bootstrapMethods = new int[readUnsignedShort(u + 8)]; + for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) { + bootstrapMethods[j] = v; + v += 2 + readUnsignedShort(v + 2) << 1; + } + context.bootstrapMethods = bootstrapMethods; + } else { + Attribute attr = readAttribute(attrs, attrName, u + 8, + readInt(u + 4), c, -1, null); + if (attr != null) { + attr.next = attributes; + attributes = attr; + } + } + u += 6 + readInt(u + 4); + } + + // visits the class declaration + classVisitor.visit(readInt(items[1] - 7), access, name, signature, + superClass, interfaces); + + // visits the source and debug info + if ((flags & SKIP_DEBUG) == 0 + && (sourceFile != null || sourceDebug != null)) { + classVisitor.visitSource(sourceFile, sourceDebug); + } + + // visits the outer class + if (enclosingOwner != null) { + classVisitor.visitOuterClass(enclosingOwner, enclosingName, + enclosingDesc); + } + + // visits the class annotations and type annotations + if (ANNOTATIONS && anns != 0) { + for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + classVisitor.visitAnnotation(readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && ianns != 0) { + for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + classVisitor.visitAnnotation(readUTF8(v, c), false)); + } + } + if (ANNOTATIONS && tanns != 0) { + for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + classVisitor.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && itanns != 0) { + for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + classVisitor.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), false)); + } + } + + // visits the attributes + while (attributes != null) { + Attribute attr = attributes.next; + attributes.next = null; + classVisitor.visitAttribute(attributes); + attributes = attr; + } + + // visits the inner classes + if (innerClasses != 0) { + int v = innerClasses + 2; + for (int i = readUnsignedShort(innerClasses); i > 0; --i) { + classVisitor.visitInnerClass(readClass(v, c), + readClass(v + 2, c), readUTF8(v + 4, c), + readUnsignedShort(v + 6)); + v += 8; + } + } + + // visits the fields and methods + u = header + 10 + 2 * interfaces.length; + for (int i = readUnsignedShort(u - 2); i > 0; --i) { + u = readField(classVisitor, context, u); + } + u += 2; + for (int i = readUnsignedShort(u - 2); i > 0; --i) { + u = readMethod(classVisitor, context, u); + } + + // visits the end of the class + classVisitor.visitEnd(); + } + + /** + * Reads a field and makes the given visitor visit it. + * + * @param classVisitor + * the visitor that must visit the field. + * @param context + * information about the class being parsed. + * @param u + * the start offset of the field in the class file. + * @return the offset of the first byte following the field in the class. + */ + private int readField(final ClassVisitor classVisitor, + final Context context, int u) { + // reads the field declaration + char[] c = context.buffer; + int access = readUnsignedShort(u); + String name = readUTF8(u + 2, c); + String desc = readUTF8(u + 4, c); + u += 6; + + // reads the field attributes + String signature = null; + int anns = 0; + int ianns = 0; + int tanns = 0; + int itanns = 0; + Object value = null; + Attribute attributes = null; + + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("ConstantValue".equals(attrName)) { + int item = readUnsignedShort(u + 8); + value = item == 0 ? null : readConst(item, c); + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(u + 8, c); + } else if ("Deprecated".equals(attrName)) { + access |= Opcodes.ACC_DEPRECATED; + } else if ("Synthetic".equals(attrName)) { + access |= Opcodes.ACC_SYNTHETIC + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if (ANNOTATIONS + && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = u + 8; + } else if (ANNOTATIONS + && "RuntimeVisibleTypeAnnotations".equals(attrName)) { + tanns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleTypeAnnotations".equals(attrName)) { + itanns = u + 8; + } else { + Attribute attr = readAttribute(context.attrs, attrName, u + 8, + readInt(u + 4), c, -1, null); + if (attr != null) { + attr.next = attributes; + attributes = attr; + } + } + u += 6 + readInt(u + 4); + } + u += 2; + + // visits the field declaration + FieldVisitor fv = classVisitor.visitField(access, name, desc, + signature, value); + if (fv == null) { + return u; + } + + // visits the field annotations and type annotations + if (ANNOTATIONS && anns != 0) { + for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + fv.visitAnnotation(readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && ianns != 0) { + for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + fv.visitAnnotation(readUTF8(v, c), false)); + } + } + if (ANNOTATIONS && tanns != 0) { + for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + fv.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && itanns != 0) { + for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + fv.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), false)); + } + } + + // visits the field attributes + while (attributes != null) { + Attribute attr = attributes.next; + attributes.next = null; + fv.visitAttribute(attributes); + attributes = attr; + } + + // visits the end of the field + fv.visitEnd(); + + return u; + } + + /** + * Reads a method and makes the given visitor visit it. + * + * @param classVisitor + * the visitor that must visit the method. + * @param context + * information about the class being parsed. + * @param u + * the start offset of the method in the class file. + * @return the offset of the first byte following the method in the class. + */ + private int readMethod(final ClassVisitor classVisitor, + final Context context, int u) { + // reads the method declaration + char[] c = context.buffer; + context.access = readUnsignedShort(u); + context.name = readUTF8(u + 2, c); + context.desc = readUTF8(u + 4, c); + u += 6; + + // reads the method attributes + int code = 0; + int exception = 0; + String[] exceptions = null; + String signature = null; + int methodParameters = 0; + int anns = 0; + int ianns = 0; + int tanns = 0; + int itanns = 0; + int dann = 0; + int mpanns = 0; + int impanns = 0; + int firstAttribute = u; + Attribute attributes = null; + + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if ("Code".equals(attrName)) { + if ((context.flags & SKIP_CODE) == 0) { + code = u + 8; + } + } else if ("Exceptions".equals(attrName)) { + exceptions = new String[readUnsignedShort(u + 8)]; + exception = u + 10; + for (int j = 0; j < exceptions.length; ++j) { + exceptions[j] = readClass(exception, c); + exception += 2; + } + } else if (SIGNATURES && "Signature".equals(attrName)) { + signature = readUTF8(u + 8, c); + } else if ("Deprecated".equals(attrName)) { + context.access |= Opcodes.ACC_DEPRECATED; + } else if (ANNOTATIONS + && "RuntimeVisibleAnnotations".equals(attrName)) { + anns = u + 8; + } else if (ANNOTATIONS + && "RuntimeVisibleTypeAnnotations".equals(attrName)) { + tanns = u + 8; + } else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) { + dann = u + 8; + } else if ("Synthetic".equals(attrName)) { + context.access |= Opcodes.ACC_SYNTHETIC + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; + } else if (ANNOTATIONS + && "RuntimeInvisibleAnnotations".equals(attrName)) { + ianns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleTypeAnnotations".equals(attrName)) { + itanns = u + 8; + } else if (ANNOTATIONS + && "RuntimeVisibleParameterAnnotations".equals(attrName)) { + mpanns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleParameterAnnotations".equals(attrName)) { + impanns = u + 8; + } else if ("MethodParameters".equals(attrName)) { + methodParameters = u + 8; + } else { + Attribute attr = readAttribute(context.attrs, attrName, u + 8, + readInt(u + 4), c, -1, null); + if (attr != null) { + attr.next = attributes; + attributes = attr; + } + } + u += 6 + readInt(u + 4); + } + u += 2; + + // visits the method declaration + MethodVisitor mv = classVisitor.visitMethod(context.access, + context.name, context.desc, signature, exceptions); + if (mv == null) { + return u; + } + + /* + * if the returned MethodVisitor is in fact a MethodWriter, it means + * there is no method adapter between the reader and the writer. If, in + * addition, the writer's constant pool was copied from this reader + * (mw.cw.cr == this), and the signature and exceptions of the method + * have not been changed, then it is possible to skip all visit events + * and just copy the original code of the method to the writer (the + * access, name and descriptor can have been changed, this is not + * important since they are not copied as is from the reader). + */ + if (WRITER && mv instanceof MethodWriter) { + MethodWriter mw = (MethodWriter) mv; + if (mw.cw.cr == this && signature == mw.signature) { + boolean sameExceptions = false; + if (exceptions == null) { + sameExceptions = mw.exceptionCount == 0; + } else if (exceptions.length == mw.exceptionCount) { + sameExceptions = true; + for (int j = exceptions.length - 1; j >= 0; --j) { + exception -= 2; + if (mw.exceptions[j] != readUnsignedShort(exception)) { + sameExceptions = false; + break; + } + } + } + if (sameExceptions) { + /* + * we do not copy directly the code into MethodWriter to + * save a byte array copy operation. The real copy will be + * done in ClassWriter.toByteArray(). + */ + mw.classReaderOffset = firstAttribute; + mw.classReaderLength = u - firstAttribute; + return u; + } + } + } + + // visit the method parameters + if (methodParameters != 0) { + for (int i = b[methodParameters] & 0xFF, v = methodParameters + 1; i > 0; --i, v = v + 4) { + mv.visitParameter(readUTF8(v, c), readUnsignedShort(v + 2)); + } + } + + // visits the method annotations + if (ANNOTATIONS && dann != 0) { + AnnotationVisitor dv = mv.visitAnnotationDefault(); + readAnnotationValue(dann, c, null, dv); + if (dv != null) { + dv.visitEnd(); + } + } + if (ANNOTATIONS && anns != 0) { + for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + mv.visitAnnotation(readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && ianns != 0) { + for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) { + v = readAnnotationValues(v + 2, c, true, + mv.visitAnnotation(readUTF8(v, c), false)); + } + } + if (ANNOTATIONS && tanns != 0) { + for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + mv.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && itanns != 0) { + for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + mv.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), false)); + } + } + if (ANNOTATIONS && mpanns != 0) { + readParameterAnnotations(mv, context, mpanns, true); + } + if (ANNOTATIONS && impanns != 0) { + readParameterAnnotations(mv, context, impanns, false); + } + + // visits the method attributes + while (attributes != null) { + Attribute attr = attributes.next; + attributes.next = null; + mv.visitAttribute(attributes); + attributes = attr; + } + + // visits the method code + if (code != 0) { + mv.visitCode(); + readCode(mv, context, code); + } + + // visits the end of the method + mv.visitEnd(); + + return u; + } + + /** + * Reads the bytecode of a method and makes the given visitor visit it. + * + * @param mv + * the visitor that must visit the method's code. + * @param context + * information about the class being parsed. + * @param u + * the start offset of the code attribute in the class file. + */ + private void readCode(final MethodVisitor mv, final Context context, int u) { + // reads the header + byte[] b = this.b; + char[] c = context.buffer; + int maxStack = readUnsignedShort(u); + int maxLocals = readUnsignedShort(u + 2); + int codeLength = readInt(u + 4); + u += 8; + + // reads the bytecode to find the labels + int codeStart = u; + int codeEnd = u + codeLength; + Label[] labels = context.labels = new Label[codeLength + 2]; + readLabel(codeLength + 1, labels); + while (u < codeEnd) { + int offset = u - codeStart; + int opcode = b[u] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + u += 1; + break; + case ClassWriter.LABEL_INSN: + readLabel(offset + readShort(u + 1), labels); + u += 3; + break; + case ClassWriter.LABELW_INSN: + readLabel(offset + readInt(u + 1), labels); + u += 5; + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + u += 6; + } else { + u += 4; + } + break; + case ClassWriter.TABL_INSN: + // skips 0 to 3 padding bytes + u = u + 4 - (offset & 3); + // reads instruction + readLabel(offset + readInt(u), labels); + for (int i = readInt(u + 8) - readInt(u + 4) + 1; i > 0; --i) { + readLabel(offset + readInt(u + 12), labels); + u += 4; + } + u += 12; + break; + case ClassWriter.LOOK_INSN: + // skips 0 to 3 padding bytes + u = u + 4 - (offset & 3); + // reads instruction + readLabel(offset + readInt(u), labels); + for (int i = readInt(u + 4); i > 0; --i) { + readLabel(offset + readInt(u + 12), labels); + u += 8; + } + u += 8; + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + u += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + u += 3; + break; + case ClassWriter.ITFMETH_INSN: + case ClassWriter.INDYMETH_INSN: + u += 5; + break; + // case MANA_INSN: + default: + u += 4; + break; + } + } + + // reads the try catch entries to find the labels, and also visits them + for (int i = readUnsignedShort(u); i > 0; --i) { + Label start = readLabel(readUnsignedShort(u + 2), labels); + Label end = readLabel(readUnsignedShort(u + 4), labels); + Label handler = readLabel(readUnsignedShort(u + 6), labels); + String type = readUTF8(items[readUnsignedShort(u + 8)], c); + mv.visitTryCatchBlock(start, end, handler, type); + u += 8; + } + u += 2; + + // reads the code attributes + int[] tanns = null; // start index of each visible type annotation + int[] itanns = null; // start index of each invisible type annotation + int tann = 0; // current index in tanns array + int itann = 0; // current index in itanns array + int ntoff = -1; // next visible type annotation code offset + int nitoff = -1; // next invisible type annotation code offset + int varTable = 0; + int varTypeTable = 0; + boolean zip = true; + boolean unzip = (context.flags & EXPAND_FRAMES) != 0; + int stackMap = 0; + int stackMapSize = 0; + int frameCount = 0; + Context frame = null; + Attribute attributes = null; + + for (int i = readUnsignedShort(u); i > 0; --i) { + String attrName = readUTF8(u + 2, c); + if ("LocalVariableTable".equals(attrName)) { + if ((context.flags & SKIP_DEBUG) == 0) { + varTable = u + 8; + for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) { + int label = readUnsignedShort(v + 10); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + label += readUnsignedShort(v + 12); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + v += 10; + } + } + } else if ("LocalVariableTypeTable".equals(attrName)) { + varTypeTable = u + 8; + } else if ("LineNumberTable".equals(attrName)) { + if ((context.flags & SKIP_DEBUG) == 0) { + for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) { + int label = readUnsignedShort(v + 10); + if (labels[label] == null) { + readLabel(label, labels).status |= Label.DEBUG; + } + Label l = labels[label]; + while (l.line > 0) { + if (l.next == null) { + l.next = new Label(); + } + l = l.next; + } + l.line = readUnsignedShort(v + 12); + v += 4; + } + } + } else if (ANNOTATIONS + && "RuntimeVisibleTypeAnnotations".equals(attrName)) { + tanns = readTypeAnnotations(mv, context, u + 8, true); + ntoff = tanns.length == 0 || readByte(tanns[0]) < 0x43 ? -1 + : readUnsignedShort(tanns[0] + 1); + } else if (ANNOTATIONS + && "RuntimeInvisibleTypeAnnotations".equals(attrName)) { + itanns = readTypeAnnotations(mv, context, u + 8, false); + nitoff = itanns.length == 0 || readByte(itanns[0]) < 0x43 ? -1 + : readUnsignedShort(itanns[0] + 1); + } else if (FRAMES && "StackMapTable".equals(attrName)) { + if ((context.flags & SKIP_FRAMES) == 0) { + stackMap = u + 10; + stackMapSize = readInt(u + 4); + frameCount = readUnsignedShort(u + 8); + } + /* + * here we do not extract the labels corresponding to the + * attribute content. This would require a full parsing of the + * attribute, which would need to be repeated in the second + * phase (see below). Instead the content of the attribute is + * read one frame at a time (i.e. after a frame has been + * visited, the next frame is read), and the labels it contains + * are also extracted one frame at a time. Thanks to the + * ordering of frames, having only a "one frame lookahead" is + * not a problem, i.e. it is not possible to see an offset + * smaller than the offset of the current insn and for which no + * Label exist. + */ + /* + * This is not true for UNINITIALIZED type offsets. We solve + * this by parsing the stack map table without a full decoding + * (see below). + */ + } else if (FRAMES && "StackMap".equals(attrName)) { + if ((context.flags & SKIP_FRAMES) == 0) { + zip = false; + stackMap = u + 10; + stackMapSize = readInt(u + 4); + frameCount = readUnsignedShort(u + 8); + } + /* + * IMPORTANT! here we assume that the frames are ordered, as in + * the StackMapTable attribute, although this is not guaranteed + * by the attribute format. + */ + } else { + for (int j = 0; j < context.attrs.length; ++j) { + if (context.attrs[j].type.equals(attrName)) { + Attribute attr = context.attrs[j].read(this, u + 8, + readInt(u + 4), c, codeStart - 8, labels); + if (attr != null) { + attr.next = attributes; + attributes = attr; + } + } + } + } + u += 6 + readInt(u + 4); + } + u += 2; + + // generates the first (implicit) stack map frame + if (FRAMES && stackMap != 0) { + /* + * for the first explicit frame the offset is not offset_delta + 1 + * but only offset_delta; setting the implicit frame offset to -1 + * allow the use of the "offset_delta + 1" rule in all cases + */ + frame = context; + frame.offset = -1; + frame.mode = 0; + frame.localCount = 0; + frame.localDiff = 0; + frame.stackCount = 0; + frame.local = new Object[maxLocals]; + frame.stack = new Object[maxStack]; + if (unzip) { + getImplicitFrame(context); + } + /* + * Finds labels for UNINITIALIZED frame types. Instead of decoding + * each element of the stack map table, we look for 3 consecutive + * bytes that "look like" an UNINITIALIZED type (tag 8, offset + * within code bounds, NEW instruction at this offset). We may find + * false positives (i.e. not real UNINITIALIZED types), but this + * should be rare, and the only consequence will be the creation of + * an unneeded label. This is better than creating a label for each + * NEW instruction, and faster than fully decoding the whole stack + * map table. + */ + for (int i = stackMap; i < stackMap + stackMapSize - 2; ++i) { + if (b[i] == 8) { // UNINITIALIZED FRAME TYPE + int v = readUnsignedShort(i + 1); + if (v >= 0 && v < codeLength) { + if ((b[codeStart + v] & 0xFF) == Opcodes.NEW) { + readLabel(v, labels); + } + } + } + } + } + + // visits the instructions + u = codeStart; + while (u < codeEnd) { + int offset = u - codeStart; + + // visits the label and line number for this offset, if any + Label l = labels[offset]; + if (l != null) { + Label next = l.next; + l.next = null; + mv.visitLabel(l); + if ((context.flags & SKIP_DEBUG) == 0 && l.line > 0) { + mv.visitLineNumber(l.line, l); + while (next != null) { + mv.visitLineNumber(next.line, l); + next = next.next; + } + } + } + + // visits the frame for this offset, if any + while (FRAMES && frame != null + && (frame.offset == offset || frame.offset == -1)) { + // if there is a frame for this offset, makes the visitor visit + // it, and reads the next frame if there is one. + if (frame.offset != -1) { + if (!zip || unzip) { + mv.visitFrame(Opcodes.F_NEW, frame.localCount, + frame.local, frame.stackCount, frame.stack); + } else { + mv.visitFrame(frame.mode, frame.localDiff, frame.local, + frame.stackCount, frame.stack); + } + } + if (frameCount > 0) { + stackMap = readFrame(stackMap, zip, unzip, frame); + --frameCount; + } else { + frame = null; + } + } + + // visits the instruction at this offset + int opcode = b[u] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + mv.visitInsn(opcode); + u += 1; + break; + case ClassWriter.IMPLVAR_INSN: + if (opcode > Opcodes.ISTORE) { + opcode -= 59; // ISTORE_0 + mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), + opcode & 0x3); + } else { + opcode -= 26; // ILOAD_0 + mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3); + } + u += 1; + break; + case ClassWriter.LABEL_INSN: + mv.visitJumpInsn(opcode, labels[offset + readShort(u + 1)]); + u += 3; + break; + case ClassWriter.LABELW_INSN: + mv.visitJumpInsn(opcode - 33, labels[offset + readInt(u + 1)]); + u += 5; + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + mv.visitIincInsn(readUnsignedShort(u + 2), readShort(u + 4)); + u += 6; + } else { + mv.visitVarInsn(opcode, readUnsignedShort(u + 2)); + u += 4; + } + break; + case ClassWriter.TABL_INSN: { + // skips 0 to 3 padding bytes + u = u + 4 - (offset & 3); + // reads instruction + int label = offset + readInt(u); + int min = readInt(u + 4); + int max = readInt(u + 8); + Label[] table = new Label[max - min + 1]; + u += 12; + for (int i = 0; i < table.length; ++i) { + table[i] = labels[offset + readInt(u)]; + u += 4; + } + mv.visitTableSwitchInsn(min, max, labels[label], table); + break; + } + case ClassWriter.LOOK_INSN: { + // skips 0 to 3 padding bytes + u = u + 4 - (offset & 3); + // reads instruction + int label = offset + readInt(u); + int len = readInt(u + 4); + int[] keys = new int[len]; + Label[] values = new Label[len]; + u += 8; + for (int i = 0; i < len; ++i) { + keys[i] = readInt(u); + values[i] = labels[offset + readInt(u + 4)]; + u += 8; + } + mv.visitLookupSwitchInsn(labels[label], keys, values); + break; + } + case ClassWriter.VAR_INSN: + mv.visitVarInsn(opcode, b[u + 1] & 0xFF); + u += 2; + break; + case ClassWriter.SBYTE_INSN: + mv.visitIntInsn(opcode, b[u + 1]); + u += 2; + break; + case ClassWriter.SHORT_INSN: + mv.visitIntInsn(opcode, readShort(u + 1)); + u += 3; + break; + case ClassWriter.LDC_INSN: + mv.visitLdcInsn(readConst(b[u + 1] & 0xFF, c)); + u += 2; + break; + case ClassWriter.LDCW_INSN: + mv.visitLdcInsn(readConst(readUnsignedShort(u + 1), c)); + u += 3; + break; + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.ITFMETH_INSN: { + int cpIndex = items[readUnsignedShort(u + 1)]; + boolean itf = b[cpIndex - 1] == ClassWriter.IMETH; + String iowner = readClass(cpIndex, c); + cpIndex = items[readUnsignedShort(cpIndex + 2)]; + String iname = readUTF8(cpIndex, c); + String idesc = readUTF8(cpIndex + 2, c); + if (opcode < Opcodes.INVOKEVIRTUAL) { + mv.visitFieldInsn(opcode, iowner, iname, idesc); + } else { + mv.visitMethodInsn(opcode, iowner, iname, idesc, itf); + } + if (opcode == Opcodes.INVOKEINTERFACE) { + u += 5; + } else { + u += 3; + } + break; + } + case ClassWriter.INDYMETH_INSN: { + int cpIndex = items[readUnsignedShort(u + 1)]; + int bsmIndex = context.bootstrapMethods[readUnsignedShort(cpIndex)]; + Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c); + int bsmArgCount = readUnsignedShort(bsmIndex + 2); + Object[] bsmArgs = new Object[bsmArgCount]; + bsmIndex += 4; + for (int i = 0; i < bsmArgCount; i++) { + bsmArgs[i] = readConst(readUnsignedShort(bsmIndex), c); + bsmIndex += 2; + } + cpIndex = items[readUnsignedShort(cpIndex + 2)]; + String iname = readUTF8(cpIndex, c); + String idesc = readUTF8(cpIndex + 2, c); + mv.visitInvokeDynamicInsn(iname, idesc, bsm, bsmArgs); + u += 5; + break; + } + case ClassWriter.TYPE_INSN: + mv.visitTypeInsn(opcode, readClass(u + 1, c)); + u += 3; + break; + case ClassWriter.IINC_INSN: + mv.visitIincInsn(b[u + 1] & 0xFF, b[u + 2]); + u += 3; + break; + // case MANA_INSN: + default: + mv.visitMultiANewArrayInsn(readClass(u + 1, c), b[u + 3] & 0xFF); + u += 4; + break; + } + + // visit the instruction annotations, if any + while (tanns != null && tann < tanns.length && ntoff <= offset) { + if (ntoff == offset) { + int v = readAnnotationTarget(context, tanns[tann]); + readAnnotationValues(v + 2, c, true, + mv.visitInsnAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), true)); + } + ntoff = ++tann >= tanns.length || readByte(tanns[tann]) < 0x43 ? -1 + : readUnsignedShort(tanns[tann] + 1); + } + while (itanns != null && itann < itanns.length && nitoff <= offset) { + if (nitoff == offset) { + int v = readAnnotationTarget(context, itanns[itann]); + readAnnotationValues(v + 2, c, true, + mv.visitInsnAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), false)); + } + nitoff = ++itann >= itanns.length + || readByte(itanns[itann]) < 0x43 ? -1 + : readUnsignedShort(itanns[itann] + 1); + } + } + if (labels[codeLength] != null) { + mv.visitLabel(labels[codeLength]); + } + + // visits the local variable tables + if ((context.flags & SKIP_DEBUG) == 0 && varTable != 0) { + int[] typeTable = null; + if (varTypeTable != 0) { + u = varTypeTable + 2; + typeTable = new int[readUnsignedShort(varTypeTable) * 3]; + for (int i = typeTable.length; i > 0;) { + typeTable[--i] = u + 6; // signature + typeTable[--i] = readUnsignedShort(u + 8); // index + typeTable[--i] = readUnsignedShort(u); // start + u += 10; + } + } + u = varTable + 2; + for (int i = readUnsignedShort(varTable); i > 0; --i) { + int start = readUnsignedShort(u); + int length = readUnsignedShort(u + 2); + int index = readUnsignedShort(u + 8); + String vsignature = null; + if (typeTable != null) { + for (int j = 0; j < typeTable.length; j += 3) { + if (typeTable[j] == start && typeTable[j + 1] == index) { + vsignature = readUTF8(typeTable[j + 2], c); + break; + } + } + } + mv.visitLocalVariable(readUTF8(u + 4, c), readUTF8(u + 6, c), + vsignature, labels[start], labels[start + length], + index); + u += 10; + } + } + + // visits the local variables type annotations + if (tanns != null) { + for (int i = 0; i < tanns.length; ++i) { + if ((readByte(tanns[i]) >> 1) == (0x40 >> 1)) { + int v = readAnnotationTarget(context, tanns[i]); + v = readAnnotationValues(v + 2, c, true, + mv.visitLocalVariableAnnotation(context.typeRef, + context.typePath, context.start, + context.end, context.index, readUTF8(v, c), + true)); + } + } + } + if (itanns != null) { + for (int i = 0; i < itanns.length; ++i) { + if ((readByte(itanns[i]) >> 1) == (0x40 >> 1)) { + int v = readAnnotationTarget(context, itanns[i]); + v = readAnnotationValues(v + 2, c, true, + mv.visitLocalVariableAnnotation(context.typeRef, + context.typePath, context.start, + context.end, context.index, readUTF8(v, c), + false)); + } + } + } + + // visits the code attributes + while (attributes != null) { + Attribute attr = attributes.next; + attributes.next = null; + mv.visitAttribute(attributes); + attributes = attr; + } + + // visits the max stack and max locals values + mv.visitMaxs(maxStack, maxLocals); + } + + /** + * Parses a type annotation table to find the labels, and to visit the try + * catch block annotations. + * + * @param u + * the start offset of a type annotation table. + * @param mv + * the method visitor to be used to visit the try catch block + * annotations. + * @param context + * information about the class being parsed. + * @param visible + * if the type annotation table to parse contains runtime visible + * annotations. + * @return the start offset of each type annotation in the parsed table. + */ + private int[] readTypeAnnotations(final MethodVisitor mv, + final Context context, int u, boolean visible) { + char[] c = context.buffer; + int[] offsets = new int[readUnsignedShort(u)]; + u += 2; + for (int i = 0; i < offsets.length; ++i) { + offsets[i] = u; + int target = readInt(u); + switch (target >>> 24) { + case 0x00: // CLASS_TYPE_PARAMETER + case 0x01: // METHOD_TYPE_PARAMETER + case 0x16: // METHOD_FORMAL_PARAMETER + u += 2; + break; + case 0x13: // FIELD + case 0x14: // METHOD_RETURN + case 0x15: // METHOD_RECEIVER + u += 1; + break; + case 0x40: // LOCAL_VARIABLE + case 0x41: // RESOURCE_VARIABLE + for (int j = readUnsignedShort(u + 1); j > 0; --j) { + int start = readUnsignedShort(u + 3); + int length = readUnsignedShort(u + 5); + readLabel(start, context.labels); + readLabel(start + length, context.labels); + u += 6; + } + u += 3; + break; + case 0x47: // CAST + case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT + case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT + u += 4; + break; + // case 0x10: // CLASS_EXTENDS + // case 0x11: // CLASS_TYPE_PARAMETER_BOUND + // case 0x12: // METHOD_TYPE_PARAMETER_BOUND + // case 0x17: // THROWS + // case 0x42: // EXCEPTION_PARAMETER + // case 0x43: // INSTANCEOF + // case 0x44: // NEW + // case 0x45: // CONSTRUCTOR_REFERENCE + // case 0x46: // METHOD_REFERENCE + default: + u += 3; + break; + } + int pathLength = readByte(u); + if ((target >>> 24) == 0x42) { + TypePath path = pathLength == 0 ? null : new TypePath(b, u); + u += 1 + 2 * pathLength; + u = readAnnotationValues(u + 2, c, true, + mv.visitTryCatchAnnotation(target, path, + readUTF8(u, c), visible)); + } else { + u = readAnnotationValues(u + 3 + 2 * pathLength, c, true, null); + } + } + return offsets; + } + + /** + * Parses the header of a type annotation to extract its target_type and + * target_path (the result is stored in the given context), and returns the + * start offset of the rest of the type_annotation structure (i.e. the + * offset to the type_index field, which is followed by + * num_element_value_pairs and then the name,value pairs). + * + * @param context + * information about the class being parsed. This is where the + * extracted target_type and target_path must be stored. + * @param u + * the start offset of a type_annotation structure. + * @return the start offset of the rest of the type_annotation structure. + */ + private int readAnnotationTarget(final Context context, int u) { + int target = readInt(u); + switch (target >>> 24) { + case 0x00: // CLASS_TYPE_PARAMETER + case 0x01: // METHOD_TYPE_PARAMETER + case 0x16: // METHOD_FORMAL_PARAMETER + target &= 0xFFFF0000; + u += 2; + break; + case 0x13: // FIELD + case 0x14: // METHOD_RETURN + case 0x15: // METHOD_RECEIVER + target &= 0xFF000000; + u += 1; + break; + case 0x40: // LOCAL_VARIABLE + case 0x41: { // RESOURCE_VARIABLE + target &= 0xFF000000; + int n = readUnsignedShort(u + 1); + context.start = new Label[n]; + context.end = new Label[n]; + context.index = new int[n]; + u += 3; + for (int i = 0; i < n; ++i) { + int start = readUnsignedShort(u); + int length = readUnsignedShort(u + 2); + context.start[i] = readLabel(start, context.labels); + context.end[i] = readLabel(start + length, context.labels); + context.index[i] = readUnsignedShort(u + 4); + u += 6; + } + break; + } + case 0x47: // CAST + case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT + case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT + target &= 0xFF0000FF; + u += 4; + break; + // case 0x10: // CLASS_EXTENDS + // case 0x11: // CLASS_TYPE_PARAMETER_BOUND + // case 0x12: // METHOD_TYPE_PARAMETER_BOUND + // case 0x17: // THROWS + // case 0x42: // EXCEPTION_PARAMETER + // case 0x43: // INSTANCEOF + // case 0x44: // NEW + // case 0x45: // CONSTRUCTOR_REFERENCE + // case 0x46: // METHOD_REFERENCE + default: + target &= (target >>> 24) < 0x43 ? 0xFFFFFF00 : 0xFF000000; + u += 3; + break; + } + int pathLength = readByte(u); + context.typeRef = target; + context.typePath = pathLength == 0 ? null : new TypePath(b, u); + return u + 1 + 2 * pathLength; + } + + /** + * Reads parameter annotations and makes the given visitor visit them. + * + * @param mv + * the visitor that must visit the annotations. + * @param context + * information about the class being parsed. + * @param v + * start offset in {@link #b b} of the annotations to be read. + * @param visible + * true if the annotations to be read are visible at + * runtime. + */ + private void readParameterAnnotations(final MethodVisitor mv, + final Context context, int v, final boolean visible) { + int i; + int n = b[v++] & 0xFF; + // workaround for a bug in javac (javac compiler generates a parameter + // annotation array whose size is equal to the number of parameters in + // the Java source file, while it should generate an array whose size is + // equal to the number of parameters in the method descriptor - which + // includes the synthetic parameters added by the compiler). This work- + // around supposes that the synthetic parameters are the first ones. + int synthetics = Type.getArgumentTypes(context.desc).length - n; + AnnotationVisitor av; + for (i = 0; i < synthetics; ++i) { + // virtual annotation to detect synthetic parameters in MethodWriter + av = mv.visitParameterAnnotation(i, "Ljava/lang/Synthetic;", false); + if (av != null) { + av.visitEnd(); + } + } + char[] c = context.buffer; + for (; i < n + synthetics; ++i) { + int j = readUnsignedShort(v); + v += 2; + for (; j > 0; --j) { + av = mv.visitParameterAnnotation(i, readUTF8(v, c), visible); + v = readAnnotationValues(v + 2, c, true, av); + } + } + } + + /** + * Reads the values of an annotation and makes the given visitor visit them. + * + * @param v + * the start offset in {@link #b b} of the values to be read + * (including the unsigned short that gives the number of + * values). + * @param buf + * buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or {@link #readConst + * readConst}. + * @param named + * if the annotation values are named or not. + * @param av + * the visitor that must visit the values. + * @return the end offset of the annotation values. + */ + private int readAnnotationValues(int v, final char[] buf, + final boolean named, final AnnotationVisitor av) { + int i = readUnsignedShort(v); + v += 2; + if (named) { + for (; i > 0; --i) { + v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av); + } + } else { + for (; i > 0; --i) { + v = readAnnotationValue(v, buf, null, av); + } + } + if (av != null) { + av.visitEnd(); + } + return v; + } + + /** + * Reads a value of an annotation and makes the given visitor visit it. + * + * @param v + * the start offset in {@link #b b} of the value to be read + * (not including the value name constant pool index). + * @param buf + * buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or {@link #readConst + * readConst}. + * @param name + * the name of the value to be read. + * @param av + * the visitor that must visit the value. + * @return the end offset of the annotation value. + */ + private int readAnnotationValue(int v, final char[] buf, final String name, + final AnnotationVisitor av) { + int i; + if (av == null) { + switch (b[v] & 0xFF) { + case 'e': // enum_const_value + return v + 5; + case '@': // annotation_value + return readAnnotationValues(v + 3, buf, true, null); + case '[': // array_value + return readAnnotationValues(v + 1, buf, false, null); + default: + return v + 3; + } + } + switch (b[v++] & 0xFF) { + case 'I': // pointer to CONSTANT_Integer + case 'J': // pointer to CONSTANT_Long + case 'F': // pointer to CONSTANT_Float + case 'D': // pointer to CONSTANT_Double + av.visit(name, readConst(readUnsignedShort(v), buf)); + v += 2; + break; + case 'B': // pointer to CONSTANT_Byte + av.visit(name, (byte) readInt(items[readUnsignedShort(v)])); + v += 2; + break; + case 'Z': // pointer to CONSTANT_Boolean + av.visit(name, + readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE + : Boolean.TRUE); + v += 2; + break; + case 'S': // pointer to CONSTANT_Short + av.visit(name, (short) readInt(items[readUnsignedShort(v)])); + v += 2; + break; + case 'C': // pointer to CONSTANT_Char + av.visit(name, (char) readInt(items[readUnsignedShort(v)])); + v += 2; + break; + case 's': // pointer to CONSTANT_Utf8 + av.visit(name, readUTF8(v, buf)); + v += 2; + break; + case 'e': // enum_const_value + av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf)); + v += 4; + break; + case 'c': // class_info + av.visit(name, Type.getType(readUTF8(v, buf))); + v += 2; + break; + case '@': // annotation_value + v = readAnnotationValues(v + 2, buf, true, + av.visitAnnotation(name, readUTF8(v, buf))); + break; + case '[': // array_value + int size = readUnsignedShort(v); + v += 2; + if (size == 0) { + return readAnnotationValues(v - 2, buf, false, + av.visitArray(name)); + } + switch (this.b[v++] & 0xFF) { + case 'B': + byte[] bv = new byte[size]; + for (i = 0; i < size; i++) { + bv[i] = (byte) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, bv); + --v; + break; + case 'Z': + boolean[] zv = new boolean[size]; + for (i = 0; i < size; i++) { + zv[i] = readInt(items[readUnsignedShort(v)]) != 0; + v += 3; + } + av.visit(name, zv); + --v; + break; + case 'S': + short[] sv = new short[size]; + for (i = 0; i < size; i++) { + sv[i] = (short) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, sv); + --v; + break; + case 'C': + char[] cv = new char[size]; + for (i = 0; i < size; i++) { + cv[i] = (char) readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, cv); + --v; + break; + case 'I': + int[] iv = new int[size]; + for (i = 0; i < size; i++) { + iv[i] = readInt(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, iv); + --v; + break; + case 'J': + long[] lv = new long[size]; + for (i = 0; i < size; i++) { + lv[i] = readLong(items[readUnsignedShort(v)]); + v += 3; + } + av.visit(name, lv); + --v; + break; + case 'F': + float[] fv = new float[size]; + for (i = 0; i < size; i++) { + fv[i] = Float + .intBitsToFloat(readInt(items[readUnsignedShort(v)])); + v += 3; + } + av.visit(name, fv); + --v; + break; + case 'D': + double[] dv = new double[size]; + for (i = 0; i < size; i++) { + dv[i] = Double + .longBitsToDouble(readLong(items[readUnsignedShort(v)])); + v += 3; + } + av.visit(name, dv); + --v; + break; + default: + v = readAnnotationValues(v - 3, buf, false, av.visitArray(name)); + } + } + return v; + } + + /** + * Computes the implicit frame of the method currently being parsed (as + * defined in the given {@link Context}) and stores it in the given context. + * + * @param frame + * information about the class being parsed. + */ + private void getImplicitFrame(final Context frame) { + String desc = frame.desc; + Object[] locals = frame.local; + int local = 0; + if ((frame.access & Opcodes.ACC_STATIC) == 0) { + if ("".equals(frame.name)) { + locals[local++] = Opcodes.UNINITIALIZED_THIS; + } else { + locals[local++] = readClass(header + 2, frame.buffer); + } + } + int i = 1; + loop: while (true) { + int j = i; + switch (desc.charAt(i++)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + locals[local++] = Opcodes.INTEGER; + break; + case 'F': + locals[local++] = Opcodes.FLOAT; + break; + case 'J': + locals[local++] = Opcodes.LONG; + break; + case 'D': + locals[local++] = Opcodes.DOUBLE; + break; + case '[': + while (desc.charAt(i) == '[') { + ++i; + } + if (desc.charAt(i) == 'L') { + ++i; + while (desc.charAt(i) != ';') { + ++i; + } + } + locals[local++] = desc.substring(j, ++i); + break; + case 'L': + while (desc.charAt(i) != ';') { + ++i; + } + locals[local++] = desc.substring(j + 1, i++); + break; + default: + break loop; + } + } + frame.localCount = local; + } + + /** + * Reads a stack map frame and stores the result in the given + * {@link Context} object. + * + * @param stackMap + * the start offset of a stack map frame in the class file. + * @param zip + * if the stack map frame at stackMap is compressed or not. + * @param unzip + * if the stack map frame must be uncompressed. + * @param frame + * where the parsed stack map frame must be stored. + * @return the offset of the first byte following the parsed frame. + */ + private int readFrame(int stackMap, boolean zip, boolean unzip, + Context frame) { + char[] c = frame.buffer; + Label[] labels = frame.labels; + int tag; + int delta; + if (zip) { + tag = b[stackMap++] & 0xFF; + } else { + tag = MethodWriter.FULL_FRAME; + frame.offset = -1; + } + frame.localDiff = 0; + if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) { + delta = tag; + frame.mode = Opcodes.F_SAME; + frame.stackCount = 0; + } else if (tag < MethodWriter.RESERVED) { + delta = tag - MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME; + stackMap = readFrameType(frame.stack, 0, stackMap, c, labels); + frame.mode = Opcodes.F_SAME1; + frame.stackCount = 1; + } else { + delta = readUnsignedShort(stackMap); + stackMap += 2; + if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { + stackMap = readFrameType(frame.stack, 0, stackMap, c, labels); + frame.mode = Opcodes.F_SAME1; + frame.stackCount = 1; + } else if (tag >= MethodWriter.CHOP_FRAME + && tag < MethodWriter.SAME_FRAME_EXTENDED) { + frame.mode = Opcodes.F_CHOP; + frame.localDiff = MethodWriter.SAME_FRAME_EXTENDED - tag; + frame.localCount -= frame.localDiff; + frame.stackCount = 0; + } else if (tag == MethodWriter.SAME_FRAME_EXTENDED) { + frame.mode = Opcodes.F_SAME; + frame.stackCount = 0; + } else if (tag < MethodWriter.FULL_FRAME) { + int local = unzip ? frame.localCount : 0; + for (int i = tag - MethodWriter.SAME_FRAME_EXTENDED; i > 0; i--) { + stackMap = readFrameType(frame.local, local++, stackMap, c, + labels); + } + frame.mode = Opcodes.F_APPEND; + frame.localDiff = tag - MethodWriter.SAME_FRAME_EXTENDED; + frame.localCount += frame.localDiff; + frame.stackCount = 0; + } else { // if (tag == FULL_FRAME) { + frame.mode = Opcodes.F_FULL; + int n = readUnsignedShort(stackMap); + stackMap += 2; + frame.localDiff = n; + frame.localCount = n; + for (int local = 0; n > 0; n--) { + stackMap = readFrameType(frame.local, local++, stackMap, c, + labels); + } + n = readUnsignedShort(stackMap); + stackMap += 2; + frame.stackCount = n; + for (int stack = 0; n > 0; n--) { + stackMap = readFrameType(frame.stack, stack++, stackMap, c, + labels); + } + } + } + frame.offset += delta + 1; + readLabel(frame.offset, labels); + return stackMap; + } + + /** + * Reads a stack map frame type and stores it at the given index in the + * given array. + * + * @param frame + * the array where the parsed type must be stored. + * @param index + * the index in 'frame' where the parsed type must be stored. + * @param v + * the start offset of the stack map frame type to read. + * @param buf + * a buffer to read strings. + * @param labels + * the labels of the method currently being parsed, indexed by + * their offset. If the parsed type is an Uninitialized type, a + * new label for the corresponding NEW instruction is stored in + * this array if it does not already exist. + * @return the offset of the first byte after the parsed type. + */ + private int readFrameType(final Object[] frame, final int index, int v, + final char[] buf, final Label[] labels) { + int type = b[v++] & 0xFF; + switch (type) { + case 0: + frame[index] = Opcodes.TOP; + break; + case 1: + frame[index] = Opcodes.INTEGER; + break; + case 2: + frame[index] = Opcodes.FLOAT; + break; + case 3: + frame[index] = Opcodes.DOUBLE; + break; + case 4: + frame[index] = Opcodes.LONG; + break; + case 5: + frame[index] = Opcodes.NULL; + break; + case 6: + frame[index] = Opcodes.UNINITIALIZED_THIS; + break; + case 7: // Object + frame[index] = readClass(v, buf); + v += 2; + break; + default: // Uninitialized + frame[index] = readLabel(readUnsignedShort(v), labels); + v += 2; + } + return v; + } + + /** + * Returns the label corresponding to the given offset. The default + * implementation of this method creates a label for the given offset if it + * has not been already created. + * + * @param offset + * a bytecode offset in a method. + * @param labels + * the already created labels, indexed by their offset. If a + * label already exists for offset this method must not create a + * new one. Otherwise it must store the new label in this array. + * @return a non null Label, which must be equal to labels[offset]. + */ + protected Label readLabel(int offset, Label[] labels) { + if (labels[offset] == null) { + labels[offset] = new Label(); + } + return labels[offset]; + } + + /** + * Returns the start index of the attribute_info structure of this class. + * + * @return the start index of the attribute_info structure of this class. + */ + private int getAttributes() { + // skips the header + int u = header + 8 + readUnsignedShort(header + 6) * 2; + // skips fields and methods + for (int i = readUnsignedShort(u); i > 0; --i) { + for (int j = readUnsignedShort(u + 8); j > 0; --j) { + u += 6 + readInt(u + 12); + } + u += 8; + } + u += 2; + for (int i = readUnsignedShort(u); i > 0; --i) { + for (int j = readUnsignedShort(u + 8); j > 0; --j) { + u += 6 + readInt(u + 12); + } + u += 8; + } + // the attribute_info structure starts just after the methods + return u + 2; + } + + /** + * Reads an attribute in {@link #b b}. + * + * @param attrs + * prototypes of the attributes that must be parsed during the + * visit of the class. Any attribute whose type is not equal to + * the type of one the prototypes is ignored (i.e. an empty + * {@link Attribute} instance is returned). + * @param type + * the type of the attribute. + * @param off + * index of the first byte of the attribute's content in + * {@link #b b}. The 6 attribute header bytes, containing the + * type and the length of the attribute, are not taken into + * account here (they have already been read). + * @param len + * the length of the attribute's content. + * @param buf + * buffer to be used to call {@link #readUTF8 readUTF8}, + * {@link #readClass(int,char[]) readClass} or {@link #readConst + * readConst}. + * @param codeOff + * index of the first byte of code's attribute content in + * {@link #b b}, or -1 if the attribute to be read is not a code + * attribute. The 6 attribute header bytes, containing the type + * and the length of the attribute, are not taken into account + * here. + * @param labels + * the labels of the method's code, or null if the + * attribute to be read is not a code attribute. + * @return the attribute that has been read, or null to skip this + * attribute. + */ + private Attribute readAttribute(final Attribute[] attrs, final String type, + final int off, final int len, final char[] buf, final int codeOff, + final Label[] labels) { + for (int i = 0; i < attrs.length; ++i) { + if (attrs[i].type.equals(type)) { + return attrs[i].read(this, off, len, buf, codeOff, labels); + } + } + return new Attribute(type).read(this, off, len, null, -1, null); + } + + // ------------------------------------------------------------------------ + // Utility methods: low level parsing + // ------------------------------------------------------------------------ + + /** + * Returns the number of constant pool items in {@link #b b}. + * + * @return the number of constant pool items in {@link #b b}. + */ + public int getItemCount() { + return items.length; + } + + /** + * Returns the start index of the constant pool item in {@link #b b}, plus + * one. This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param item + * the index a constant pool item. + * @return the start index of the constant pool item in {@link #b b}, plus + * one. + */ + public int getItem(final int item) { + return items[item]; + } + + /** + * Returns the maximum length of the strings contained in the constant pool + * of the class. + * + * @return the maximum length of the strings contained in the constant pool + * of the class. + */ + public int getMaxStringLength() { + return maxStringLength; + } + + /** + * Reads a byte value in {@link #b b}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readByte(final int index) { + return b[index] & 0xFF; + } + + /** + * Reads an unsigned short value in {@link #b b}. This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readUnsignedShort(final int index) { + byte[] b = this.b; + return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); + } + + /** + * Reads a signed short value in {@link #b b}. This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public short readShort(final int index) { + byte[] b = this.b; + return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF)); + } + + /** + * Reads a signed int value in {@link #b b}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public int readInt(final int index) { + byte[] b = this.b; + return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) + | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); + } + + /** + * Reads a signed long value in {@link #b b}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index + * the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + public long readLong(final int index) { + long l1 = readInt(index); + long l0 = readInt(index + 4) & 0xFFFFFFFFL; + return (l1 << 32) | l0; + } + + /** + * Reads an UTF8 string constant pool item in {@link #b b}. This method + * is intended for {@link Attribute} sub classes, and is normally not needed + * by class generators or adapters. + * + * @param index + * the start index of an unsigned short value in {@link #b b}, + * whose value is the index of an UTF8 constant pool item. + * @param buf + * buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 item. + */ + public String readUTF8(int index, final char[] buf) { + int item = readUnsignedShort(index); + if (index == 0 || item == 0) { + return null; + } + String s = strings[item]; + if (s != null) { + return s; + } + index = items[item]; + return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf); + } + + /** + * Reads UTF8 string in {@link #b b}. + * + * @param index + * start offset of the UTF8 string to be read. + * @param utfLen + * length of the UTF8 string to be read. + * @param buf + * buffer to be used to read the string. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 string. + */ + private String readUTF(int index, final int utfLen, final char[] buf) { + int endIndex = index + utfLen; + byte[] b = this.b; + int strLen = 0; + int c; + int st = 0; + char cc = 0; + while (index < endIndex) { + c = b[index++]; + switch (st) { + case 0: + c = c & 0xFF; + if (c < 0x80) { // 0xxxxxxx + buf[strLen++] = (char) c; + } else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx + cc = (char) (c & 0x1F); + st = 1; + } else { // 1110 xxxx 10xx xxxx 10xx xxxx + cc = (char) (c & 0x0F); + st = 2; + } + break; + + case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char + buf[strLen++] = (char) ((cc << 6) | (c & 0x3F)); + st = 0; + break; + + case 2: // byte 2 of 3-byte char + cc = (char) ((cc << 6) | (c & 0x3F)); + st = 1; + break; + } + } + return new String(buf, 0, strLen); + } + + /** + * Reads a class constant pool item in {@link #b b}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters. + * + * @param index + * the start index of an unsigned short value in {@link #b b}, + * whose value is the index of a class constant pool item. + * @param buf + * buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified class item. + */ + public String readClass(final int index, final char[] buf) { + // computes the start index of the CONSTANT_Class item in b + // and reads the CONSTANT_Utf8 item designated by + // the first two bytes of this CONSTANT_Class item + return readUTF8(items[readUnsignedShort(index)], buf); + } + + /** + * Reads a numeric or string constant pool item in {@link #b b}. This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters. + * + * @param item + * the index of a constant pool item. + * @param buf + * buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double}, + * {@link String}, {@link Type} or {@link Handle} corresponding to + * the given constant pool item. + */ + public Object readConst(final int item, final char[] buf) { + int index = items[item]; + switch (b[index - 1]) { + case ClassWriter.INT: + return readInt(index); + case ClassWriter.FLOAT: + return Float.intBitsToFloat(readInt(index)); + case ClassWriter.LONG: + return readLong(index); + case ClassWriter.DOUBLE: + return Double.longBitsToDouble(readLong(index)); + case ClassWriter.CLASS: + return Type.getObjectType(readUTF8(index, buf)); + case ClassWriter.STR: + return readUTF8(index, buf); + case ClassWriter.MTYPE: + return Type.getMethodType(readUTF8(index, buf)); + default: // case ClassWriter.HANDLE_BASE + [1..9]: + int tag = readByte(index); + int[] items = this.items; + int cpIndex = items[readUnsignedShort(index + 1)]; + boolean itf = b[cpIndex - 1] == ClassWriter.IMETH; + String owner = readClass(cpIndex, buf); + cpIndex = items[readUnsignedShort(cpIndex + 2)]; + String name = readUTF8(cpIndex, buf); + String desc = readUTF8(cpIndex + 2, buf); + return new Handle(tag, owner, name, desc, itf); + } + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/ClassVisitor.java b/blade-core/src/main/java/org/objectweb/asm/ClassVisitor.java new file mode 100644 index 000000000..107ada001 --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/ClassVisitor.java @@ -0,0 +1,320 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * A visitor to visit a Java class. The methods of this class must be called in + * the following order: visit [ visitSource ] [ + * visitOuterClass ] ( visitAnnotation | + * visitTypeAnnotation | visitAttribute )* ( + * visitInnerClass | visitField | visitMethod )* + * visitEnd. + * + * @author Eric Bruneton + */ +public abstract class ClassVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + protected final int api; + + /** + * The class visitor to which this visitor must delegate method calls. May + * be null. + */ + protected ClassVisitor cv; + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + public ClassVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + * @param cv + * the class visitor to which this visitor must delegate method + * calls. May be null. + */ + public ClassVisitor(final int api, final ClassVisitor cv) { + if (api != Opcodes.ASM4 && api != Opcodes.ASM5) { + throw new IllegalArgumentException(); + } + this.api = api; + this.cv = cv; + } + + /** + * Visits the header of the class. + * + * @param version + * the class version. + * @param access + * the class's access flags (see {@link Opcodes}). This parameter + * also indicates if the class is deprecated. + * @param name + * the internal name of the class (see + * {@link Type#getInternalName() getInternalName}). + * @param signature + * the signature of this class. May be null if the class + * is not a generic one, and does not extend or implement generic + * classes or interfaces. + * @param superName + * the internal of name of the super class (see + * {@link Type#getInternalName() getInternalName}). For + * interfaces, the super class is {@link Object}. May be + * null, but only for the {@link Object} class. + * @param interfaces + * the internal names of the class's interfaces (see + * {@link Type#getInternalName() getInternalName}). May be + * null. + */ + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + if (cv != null) { + cv.visit(version, access, name, signature, superName, interfaces); + } + } + + /** + * Visits the source of the class. + * + * @param source + * the name of the source file from which the class was compiled. + * May be null. + * @param debug + * additional debug information to compute the correspondance + * between source and compiled elements of the class. May be + * null. + */ + public void visitSource(String source, String debug) { + if (cv != null) { + cv.visitSource(source, debug); + } + } + + /** + * Visits the enclosing class of the class. This method must be called only + * if the class has an enclosing class. + * + * @param owner + * internal name of the enclosing class of the class. + * @param name + * the name of the method that contains the class, or + * null if the class is not enclosed in a method of its + * enclosing class. + * @param desc + * the descriptor of the method that contains the class, or + * null if the class is not enclosed in a method of its + * enclosing class. + */ + public void visitOuterClass(String owner, String name, String desc) { + if (cv != null) { + cv.visitOuterClass(owner, name, desc); + } + } + + /** + * Visits an annotation of the class. + * + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (cv != null) { + return cv.visitAnnotation(desc, visible); + } + return null; + } + + /** + * Visits an annotation on a type in the class signature. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#CLASS_TYPE_PARAMETER + * CLASS_TYPE_PARAMETER}, + * {@link TypeReference#CLASS_TYPE_PARAMETER_BOUND + * CLASS_TYPE_PARAMETER_BOUND} or + * {@link TypeReference#CLASS_EXTENDS CLASS_EXTENDS}. See + * {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (cv != null) { + return cv.visitTypeAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the class. + * + * @param attr + * an attribute. + */ + public void visitAttribute(Attribute attr) { + if (cv != null) { + cv.visitAttribute(attr); + } + } + + /** + * Visits information about an inner class. This inner class is not + * necessarily a member of the class being visited. + * + * @param name + * the internal name of an inner class (see + * {@link Type#getInternalName() getInternalName}). + * @param outerName + * the internal name of the class to which the inner class + * belongs (see {@link Type#getInternalName() getInternalName}). + * May be null for not member classes. + * @param innerName + * the (simple) name of the inner class inside its enclosing + * class. May be null for anonymous inner classes. + * @param access + * the access flags of the inner class as originally declared in + * the enclosing class. + */ + public void visitInnerClass(String name, String outerName, + String innerName, int access) { + if (cv != null) { + cv.visitInnerClass(name, outerName, innerName, access); + } + } + + /** + * Visits a field of the class. + * + * @param access + * the field's access flags (see {@link Opcodes}). This parameter + * also indicates if the field is synthetic and/or deprecated. + * @param name + * the field's name. + * @param desc + * the field's descriptor (see {@link Type Type}). + * @param signature + * the field's signature. May be null if the field's + * type does not use generic types. + * @param value + * the field's initial value. This parameter, which may be + * null if the field does not have an initial value, + * must be an {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double} or a {@link String} (for int, + * float, long or String fields + * respectively). This parameter is only used for static + * fields. Its value is ignored for non static fields, which + * must be initialized through bytecode instructions in + * constructors or methods. + * @return a visitor to visit field annotations and attributes, or + * null if this class visitor is not interested in visiting + * these annotations and attributes. + */ + public FieldVisitor visitField(int access, String name, String desc, + String signature, Object value) { + if (cv != null) { + return cv.visitField(access, name, desc, signature, value); + } + return null; + } + + /** + * Visits a method of the class. This method must return a new + * {@link MethodVisitor} instance (or null) each time it is called, + * i.e., it should not return a previously returned visitor. + * + * @param access + * the method's access flags (see {@link Opcodes}). This + * parameter also indicates if the method is synthetic and/or + * deprecated. + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type Type}). + * @param signature + * the method's signature. May be null if the method + * parameters, return type and exceptions do not use generic + * types. + * @param exceptions + * the internal names of the method's exception classes (see + * {@link Type#getInternalName() getInternalName}). May be + * null. + * @return an object to visit the byte code of the method, or null + * if this class visitor is not interested in visiting the code of + * this method. + */ + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + if (cv != null) { + return cv.visitMethod(access, name, desc, signature, exceptions); + } + return null; + } + + /** + * Visits the end of the class. This method, which is the last one to be + * called, is used to inform the visitor that all the fields and methods of + * the class have been visited. + */ + public void visitEnd() { + if (cv != null) { + cv.visitEnd(); + } + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/ClassWriter.java b/blade-core/src/main/java/org/objectweb/asm/ClassWriter.java new file mode 100644 index 000000000..f4dd6c631 --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/ClassWriter.java @@ -0,0 +1,1811 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * A {@link ClassVisitor} that generates classes in bytecode form. More + * precisely this visitor generates a byte array conforming to the Java class + * file format. It can be used alone, to generate a Java class "from scratch", + * or with one or more {@link ClassReader ClassReader} and adapter class visitor + * to generate a modified class from one or more existing Java classes. + * + * @author Eric Bruneton + */ +public class ClassWriter extends ClassVisitor { + + /** + * Flag to automatically compute the maximum stack size and the maximum + * number of local variables of methods. If this flag is set, then the + * arguments of the {@link MethodVisitor#visitMaxs visitMaxs} method of the + * {@link MethodVisitor} returned by the {@link #visitMethod visitMethod} + * method will be ignored, and computed automatically from the signature and + * the bytecode of each method. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_MAXS = 1; + + /** + * Flag to automatically compute the stack map frames of methods from + * scratch. If this flag is set, then the calls to the + * {@link MethodVisitor#visitFrame} method are ignored, and the stack map + * frames are recomputed from the methods bytecode. The arguments of the + * {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and + * recomputed from the bytecode. In other words, computeFrames implies + * computeMaxs. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_FRAMES = 2; + + /** + * Pseudo access flag to distinguish between the synthetic attribute and the + * synthetic access flag. + */ + static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000; + + /** + * Factor to convert from ACC_SYNTHETIC_ATTRIBUTE to Opcode.ACC_SYNTHETIC. + */ + static final int TO_ACC_SYNTHETIC = ACC_SYNTHETIC_ATTRIBUTE + / Opcodes.ACC_SYNTHETIC; + + /** + * The type of instructions without any argument. + */ + static final int NOARG_INSN = 0; + + /** + * The type of instructions with an signed byte argument. + */ + static final int SBYTE_INSN = 1; + + /** + * The type of instructions with an signed short argument. + */ + static final int SHORT_INSN = 2; + + /** + * The type of instructions with a local variable index argument. + */ + static final int VAR_INSN = 3; + + /** + * The type of instructions with an implicit local variable index argument. + */ + static final int IMPLVAR_INSN = 4; + + /** + * The type of instructions with a type descriptor argument. + */ + static final int TYPE_INSN = 5; + + /** + * The type of field and method invocations instructions. + */ + static final int FIELDORMETH_INSN = 6; + + /** + * The type of the INVOKEINTERFACE/INVOKEDYNAMIC instruction. + */ + static final int ITFMETH_INSN = 7; + + /** + * The type of the INVOKEDYNAMIC instruction. + */ + static final int INDYMETH_INSN = 8; + + /** + * The type of instructions with a 2 bytes bytecode offset label. + */ + static final int LABEL_INSN = 9; + + /** + * The type of instructions with a 4 bytes bytecode offset label. + */ + static final int LABELW_INSN = 10; + + /** + * The type of the LDC instruction. + */ + static final int LDC_INSN = 11; + + /** + * The type of the LDC_W and LDC2_W instructions. + */ + static final int LDCW_INSN = 12; + + /** + * The type of the IINC instruction. + */ + static final int IINC_INSN = 13; + + /** + * The type of the TABLESWITCH instruction. + */ + static final int TABL_INSN = 14; + + /** + * The type of the LOOKUPSWITCH instruction. + */ + static final int LOOK_INSN = 15; + + /** + * The type of the MULTIANEWARRAY instruction. + */ + static final int MANA_INSN = 16; + + /** + * The type of the WIDE instruction. + */ + static final int WIDE_INSN = 17; + + /** + * The instruction types of all JVM opcodes. + */ + static final byte[] TYPE; + + /** + * The type of CONSTANT_Class constant pool items. + */ + static final int CLASS = 7; + + /** + * The type of CONSTANT_Fieldref constant pool items. + */ + static final int FIELD = 9; + + /** + * The type of CONSTANT_Methodref constant pool items. + */ + static final int METH = 10; + + /** + * The type of CONSTANT_InterfaceMethodref constant pool items. + */ + static final int IMETH = 11; + + /** + * The type of CONSTANT_String constant pool items. + */ + static final int STR = 8; + + /** + * The type of CONSTANT_Integer constant pool items. + */ + static final int INT = 3; + + /** + * The type of CONSTANT_Float constant pool items. + */ + static final int FLOAT = 4; + + /** + * The type of CONSTANT_Long constant pool items. + */ + static final int LONG = 5; + + /** + * The type of CONSTANT_Double constant pool items. + */ + static final int DOUBLE = 6; + + /** + * The type of CONSTANT_NameAndType constant pool items. + */ + static final int NAME_TYPE = 12; + + /** + * The type of CONSTANT_Utf8 constant pool items. + */ + static final int UTF8 = 1; + + /** + * The type of CONSTANT_MethodType constant pool items. + */ + static final int MTYPE = 16; + + /** + * The type of CONSTANT_MethodHandle constant pool items. + */ + static final int HANDLE = 15; + + /** + * The type of CONSTANT_InvokeDynamic constant pool items. + */ + static final int INDY = 18; + + /** + * The base value for all CONSTANT_MethodHandle constant pool items. + * Internally, ASM store the 9 variations of CONSTANT_MethodHandle into 9 + * different items. + */ + static final int HANDLE_BASE = 20; + + /** + * Normal type Item stored in the ClassWriter {@link ClassWriter#typeTable}, + * instead of the constant pool, in order to avoid clashes with normal + * constant pool items in the ClassWriter constant pool's hash table. + */ + static final int TYPE_NORMAL = 30; + + /** + * Uninitialized type Item stored in the ClassWriter + * {@link ClassWriter#typeTable}, instead of the constant pool, in order to + * avoid clashes with normal constant pool items in the ClassWriter constant + * pool's hash table. + */ + static final int TYPE_UNINIT = 31; + + /** + * Merged type Item stored in the ClassWriter {@link ClassWriter#typeTable}, + * instead of the constant pool, in order to avoid clashes with normal + * constant pool items in the ClassWriter constant pool's hash table. + */ + static final int TYPE_MERGED = 32; + + /** + * The type of BootstrapMethods items. These items are stored in a special + * class attribute named BootstrapMethods and not in the constant pool. + */ + static final int BSM = 33; + + /** + * The class reader from which this class writer was constructed, if any. + */ + ClassReader cr; + + /** + * Minor and major version numbers of the class to be generated. + */ + int version; + + /** + * Index of the next item to be added in the constant pool. + */ + int index; + + /** + * The constant pool of this class. + */ + final ByteVector pool; + + /** + * The constant pool's hash table data. + */ + Item[] items; + + /** + * The threshold of the constant pool's hash table. + */ + int threshold; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key2; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key3; + + /** + * A reusable key used to look for items in the {@link #items} hash table. + */ + final Item key4; + + /** + * A type table used to temporarily store internal names that will not + * necessarily be stored in the constant pool. This type table is used by + * the control flow and data flow analysis algorithm used to compute stack + * map frames from scratch. This array associates to each index i + * the Item whose index is i. All Item objects stored in this array + * are also stored in the {@link #items} hash table. These two arrays allow + * to retrieve an Item from its index or, conversely, to get the index of an + * Item from its value. Each Item stores an internal name in its + * {@link Item#strVal1} field. + */ + Item[] typeTable; + + /** + * Number of elements in the {@link #typeTable} array. + */ + private short typeCount; + + /** + * The access flags of this class. + */ + private int access; + + /** + * The constant pool item that contains the internal name of this class. + */ + private int name; + + /** + * The internal name of this class. + */ + String thisName; + + /** + * The constant pool item that contains the signature of this class. + */ + private int signature; + + /** + * The constant pool item that contains the internal name of the super class + * of this class. + */ + private int superName; + + /** + * Number of interfaces implemented or extended by this class or interface. + */ + private int interfaceCount; + + /** + * The interfaces implemented or extended by this class or interface. More + * precisely, this array contains the indexes of the constant pool items + * that contain the internal names of these interfaces. + */ + private int[] interfaces; + + /** + * The index of the constant pool item that contains the name of the source + * file from which this class was compiled. + */ + private int sourceFile; + + /** + * The SourceDebug attribute of this class. + */ + private ByteVector sourceDebug; + + /** + * The constant pool item that contains the name of the enclosing class of + * this class. + */ + private int enclosingMethodOwner; + + /** + * The constant pool item that contains the name and descriptor of the + * enclosing method of this class. + */ + private int enclosingMethod; + + /** + * The runtime visible annotations of this class. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this class. + */ + private AnnotationWriter ianns; + + /** + * The runtime visible type annotations of this class. + */ + private AnnotationWriter tanns; + + /** + * The runtime invisible type annotations of this class. + */ + private AnnotationWriter itanns; + + /** + * The non standard attributes of this class. + */ + private Attribute attrs; + + /** + * The number of entries in the InnerClasses attribute. + */ + private int innerClassesCount; + + /** + * The InnerClasses attribute. + */ + private ByteVector innerClasses; + + /** + * The number of entries in the BootstrapMethods attribute. + */ + int bootstrapMethodsCount; + + /** + * The BootstrapMethods attribute. + */ + ByteVector bootstrapMethods; + + /** + * The fields of this class. These fields are stored in a linked list of + * {@link FieldWriter} objects, linked to each other by their + * {@link FieldWriter#fv} field. This field stores the first element of this + * list. + */ + FieldWriter firstField; + + /** + * The fields of this class. These fields are stored in a linked list of + * {@link FieldWriter} objects, linked to each other by their + * {@link FieldWriter#fv} field. This field stores the last element of this + * list. + */ + FieldWriter lastField; + + /** + * The methods of this class. These methods are stored in a linked list of + * {@link MethodWriter} objects, linked to each other by their + * {@link MethodWriter#mv} field. This field stores the first element of + * this list. + */ + MethodWriter firstMethod; + + /** + * The methods of this class. These methods are stored in a linked list of + * {@link MethodWriter} objects, linked to each other by their + * {@link MethodWriter#mv} field. This field stores the last element of this + * list. + */ + MethodWriter lastMethod; + + /** + * true if the maximum stack size and number of local variables + * must be automatically computed. + */ + private boolean computeMaxs; + + /** + * true if the stack map frames must be recomputed from scratch. + */ + private boolean computeFrames; + + /** + * true if the stack map tables of this class are invalid. The + * {@link MethodWriter#resizeInstructions} method cannot transform existing + * stack map tables, and so produces potentially invalid classes when it is + * executed. In this case the class is reread and rewritten with the + * {@link #COMPUTE_FRAMES} option (the resizeInstructions method can resize + * stack map tables when this option is used). + */ + boolean invalidFrames; + + // ------------------------------------------------------------------------ + // Static initializer + // ------------------------------------------------------------------------ + + /** + * Computes the instruction types of JVM opcodes. + */ + static { + int i; + byte[] b = new byte[220]; + String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD" + + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + + "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA" + + "AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ"; + for (i = 0; i < b.length; ++i) { + b[i] = (byte) (s.charAt(i) - 'A'); + } + TYPE = b; + + // code to generate the above string + // + // // SBYTE_INSN instructions + // b[Constants.NEWARRAY] = SBYTE_INSN; + // b[Constants.BIPUSH] = SBYTE_INSN; + // + // // SHORT_INSN instructions + // b[Constants.SIPUSH] = SHORT_INSN; + // + // // (IMPL)VAR_INSN instructions + // b[Constants.RET] = VAR_INSN; + // for (i = Constants.ILOAD; i <= Constants.ALOAD; ++i) { + // b[i] = VAR_INSN; + // } + // for (i = Constants.ISTORE; i <= Constants.ASTORE; ++i) { + // b[i] = VAR_INSN; + // } + // for (i = 26; i <= 45; ++i) { // ILOAD_0 to ALOAD_3 + // b[i] = IMPLVAR_INSN; + // } + // for (i = 59; i <= 78; ++i) { // ISTORE_0 to ASTORE_3 + // b[i] = IMPLVAR_INSN; + // } + // + // // TYPE_INSN instructions + // b[Constants.NEW] = TYPE_INSN; + // b[Constants.ANEWARRAY] = TYPE_INSN; + // b[Constants.CHECKCAST] = TYPE_INSN; + // b[Constants.INSTANCEOF] = TYPE_INSN; + // + // // (Set)FIELDORMETH_INSN instructions + // for (i = Constants.GETSTATIC; i <= Constants.INVOKESTATIC; ++i) { + // b[i] = FIELDORMETH_INSN; + // } + // b[Constants.INVOKEINTERFACE] = ITFMETH_INSN; + // b[Constants.INVOKEDYNAMIC] = INDYMETH_INSN; + // + // // LABEL(W)_INSN instructions + // for (i = Constants.IFEQ; i <= Constants.JSR; ++i) { + // b[i] = LABEL_INSN; + // } + // b[Constants.IFNULL] = LABEL_INSN; + // b[Constants.IFNONNULL] = LABEL_INSN; + // b[200] = LABELW_INSN; // GOTO_W + // b[201] = LABELW_INSN; // JSR_W + // // temporary opcodes used internally by ASM - see Label and + // MethodWriter + // for (i = 202; i < 220; ++i) { + // b[i] = LABEL_INSN; + // } + // + // // LDC(_W) instructions + // b[Constants.LDC] = LDC_INSN; + // b[19] = LDCW_INSN; // LDC_W + // b[20] = LDCW_INSN; // LDC2_W + // + // // special instructions + // b[Constants.IINC] = IINC_INSN; + // b[Constants.TABLESWITCH] = TABL_INSN; + // b[Constants.LOOKUPSWITCH] = LOOK_INSN; + // b[Constants.MULTIANEWARRAY] = MANA_INSN; + // b[196] = WIDE_INSN; // WIDE + // + // for (i = 0; i < b.length; ++i) { + // System.err.print((char)('A' + b[i])); + // } + // System.err.println(); + } + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link ClassWriter} object. + * + * @param flags + * option flags that can be used to modify the default behavior + * of this class. See {@link #COMPUTE_MAXS}, + * {@link #COMPUTE_FRAMES}. + */ + public ClassWriter(final int flags) { + super(Opcodes.ASM5); + index = 1; + pool = new ByteVector(); + items = new Item[256]; + threshold = (int) (0.75d * items.length); + key = new Item(); + key2 = new Item(); + key3 = new Item(); + key4 = new Item(); + this.computeMaxs = (flags & COMPUTE_MAXS) != 0; + this.computeFrames = (flags & COMPUTE_FRAMES) != 0; + } + + /** + * Constructs a new {@link ClassWriter} object and enables optimizations for + * "mostly add" bytecode transformations. These optimizations are the + * following: + * + *
    + *
  • The constant pool from the original class is copied as is in the new + * class, which saves time. New constant pool entries will be added at the + * end if necessary, but unused constant pool entries won't be + * removed.
  • + *
  • Methods that are not transformed are copied as is in the new class, + * directly from the original class bytecode (i.e. without emitting visit + * events for all the method instructions), which saves a lot of + * time. Untransformed methods are detected by the fact that the + * {@link ClassReader} receives {@link MethodVisitor} objects that come from + * a {@link ClassWriter} (and not from any other {@link ClassVisitor} + * instance).
  • + *
+ * + * @param classReader + * the {@link ClassReader} used to read the original class. It + * will be used to copy the entire constant pool from the + * original class and also to copy other fragments of original + * bytecode where applicable. + * @param flags + * option flags that can be used to modify the default behavior + * of this class. These option flags do not affect methods + * that are copied as is in the new class. This means that the + * maximum stack size nor the stack frames will be computed for + * these methods. See {@link #COMPUTE_MAXS}, + * {@link #COMPUTE_FRAMES}. + */ + public ClassWriter(final ClassReader classReader, final int flags) { + this(flags); + classReader.copyPool(this); + this.cr = classReader; + } + + // ------------------------------------------------------------------------ + // Implementation of the ClassVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public final void visit(final int version, final int access, + final String name, final String signature, final String superName, + final String[] interfaces) { + this.version = version; + this.access = access; + this.name = newClass(name); + thisName = name; + if (ClassReader.SIGNATURES && signature != null) { + this.signature = newUTF8(signature); + } + this.superName = superName == null ? 0 : newClass(superName); + if (interfaces != null && interfaces.length > 0) { + interfaceCount = interfaces.length; + this.interfaces = new int[interfaceCount]; + for (int i = 0; i < interfaceCount; ++i) { + this.interfaces[i] = newClass(interfaces[i]); + } + } + } + + @Override + public final void visitSource(final String file, final String debug) { + if (file != null) { + sourceFile = newUTF8(file); + } + if (debug != null) { + sourceDebug = new ByteVector().encodeUTF8(debug, 0, + Integer.MAX_VALUE); + } + } + + @Override + public final void visitOuterClass(final String owner, final String name, + final String desc) { + enclosingMethodOwner = newClass(owner); + if (name != null && desc != null) { + enclosingMethod = newNameType(name, desc); + } + } + + @Override + public final AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + @Override + public final AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, final String desc, final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = tanns; + tanns = aw; + } else { + aw.next = itanns; + itanns = aw; + } + return aw; + } + + @Override + public final void visitAttribute(final Attribute attr) { + attr.next = attrs; + attrs = attr; + } + + @Override + public final void visitInnerClass(final String name, + final String outerName, final String innerName, final int access) { + if (innerClasses == null) { + innerClasses = new ByteVector(); + } + // Sec. 4.7.6 of the JVMS states "Every CONSTANT_Class_info entry in the + // constant_pool table which represents a class or interface C that is + // not a package member must have exactly one corresponding entry in the + // classes array". To avoid duplicates we keep track in the intVal field + // of the Item of each CONSTANT_Class_info entry C whether an inner + // class entry has already been added for C (this field is unused for + // class entries, and changing its value does not change the hashcode + // and equality tests). If so we store the index of this inner class + // entry (plus one) in intVal. This hack allows duplicate detection in + // O(1) time. + Item nameItem = newClassItem(name); + if (nameItem.intVal == 0) { + ++innerClassesCount; + innerClasses.putShort(nameItem.index); + innerClasses.putShort(outerName == null ? 0 : newClass(outerName)); + innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName)); + innerClasses.putShort(access); + nameItem.intVal = innerClassesCount; + } else { + // Compare the inner classes entry nameItem.intVal - 1 with the + // arguments of this method and throw an exception if there is a + // difference? + } + } + + @Override + public final FieldVisitor visitField(final int access, final String name, + final String desc, final String signature, final Object value) { + return new FieldWriter(this, access, name, desc, signature, value); + } + + @Override + public final MethodVisitor visitMethod(final int access, final String name, + final String desc, final String signature, final String[] exceptions) { + return new MethodWriter(this, access, name, desc, signature, + exceptions, computeMaxs, computeFrames); + } + + @Override + public final void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Other public methods + // ------------------------------------------------------------------------ + + /** + * Returns the bytecode of the class that was build with this class writer. + * + * @return the bytecode of the class that was build with this class writer. + */ + public byte[] toByteArray() { + if (index > 0xFFFF) { + throw new RuntimeException("Class file too large!"); + } + // computes the real size of the bytecode of this class + int size = 24 + 2 * interfaceCount; + int nbFields = 0; + FieldWriter fb = firstField; + while (fb != null) { + ++nbFields; + size += fb.getSize(); + fb = (FieldWriter) fb.fv; + } + int nbMethods = 0; + MethodWriter mb = firstMethod; + while (mb != null) { + ++nbMethods; + size += mb.getSize(); + mb = (MethodWriter) mb.mv; + } + int attributeCount = 0; + if (bootstrapMethods != null) { + // we put it as first attribute in order to improve a bit + // ClassReader.copyBootstrapMethods + ++attributeCount; + size += 8 + bootstrapMethods.length; + newUTF8("BootstrapMethods"); + } + if (ClassReader.SIGNATURES && signature != 0) { + ++attributeCount; + size += 8; + newUTF8("Signature"); + } + if (sourceFile != 0) { + ++attributeCount; + size += 8; + newUTF8("SourceFile"); + } + if (sourceDebug != null) { + ++attributeCount; + size += sourceDebug.length + 6; + newUTF8("SourceDebugExtension"); + } + if (enclosingMethodOwner != 0) { + ++attributeCount; + size += 10; + newUTF8("EnclosingMethod"); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + size += 6; + newUTF8("Deprecated"); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((version & 0xFFFF) < Opcodes.V1_5 + || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) { + ++attributeCount; + size += 6; + newUTF8("Synthetic"); + } + } + if (innerClasses != null) { + ++attributeCount; + size += 8 + innerClasses.length; + newUTF8("InnerClasses"); + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + size += 8 + anns.getSize(); + newUTF8("RuntimeVisibleAnnotations"); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + size += 8 + ianns.getSize(); + newUTF8("RuntimeInvisibleAnnotations"); + } + if (ClassReader.ANNOTATIONS && tanns != null) { + ++attributeCount; + size += 8 + tanns.getSize(); + newUTF8("RuntimeVisibleTypeAnnotations"); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + ++attributeCount; + size += 8 + itanns.getSize(); + newUTF8("RuntimeInvisibleTypeAnnotations"); + } + if (attrs != null) { + attributeCount += attrs.getCount(); + size += attrs.getSize(this, null, 0, -1, -1); + } + size += pool.length; + // allocates a byte vector of this size, in order to avoid unnecessary + // arraycopy operations in the ByteVector.enlarge() method + ByteVector out = new ByteVector(size); + out.putInt(0xCAFEBABE).putInt(version); + out.putShort(index).putByteArray(pool.data, 0, pool.length); + int mask = Opcodes.ACC_DEPRECATED | ACC_SYNTHETIC_ATTRIBUTE + | ((access & ACC_SYNTHETIC_ATTRIBUTE) / TO_ACC_SYNTHETIC); + out.putShort(access & ~mask).putShort(name).putShort(superName); + out.putShort(interfaceCount); + for (int i = 0; i < interfaceCount; ++i) { + out.putShort(interfaces[i]); + } + out.putShort(nbFields); + fb = firstField; + while (fb != null) { + fb.put(out); + fb = (FieldWriter) fb.fv; + } + out.putShort(nbMethods); + mb = firstMethod; + while (mb != null) { + mb.put(out); + mb = (MethodWriter) mb.mv; + } + out.putShort(attributeCount); + if (bootstrapMethods != null) { + out.putShort(newUTF8("BootstrapMethods")); + out.putInt(bootstrapMethods.length + 2).putShort( + bootstrapMethodsCount); + out.putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length); + } + if (ClassReader.SIGNATURES && signature != 0) { + out.putShort(newUTF8("Signature")).putInt(2).putShort(signature); + } + if (sourceFile != 0) { + out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile); + } + if (sourceDebug != null) { + int len = sourceDebug.length; + out.putShort(newUTF8("SourceDebugExtension")).putInt(len); + out.putByteArray(sourceDebug.data, 0, len); + } + if (enclosingMethodOwner != 0) { + out.putShort(newUTF8("EnclosingMethod")).putInt(4); + out.putShort(enclosingMethodOwner).putShort(enclosingMethod); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(newUTF8("Deprecated")).putInt(0); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((version & 0xFFFF) < Opcodes.V1_5 + || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) { + out.putShort(newUTF8("Synthetic")).putInt(0); + } + } + if (innerClasses != null) { + out.putShort(newUTF8("InnerClasses")); + out.putInt(innerClasses.length + 2).putShort(innerClassesCount); + out.putByteArray(innerClasses.data, 0, innerClasses.length); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (ClassReader.ANNOTATIONS && tanns != null) { + out.putShort(newUTF8("RuntimeVisibleTypeAnnotations")); + tanns.put(out); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + out.putShort(newUTF8("RuntimeInvisibleTypeAnnotations")); + itanns.put(out); + } + if (attrs != null) { + attrs.put(this, null, 0, -1, -1, out); + } + if (invalidFrames) { + anns = null; + ianns = null; + attrs = null; + innerClassesCount = 0; + innerClasses = null; + bootstrapMethodsCount = 0; + bootstrapMethods = null; + firstField = null; + lastField = null; + firstMethod = null; + lastMethod = null; + computeMaxs = false; + computeFrames = true; + invalidFrames = false; + new ClassReader(out.data).accept(this, ClassReader.SKIP_FRAMES); + return toByteArray(); + } + return out.data; + } + + // ------------------------------------------------------------------------ + // Utility methods: constant pool management + // ------------------------------------------------------------------------ + + /** + * Adds a number or string constant to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * + * @param cst + * the value of the constant to be added to the constant pool. + * This parameter must be an {@link Integer}, a {@link Float}, a + * {@link Long}, a {@link Double}, a {@link String} or a + * {@link Type}. + * @return a new or already existing constant item with the given value. + */ + Item newConstItem(final Object cst) { + if (cst instanceof Integer) { + int val = ((Integer) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Byte) { + int val = ((Byte) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Character) { + int val = ((Character) cst).charValue(); + return newInteger(val); + } else if (cst instanceof Short) { + int val = ((Short) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Boolean) { + int val = ((Boolean) cst).booleanValue() ? 1 : 0; + return newInteger(val); + } else if (cst instanceof Float) { + float val = ((Float) cst).floatValue(); + return newFloat(val); + } else if (cst instanceof Long) { + long val = ((Long) cst).longValue(); + return newLong(val); + } else if (cst instanceof Double) { + double val = ((Double) cst).doubleValue(); + return newDouble(val); + } else if (cst instanceof String) { + return newString((String) cst); + } else if (cst instanceof Type) { + Type t = (Type) cst; + int s = t.getSort(); + if (s == Type.OBJECT) { + return newClassItem(t.getInternalName()); + } else if (s == Type.METHOD) { + return newMethodTypeItem(t.getDescriptor()); + } else { // s == primitive type or array + return newClassItem(t.getDescriptor()); + } + } else if (cst instanceof Handle) { + Handle h = (Handle) cst; + return newHandleItem(h.tag, h.owner, h.name, h.desc, h.itf); + } else { + throw new IllegalArgumentException("value " + cst); + } + } + + /** + * Adds a number or string constant to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param cst + * the value of the constant to be added to the constant pool. + * This parameter must be an {@link Integer}, a {@link Float}, a + * {@link Long}, a {@link Double} or a {@link String}. + * @return the index of a new or already existing constant item with the + * given value. + */ + public int newConst(final Object cst) { + return newConstItem(cst).index; + } + + /** + * Adds an UTF8 string to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters. + * + * @param value + * the String value. + * @return the index of a new or already existing UTF8 item. + */ + public int newUTF8(final String value) { + key.set(UTF8, value, null, null); + Item result = get(key); + if (result == null) { + pool.putByte(UTF8).putUTF8(value); + result = new Item(index++, key); + put(result); + } + return result.index; + } + + /** + * Adds a class reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param value + * the internal name of the class. + * @return a new or already existing class reference item. + */ + Item newClassItem(final String value) { + key2.set(CLASS, value, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(CLASS, newUTF8(value)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a class reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param value + * the internal name of the class. + * @return the index of a new or already existing class reference item. + */ + public int newClass(final String value) { + return newClassItem(value).index; + } + + /** + * Adds a method type reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param methodDesc + * method descriptor of the method type. + * @return a new or already existing method type reference item. + */ + Item newMethodTypeItem(final String methodDesc) { + key2.set(MTYPE, methodDesc, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(MTYPE, newUTF8(methodDesc)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a method type reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param methodDesc + * method descriptor of the method type. + * @return the index of a new or already existing method type reference + * item. + */ + public int newMethodType(final String methodDesc) { + return newMethodTypeItem(methodDesc).index; + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters. + * + * @param tag + * the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the field or method owner class. + * @param name + * the name of the field or method. + * @param desc + * the descriptor of the field or method. + * @param itf + * true if the owner is an interface. + * @return a new or an already existing method type reference item. + */ + Item newHandleItem(final int tag, final String owner, final String name, + final String desc, final boolean itf) { + key4.set(HANDLE_BASE + tag, owner, name, desc); + Item result = get(key4); + if (result == null) { + if (tag <= Opcodes.H_PUTSTATIC) { + put112(HANDLE, tag, newField(owner, name, desc)); + } else { + put112(HANDLE, + tag, + newMethod(owner, name, desc, itf)); + } + result = new Item(index++, key4); + put(result); + } + return result; + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters. + * + * @param tag + * the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the field or method owner class. + * @param name + * the name of the field or method. + * @param desc + * the descriptor of the field or method. + * @return the index of a new or already existing method type reference + * item. + * + * @deprecated this method is superseded by + * {@link #newHandle(int, String, String, String, boolean)}. + */ + @Deprecated + public int newHandle(final int tag, final String owner, final String name, + final String desc) { + return newHandle(tag, owner, name, desc, tag == Opcodes.H_INVOKEINTERFACE); + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters. + * + * @param tag + * the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the field or method owner class. + * @param name + * the name of the field or method. + * @param desc + * the descriptor of the field or method. + * @param itf + * true if the owner is an interface. + * @return the index of a new or already existing method type reference + * item. + */ + public int newHandle(final int tag, final String owner, final String name, + final String desc, final boolean itf) { + return newHandleItem(tag, owner, name, desc, itf).index; + } + + /** + * Adds an invokedynamic reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param name + * name of the invoked method. + * @param desc + * descriptor of the invoke method. + * @param bsm + * the bootstrap method. + * @param bsmArgs + * the bootstrap method constant arguments. + * + * @return a new or an already existing invokedynamic type reference item. + */ + Item newInvokeDynamicItem(final String name, final String desc, + final Handle bsm, final Object... bsmArgs) { + // cache for performance + ByteVector bootstrapMethods = this.bootstrapMethods; + if (bootstrapMethods == null) { + bootstrapMethods = this.bootstrapMethods = new ByteVector(); + } + + int position = bootstrapMethods.length; // record current position + + int hashCode = bsm.hashCode(); + bootstrapMethods.putShort(newHandle(bsm.tag, bsm.owner, bsm.name, + bsm.desc, bsm.isInterface())); + + int argsLength = bsmArgs.length; + bootstrapMethods.putShort(argsLength); + + for (int i = 0; i < argsLength; i++) { + Object bsmArg = bsmArgs[i]; + hashCode ^= bsmArg.hashCode(); + bootstrapMethods.putShort(newConst(bsmArg)); + } + + byte[] data = bootstrapMethods.data; + int length = (1 + 1 + argsLength) << 1; // (bsm + argCount + arguments) + hashCode &= 0x7FFFFFFF; + Item result = items[hashCode % items.length]; + loop: while (result != null) { + if (result.type != BSM || result.hashCode != hashCode) { + result = result.next; + continue; + } + + // because the data encode the size of the argument + // we don't need to test if these size are equals + int resultPosition = result.intVal; + for (int p = 0; p < length; p++) { + if (data[position + p] != data[resultPosition + p]) { + result = result.next; + continue loop; + } + } + break; + } + + int bootstrapMethodIndex; + if (result != null) { + bootstrapMethodIndex = result.index; + bootstrapMethods.length = position; // revert to old position + } else { + bootstrapMethodIndex = bootstrapMethodsCount++; + result = new Item(bootstrapMethodIndex); + result.set(position, hashCode); + put(result); + } + + // now, create the InvokeDynamic constant + key3.set(name, desc, bootstrapMethodIndex); + result = get(key3); + if (result == null) { + put122(INDY, bootstrapMethodIndex, newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds an invokedynamic reference to the constant pool of the class being + * build. Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param name + * name of the invoked method. + * @param desc + * descriptor of the invoke method. + * @param bsm + * the bootstrap method. + * @param bsmArgs + * the bootstrap method constant arguments. + * + * @return the index of a new or already existing invokedynamic reference + * item. + */ + public int newInvokeDynamic(final String name, final String desc, + final Handle bsm, final Object... bsmArgs) { + return newInvokeDynamicItem(name, desc, bsm, bsmArgs).index; + } + + /** + * Adds a field reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * + * @param owner + * the internal name of the field's owner class. + * @param name + * the field's name. + * @param desc + * the field's descriptor. + * @return a new or already existing field reference item. + */ + Item newFieldItem(final String owner, final String name, final String desc) { + key3.set(FIELD, owner, name, desc); + Item result = get(key3); + if (result == null) { + put122(FIELD, newClass(owner), newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds a field reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param owner + * the internal name of the field's owner class. + * @param name + * the field's name. + * @param desc + * the field's descriptor. + * @return the index of a new or already existing field reference item. + */ + public int newField(final String owner, final String name, final String desc) { + return newFieldItem(owner, name, desc).index; + } + + /** + * Adds a method reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * + * @param owner + * the internal name of the method's owner class. + * @param name + * the method's name. + * @param desc + * the method's descriptor. + * @param itf + * true if owner is an interface. + * @return a new or already existing method reference item. + */ + Item newMethodItem(final String owner, final String name, + final String desc, final boolean itf) { + int type = itf ? IMETH : METH; + key3.set(type, owner, name, desc); + Item result = get(key3); + if (result == null) { + put122(type, newClass(owner), newNameType(name, desc)); + result = new Item(index++, key3); + put(result); + } + return result; + } + + /** + * Adds a method reference to the constant pool of the class being build. + * Does nothing if the constant pool already contains a similar item. + * This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @param owner + * the internal name of the method's owner class. + * @param name + * the method's name. + * @param desc + * the method's descriptor. + * @param itf + * true if owner is an interface. + * @return the index of a new or already existing method reference item. + */ + public int newMethod(final String owner, final String name, + final String desc, final boolean itf) { + return newMethodItem(owner, name, desc, itf).index; + } + + /** + * Adds an integer to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. + * + * @param value + * the int value. + * @return a new or already existing int item. + */ + Item newInteger(final int value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(INT).putInt(value); + result = new Item(index++, key); + put(result); + } + return result; + } + + /** + * Adds a float to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value + * the float value. + * @return a new or already existing float item. + */ + Item newFloat(final float value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(FLOAT).putInt(key.intVal); + result = new Item(index++, key); + put(result); + } + return result; + } + + /** + * Adds a long to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value + * the long value. + * @return a new or already existing long item. + */ + Item newLong(final long value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(LONG).putLong(value); + result = new Item(index, key); + index += 2; + put(result); + } + return result; + } + + /** + * Adds a double to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value + * the double value. + * @return a new or already existing double item. + */ + Item newDouble(final double value) { + key.set(value); + Item result = get(key); + if (result == null) { + pool.putByte(DOUBLE).putLong(key.longVal); + result = new Item(index, key); + index += 2; + put(result); + } + return result; + } + + /** + * Adds a string to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. + * + * @param value + * the String value. + * @return a new or already existing string item. + */ + private Item newString(final String value) { + key2.set(STR, value, null, null); + Item result = get(key2); + if (result == null) { + pool.put12(STR, newUTF8(value)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds a name and type to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. This + * method is intended for {@link Attribute} sub classes, and is normally not + * needed by class generators or adapters. + * + * @param name + * a name. + * @param desc + * a type descriptor. + * @return the index of a new or already existing name and type item. + */ + public int newNameType(final String name, final String desc) { + return newNameTypeItem(name, desc).index; + } + + /** + * Adds a name and type to the constant pool of the class being build. Does + * nothing if the constant pool already contains a similar item. + * + * @param name + * a name. + * @param desc + * a type descriptor. + * @return a new or already existing name and type item. + */ + Item newNameTypeItem(final String name, final String desc) { + key2.set(NAME_TYPE, name, desc, null); + Item result = get(key2); + if (result == null) { + put122(NAME_TYPE, newUTF8(name), newUTF8(desc)); + result = new Item(index++, key2); + put(result); + } + return result; + } + + /** + * Adds the given internal name to {@link #typeTable} and returns its index. + * Does nothing if the type table already contains this internal name. + * + * @param type + * the internal name to be added to the type table. + * @return the index of this internal name in the type table. + */ + int addType(final String type) { + key.set(TYPE_NORMAL, type, null, null); + Item result = get(key); + if (result == null) { + result = addType(key); + } + return result.index; + } + + /** + * Adds the given "uninitialized" type to {@link #typeTable} and returns its + * index. This method is used for UNINITIALIZED types, made of an internal + * name and a bytecode offset. + * + * @param type + * the internal name to be added to the type table. + * @param offset + * the bytecode offset of the NEW instruction that created this + * UNINITIALIZED type value. + * @return the index of this internal name in the type table. + */ + int addUninitializedType(final String type, final int offset) { + key.type = TYPE_UNINIT; + key.intVal = offset; + key.strVal1 = type; + key.hashCode = 0x7FFFFFFF & (TYPE_UNINIT + type.hashCode() + offset); + Item result = get(key); + if (result == null) { + result = addType(key); + } + return result.index; + } + + /** + * Adds the given Item to {@link #typeTable}. + * + * @param item + * the value to be added to the type table. + * @return the added Item, which a new Item instance with the same value as + * the given Item. + */ + private Item addType(final Item item) { + ++typeCount; + Item result = new Item(typeCount, key); + put(result); + if (typeTable == null) { + typeTable = new Item[16]; + } + if (typeCount == typeTable.length) { + Item[] newTable = new Item[2 * typeTable.length]; + System.arraycopy(typeTable, 0, newTable, 0, typeTable.length); + typeTable = newTable; + } + typeTable[typeCount] = result; + return result; + } + + /** + * Returns the index of the common super type of the two given types. This + * method calls {@link #getCommonSuperClass} and caches the result in the + * {@link #items} hash table to speedup future calls with the same + * parameters. + * + * @param type1 + * index of an internal name in {@link #typeTable}. + * @param type2 + * index of an internal name in {@link #typeTable}. + * @return the index of the common super type of the two given types. + */ + int getMergedType(final int type1, final int type2) { + key2.type = TYPE_MERGED; + key2.longVal = type1 | (((long) type2) << 32); + key2.hashCode = 0x7FFFFFFF & (TYPE_MERGED + type1 + type2); + Item result = get(key2); + if (result == null) { + String t = typeTable[type1].strVal1; + String u = typeTable[type2].strVal1; + key2.intVal = addType(getCommonSuperClass(t, u)); + result = new Item((short) 0, key2); + put(result); + } + return result.intVal; + } + + /** + * Returns the common super type of the two given types. The default + * implementation of this method loads the two given classes and uses + * the java.lang.Class methods to find the common super class. It can be + * overridden to compute this common super type in other ways, in particular + * without actually loading any class, or to take into account the class + * that is currently being generated by this ClassWriter, which can of + * course not be loaded since it is under construction. + * + * @param type1 + * the internal name of a class. + * @param type2 + * the internal name of another class. + * @return the internal name of the common super class of the two given + * classes. + */ + protected String getCommonSuperClass(final String type1, final String type2) { + Class c, d; + ClassLoader classLoader = getClass().getClassLoader(); + try { + c = Class.forName(type1.replace('/', '.'), false, classLoader); + d = Class.forName(type2.replace('/', '.'), false, classLoader); + } catch (Exception e) { + throw new RuntimeException(e.toString()); + } + if (c.isAssignableFrom(d)) { + return type1; + } + if (d.isAssignableFrom(c)) { + return type2; + } + if (c.isInterface() || d.isInterface()) { + return "java/lang/Object"; + } else { + do { + c = c.getSuperclass(); + } while (!c.isAssignableFrom(d)); + return c.getName().replace('.', '/'); + } + } + + /** + * Returns the constant pool's hash table item which is equal to the given + * item. + * + * @param key + * a constant pool item. + * @return the constant pool's hash table item which is equal to the given + * item, or null if there is no such item. + */ + private Item get(final Item key) { + Item i = items[key.hashCode % items.length]; + while (i != null && (i.type != key.type || !key.isEqualTo(i))) { + i = i.next; + } + return i; + } + + /** + * Puts the given item in the constant pool's hash table. The hash table + * must not already contains this item. + * + * @param i + * the item to be added to the constant pool's hash table. + */ + private void put(final Item i) { + if (index + typeCount > threshold) { + int ll = items.length; + int nl = ll * 2 + 1; + Item[] newItems = new Item[nl]; + for (int l = ll - 1; l >= 0; --l) { + Item j = items[l]; + while (j != null) { + int index = j.hashCode % newItems.length; + Item k = j.next; + j.next = newItems[index]; + newItems[index] = j; + j = k; + } + } + items = newItems; + threshold = (int) (nl * 0.75); + } + int index = i.hashCode % items.length; + i.next = items[index]; + items[index] = i; + } + + /** + * Puts one byte and two shorts into the constant pool. + * + * @param b + * a byte. + * @param s1 + * a short. + * @param s2 + * another short. + */ + private void put122(final int b, final int s1, final int s2) { + pool.put12(b, s1).putShort(s2); + } + + /** + * Puts two bytes and one short into the constant pool. + * + * @param b1 + * a byte. + * @param b2 + * another byte. + * @param s + * a short. + */ + private void put112(final int b1, final int b2, final int s) { + pool.put11(b1, b2).putShort(s); + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/Context.java b/blade-core/src/main/java/org/objectweb/asm/Context.java new file mode 100644 index 000000000..363b34cf8 --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/Context.java @@ -0,0 +1,145 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.objectweb.asm; + +/** + * Information about a class being parsed in a {@link ClassReader}. + * + * @author Eric Bruneton + */ +class Context { + + /** + * Prototypes of the attributes that must be parsed for this class. + */ + Attribute[] attrs; + + /** + * The {@link ClassReader} option flags for the parsing of this class. + */ + int flags; + + /** + * The buffer used to read strings. + */ + char[] buffer; + + /** + * The start index of each bootstrap method. + */ + int[] bootstrapMethods; + + /** + * The access flags of the method currently being parsed. + */ + int access; + + /** + * The name of the method currently being parsed. + */ + String name; + + /** + * The descriptor of the method currently being parsed. + */ + String desc; + + /** + * The label objects, indexed by bytecode offset, of the method currently + * being parsed (only bytecode offsets for which a label is needed have a + * non null associated Label object). + */ + Label[] labels; + + /** + * The target of the type annotation currently being parsed. + */ + int typeRef; + + /** + * The path of the type annotation currently being parsed. + */ + TypePath typePath; + + /** + * The offset of the latest stack map frame that has been parsed. + */ + int offset; + + /** + * The labels corresponding to the start of the local variable ranges in the + * local variable type annotation currently being parsed. + */ + Label[] start; + + /** + * The labels corresponding to the end of the local variable ranges in the + * local variable type annotation currently being parsed. + */ + Label[] end; + + /** + * The local variable indices for each local variable range in the local + * variable type annotation currently being parsed. + */ + int[] index; + + /** + * The encoding of the latest stack map frame that has been parsed. + */ + int mode; + + /** + * The number of locals in the latest stack map frame that has been parsed. + */ + int localCount; + + /** + * The number locals in the latest stack map frame that has been parsed, + * minus the number of locals in the previous frame. + */ + int localDiff; + + /** + * The local values of the latest stack map frame that has been parsed. + */ + Object[] local; + + /** + * The stack size of the latest stack map frame that has been parsed. + */ + int stackCount; + + /** + * The stack values of the latest stack map frame that has been parsed. + */ + Object[] stack; +} \ No newline at end of file diff --git a/blade-core/src/main/java/org/objectweb/asm/Edge.java b/blade-core/src/main/java/org/objectweb/asm/Edge.java new file mode 100644 index 000000000..4e87cbaba --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/Edge.java @@ -0,0 +1,75 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * An edge in the control flow graph of a method body. See {@link Label Label}. + * + * @author Eric Bruneton + */ +class Edge { + + /** + * Denotes a normal control flow graph edge. + */ + static final int NORMAL = 0; + + /** + * Denotes a control flow graph edge corresponding to an exception handler. + * More precisely any {@link Edge} whose {@link #info} is strictly positive + * corresponds to an exception handler. The actual value of {@link #info} is + * the index, in the {@link ClassWriter} type table, of the exception that + * is catched. + */ + static final int EXCEPTION = 0x7FFFFFFF; + + /** + * Information about this control flow graph edge. If + * {@link ClassWriter#COMPUTE_MAXS} is used this field is the (relative) + * stack size in the basic block from which this edge originates. This size + * is equal to the stack size at the "jump" instruction to which this edge + * corresponds, relatively to the stack size at the beginning of the + * originating basic block. If {@link ClassWriter#COMPUTE_FRAMES} is used, + * this field is the kind of this control flow graph edge (i.e. NORMAL or + * EXCEPTION). + */ + int info; + + /** + * The successor block of the basic block from which this edge originates. + */ + Label successor; + + /** + * The next edge in the list of successors of the originating basic block. + * See {@link Label#successors successors}. + */ + Edge next; +} diff --git a/blade-core/src/main/java/org/objectweb/asm/FieldVisitor.java b/blade-core/src/main/java/org/objectweb/asm/FieldVisitor.java new file mode 100644 index 000000000..2372e4c7f --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/FieldVisitor.java @@ -0,0 +1,150 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * A visitor to visit a Java field. The methods of this class must be called in + * the following order: ( visitAnnotation | + * visitTypeAnnotation | visitAttribute )* visitEnd. + * + * @author Eric Bruneton + */ +public abstract class FieldVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + protected final int api; + + /** + * The field visitor to which this visitor must delegate method calls. May + * be null. + */ + protected FieldVisitor fv; + + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + public FieldVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + * @param fv + * the field visitor to which this visitor must delegate method + * calls. May be null. + */ + public FieldVisitor(final int api, final FieldVisitor fv) { + if (api != Opcodes.ASM4 && api != Opcodes.ASM5) { + throw new IllegalArgumentException(); + } + this.api = api; + this.fv = fv; + } + + /** + * Visits an annotation of the field. + * + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (fv != null) { + return fv.visitAnnotation(desc, visible); + } + return null; + } + + /** + * Visits an annotation on the type of the field. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#FIELD FIELD}. See + * {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (fv != null) { + return fv.visitTypeAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the field. + * + * @param attr + * an attribute. + */ + public void visitAttribute(Attribute attr) { + if (fv != null) { + fv.visitAttribute(attr); + } + } + + /** + * Visits the end of the field. This method, which is the last one to be + * called, is used to inform the visitor that all the annotations and + * attributes of the field have been visited. + */ + public void visitEnd() { + if (fv != null) { + fv.visitEnd(); + } + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/FieldWriter.java b/blade-core/src/main/java/org/objectweb/asm/FieldWriter.java new file mode 100644 index 000000000..84d92aae9 --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/FieldWriter.java @@ -0,0 +1,329 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * An {@link FieldVisitor} that generates Java fields in bytecode form. + * + * @author Eric Bruneton + */ +final class FieldWriter extends FieldVisitor { + + /** + * The class writer to which this field must be added. + */ + private final ClassWriter cw; + + /** + * Access flags of this field. + */ + private final int access; + + /** + * The index of the constant pool item that contains the name of this + * method. + */ + private final int name; + + /** + * The index of the constant pool item that contains the descriptor of this + * field. + */ + private final int desc; + + /** + * The index of the constant pool item that contains the signature of this + * field. + */ + private int signature; + + /** + * The index of the constant pool item that contains the constant value of + * this field. + */ + private int value; + + /** + * The runtime visible annotations of this field. May be null. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this field. May be null. + */ + private AnnotationWriter ianns; + + /** + * The runtime visible type annotations of this field. May be null. + */ + private AnnotationWriter tanns; + + /** + * The runtime invisible type annotations of this field. May be + * null. + */ + private AnnotationWriter itanns; + + /** + * The non standard attributes of this field. May be null. + */ + private Attribute attrs; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link FieldWriter}. + * + * @param cw + * the class writer to which this field must be added. + * @param access + * the field's access flags (see {@link Opcodes}). + * @param name + * the field's name. + * @param desc + * the field's descriptor (see {@link Type}). + * @param signature + * the field's signature. May be null. + * @param value + * the field's constant value. May be null. + */ + FieldWriter(final ClassWriter cw, final int access, final String name, + final String desc, final String signature, final Object value) { + super(Opcodes.ASM5); + if (cw.firstField == null) { + cw.firstField = this; + } else { + cw.lastField.fv = this; + } + cw.lastField = this; + this.cw = cw; + this.access = access; + this.name = cw.newUTF8(name); + this.desc = cw.newUTF8(desc); + if (ClassReader.SIGNATURES && signature != null) { + this.signature = cw.newUTF8(signature); + } + if (value != null) { + this.value = cw.newConstItem(value).index; + } + } + + // ------------------------------------------------------------------------ + // Implementation of the FieldVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + @Override + public AnnotationVisitor visitTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = tanns; + tanns = aw; + } else { + aw.next = itanns; + itanns = aw; + } + return aw; + } + + @Override + public void visitAttribute(final Attribute attr) { + attr.next = attrs; + attrs = attr; + } + + @Override + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Utility methods + // ------------------------------------------------------------------------ + + /** + * Returns the size of this field. + * + * @return the size of this field. + */ + int getSize() { + int size = 8; + if (value != 0) { + cw.newUTF8("ConstantValue"); + size += 8; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + cw.newUTF8("Synthetic"); + size += 6; + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + cw.newUTF8("Deprecated"); + size += 6; + } + if (ClassReader.SIGNATURES && signature != 0) { + cw.newUTF8("Signature"); + size += 8; + } + if (ClassReader.ANNOTATIONS && anns != null) { + cw.newUTF8("RuntimeVisibleAnnotations"); + size += 8 + anns.getSize(); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + cw.newUTF8("RuntimeInvisibleAnnotations"); + size += 8 + ianns.getSize(); + } + if (ClassReader.ANNOTATIONS && tanns != null) { + cw.newUTF8("RuntimeVisibleTypeAnnotations"); + size += 8 + tanns.getSize(); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + cw.newUTF8("RuntimeInvisibleTypeAnnotations"); + size += 8 + itanns.getSize(); + } + if (attrs != null) { + size += attrs.getSize(cw, null, 0, -1, -1); + } + return size; + } + + /** + * Puts the content of this field into the given byte vector. + * + * @param out + * where the content of this field must be put. + */ + void put(final ByteVector out) { + final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC; + int mask = Opcodes.ACC_DEPRECATED | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR); + out.putShort(access & ~mask).putShort(name).putShort(desc); + int attributeCount = 0; + if (value != 0) { + ++attributeCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + ++attributeCount; + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + } + if (ClassReader.SIGNATURES && signature != 0) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && tanns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && itanns != null) { + ++attributeCount; + } + if (attrs != null) { + attributeCount += attrs.getCount(); + } + out.putShort(attributeCount); + if (value != 0) { + out.putShort(cw.newUTF8("ConstantValue")); + out.putInt(2).putShort(value); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + out.putShort(cw.newUTF8("Synthetic")).putInt(0); + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(cw.newUTF8("Deprecated")).putInt(0); + } + if (ClassReader.SIGNATURES && signature != 0) { + out.putShort(cw.newUTF8("Signature")); + out.putInt(2).putShort(signature); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (ClassReader.ANNOTATIONS && tanns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations")); + tanns.put(out); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations")); + itanns.put(out); + } + if (attrs != null) { + attrs.put(cw, null, 0, -1, -1, out); + } + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/Frame.java b/blade-core/src/main/java/org/objectweb/asm/Frame.java new file mode 100644 index 000000000..1f6106f66 --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/Frame.java @@ -0,0 +1,1462 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * Information about the input and output stack map frames of a basic block. + * + * @author Eric Bruneton + */ +final class Frame { + + /* + * Frames are computed in a two steps process: during the visit of each + * instruction, the state of the frame at the end of current basic block is + * updated by simulating the action of the instruction on the previous state + * of this so called "output frame". In visitMaxs, a fix point algorithm is + * used to compute the "input frame" of each basic block, i.e. the stack map + * frame at the beginning of the basic block, starting from the input frame + * of the first basic block (which is computed from the method descriptor), + * and by using the previously computed output frames to compute the input + * state of the other blocks. + * + * All output and input frames are stored as arrays of integers. Reference + * and array types are represented by an index into a type table (which is + * not the same as the constant pool of the class, in order to avoid adding + * unnecessary constants in the pool - not all computed frames will end up + * being stored in the stack map table). This allows very fast type + * comparisons. + * + * Output stack map frames are computed relatively to the input frame of the + * basic block, which is not yet known when output frames are computed. It + * is therefore necessary to be able to represent abstract types such as + * "the type at position x in the input frame locals" or "the type at + * position x from the top of the input frame stack" or even "the type at + * position x in the input frame, with y more (or less) array dimensions". + * This explains the rather complicated type format used in output frames. + * + * This format is the following: DIM KIND VALUE (4, 4 and 24 bits). DIM is a + * signed number of array dimensions (from -8 to 7). KIND is either BASE, + * LOCAL or STACK. BASE is used for types that are not relative to the input + * frame. LOCAL is used for types that are relative to the input local + * variable types. STACK is used for types that are relative to the input + * stack types. VALUE depends on KIND. For LOCAL types, it is an index in + * the input local variable types. For STACK types, it is a position + * relatively to the top of input frame stack. For BASE types, it is either + * one of the constants defined below, or for OBJECT and UNINITIALIZED + * types, a tag and an index in the type table. + * + * Output frames can contain types of any kind and with a positive or + * negative dimension (and even unassigned types, represented by 0 - which + * does not correspond to any valid type value). Input frames can only + * contain BASE types of positive or null dimension. In all cases the type + * table contains only internal type names (array type descriptors are + * forbidden - dimensions must be represented through the DIM field). + * + * The LONG and DOUBLE types are always represented by using two slots (LONG + * + TOP or DOUBLE + TOP), for local variable types as well as in the + * operand stack. This is necessary to be able to simulate DUPx_y + * instructions, whose effect would be dependent on the actual type values + * if types were always represented by a single slot in the stack (and this + * is not possible, since actual type values are not always known - cf LOCAL + * and STACK type kinds). + */ + + /** + * Mask to get the dimension of a frame type. This dimension is a signed + * integer between -8 and 7. + */ + static final int DIM = 0xF0000000; + + /** + * Constant to be added to a type to get a type with one more dimension. + */ + static final int ARRAY_OF = 0x10000000; + + /** + * Constant to be added to a type to get a type with one less dimension. + */ + static final int ELEMENT_OF = 0xF0000000; + + /** + * Mask to get the kind of a frame type. + * + * @see #BASE + * @see #LOCAL + * @see #STACK + */ + static final int KIND = 0xF000000; + + /** + * Flag used for LOCAL and STACK types. Indicates that if this type happens + * to be a long or double type (during the computations of input frames), + * then it must be set to TOP because the second word of this value has been + * reused to store other data in the basic block. Hence the first word no + * longer stores a valid long or double value. + */ + static final int TOP_IF_LONG_OR_DOUBLE = 0x800000; + + /** + * Mask to get the value of a frame type. + */ + static final int VALUE = 0x7FFFFF; + + /** + * Mask to get the kind of base types. + */ + static final int BASE_KIND = 0xFF00000; + + /** + * Mask to get the value of base types. + */ + static final int BASE_VALUE = 0xFFFFF; + + /** + * Kind of the types that are not relative to an input stack map frame. + */ + static final int BASE = 0x1000000; + + /** + * Base kind of the base reference types. The BASE_VALUE of such types is an + * index into the type table. + */ + static final int OBJECT = BASE | 0x700000; + + /** + * Base kind of the uninitialized base types. The BASE_VALUE of such types + * in an index into the type table (the Item at that index contains both an + * instruction offset and an internal class name). + */ + static final int UNINITIALIZED = BASE | 0x800000; + + /** + * Kind of the types that are relative to the local variable types of an + * input stack map frame. The value of such types is a local variable index. + */ + private static final int LOCAL = 0x2000000; + + /** + * Kind of the the types that are relative to the stack of an input stack + * map frame. The value of such types is a position relatively to the top of + * this stack. + */ + private static final int STACK = 0x3000000; + + /** + * The TOP type. This is a BASE type. + */ + static final int TOP = BASE | 0; + + /** + * The BOOLEAN type. This is a BASE type mainly used for array types. + */ + static final int BOOLEAN = BASE | 9; + + /** + * The BYTE type. This is a BASE type mainly used for array types. + */ + static final int BYTE = BASE | 10; + + /** + * The CHAR type. This is a BASE type mainly used for array types. + */ + static final int CHAR = BASE | 11; + + /** + * The SHORT type. This is a BASE type mainly used for array types. + */ + static final int SHORT = BASE | 12; + + /** + * The INTEGER type. This is a BASE type. + */ + static final int INTEGER = BASE | 1; + + /** + * The FLOAT type. This is a BASE type. + */ + static final int FLOAT = BASE | 2; + + /** + * The DOUBLE type. This is a BASE type. + */ + static final int DOUBLE = BASE | 3; + + /** + * The LONG type. This is a BASE type. + */ + static final int LONG = BASE | 4; + + /** + * The NULL type. This is a BASE type. + */ + static final int NULL = BASE | 5; + + /** + * The UNINITIALIZED_THIS type. This is a BASE type. + */ + static final int UNINITIALIZED_THIS = BASE | 6; + + /** + * The stack size variation corresponding to each JVM instruction. This + * stack variation is equal to the size of the values produced by an + * instruction, minus the size of the values consumed by this instruction. + */ + static final int[] SIZE; + + /** + * Computes the stack size variation corresponding to each JVM instruction. + */ + static { + int i; + int[] b = new int[202]; + String s = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD" + + "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD" + + "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED" + + "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE"; + for (i = 0; i < b.length; ++i) { + b[i] = s.charAt(i) - 'E'; + } + SIZE = b; + + // code to generate the above string + // + // int NA = 0; // not applicable (unused opcode or variable size opcode) + // + // b = new int[] { + // 0, //NOP, // visitInsn + // 1, //ACONST_NULL, // - + // 1, //ICONST_M1, // - + // 1, //ICONST_0, // - + // 1, //ICONST_1, // - + // 1, //ICONST_2, // - + // 1, //ICONST_3, // - + // 1, //ICONST_4, // - + // 1, //ICONST_5, // - + // 2, //LCONST_0, // - + // 2, //LCONST_1, // - + // 1, //FCONST_0, // - + // 1, //FCONST_1, // - + // 1, //FCONST_2, // - + // 2, //DCONST_0, // - + // 2, //DCONST_1, // - + // 1, //BIPUSH, // visitIntInsn + // 1, //SIPUSH, // - + // 1, //LDC, // visitLdcInsn + // NA, //LDC_W, // - + // NA, //LDC2_W, // - + // 1, //ILOAD, // visitVarInsn + // 2, //LLOAD, // - + // 1, //FLOAD, // - + // 2, //DLOAD, // - + // 1, //ALOAD, // - + // NA, //ILOAD_0, // - + // NA, //ILOAD_1, // - + // NA, //ILOAD_2, // - + // NA, //ILOAD_3, // - + // NA, //LLOAD_0, // - + // NA, //LLOAD_1, // - + // NA, //LLOAD_2, // - + // NA, //LLOAD_3, // - + // NA, //FLOAD_0, // - + // NA, //FLOAD_1, // - + // NA, //FLOAD_2, // - + // NA, //FLOAD_3, // - + // NA, //DLOAD_0, // - + // NA, //DLOAD_1, // - + // NA, //DLOAD_2, // - + // NA, //DLOAD_3, // - + // NA, //ALOAD_0, // - + // NA, //ALOAD_1, // - + // NA, //ALOAD_2, // - + // NA, //ALOAD_3, // - + // -1, //IALOAD, // visitInsn + // 0, //LALOAD, // - + // -1, //FALOAD, // - + // 0, //DALOAD, // - + // -1, //AALOAD, // - + // -1, //BALOAD, // - + // -1, //CALOAD, // - + // -1, //SALOAD, // - + // -1, //ISTORE, // visitVarInsn + // -2, //LSTORE, // - + // -1, //FSTORE, // - + // -2, //DSTORE, // - + // -1, //ASTORE, // - + // NA, //ISTORE_0, // - + // NA, //ISTORE_1, // - + // NA, //ISTORE_2, // - + // NA, //ISTORE_3, // - + // NA, //LSTORE_0, // - + // NA, //LSTORE_1, // - + // NA, //LSTORE_2, // - + // NA, //LSTORE_3, // - + // NA, //FSTORE_0, // - + // NA, //FSTORE_1, // - + // NA, //FSTORE_2, // - + // NA, //FSTORE_3, // - + // NA, //DSTORE_0, // - + // NA, //DSTORE_1, // - + // NA, //DSTORE_2, // - + // NA, //DSTORE_3, // - + // NA, //ASTORE_0, // - + // NA, //ASTORE_1, // - + // NA, //ASTORE_2, // - + // NA, //ASTORE_3, // - + // -3, //IASTORE, // visitInsn + // -4, //LASTORE, // - + // -3, //FASTORE, // - + // -4, //DASTORE, // - + // -3, //AASTORE, // - + // -3, //BASTORE, // - + // -3, //CASTORE, // - + // -3, //SASTORE, // - + // -1, //POP, // - + // -2, //POP2, // - + // 1, //DUP, // - + // 1, //DUP_X1, // - + // 1, //DUP_X2, // - + // 2, //DUP2, // - + // 2, //DUP2_X1, // - + // 2, //DUP2_X2, // - + // 0, //SWAP, // - + // -1, //IADD, // - + // -2, //LADD, // - + // -1, //FADD, // - + // -2, //DADD, // - + // -1, //ISUB, // - + // -2, //LSUB, // - + // -1, //FSUB, // - + // -2, //DSUB, // - + // -1, //IMUL, // - + // -2, //LMUL, // - + // -1, //FMUL, // - + // -2, //DMUL, // - + // -1, //IDIV, // - + // -2, //LDIV, // - + // -1, //FDIV, // - + // -2, //DDIV, // - + // -1, //IREM, // - + // -2, //LREM, // - + // -1, //FREM, // - + // -2, //DREM, // - + // 0, //INEG, // - + // 0, //LNEG, // - + // 0, //FNEG, // - + // 0, //DNEG, // - + // -1, //ISHL, // - + // -1, //LSHL, // - + // -1, //ISHR, // - + // -1, //LSHR, // - + // -1, //IUSHR, // - + // -1, //LUSHR, // - + // -1, //IAND, // - + // -2, //LAND, // - + // -1, //IOR, // - + // -2, //LOR, // - + // -1, //IXOR, // - + // -2, //LXOR, // - + // 0, //IINC, // visitIincInsn + // 1, //I2L, // visitInsn + // 0, //I2F, // - + // 1, //I2D, // - + // -1, //L2I, // - + // -1, //L2F, // - + // 0, //L2D, // - + // 0, //F2I, // - + // 1, //F2L, // - + // 1, //F2D, // - + // -1, //D2I, // - + // 0, //D2L, // - + // -1, //D2F, // - + // 0, //I2B, // - + // 0, //I2C, // - + // 0, //I2S, // - + // -3, //LCMP, // - + // -1, //FCMPL, // - + // -1, //FCMPG, // - + // -3, //DCMPL, // - + // -3, //DCMPG, // - + // -1, //IFEQ, // visitJumpInsn + // -1, //IFNE, // - + // -1, //IFLT, // - + // -1, //IFGE, // - + // -1, //IFGT, // - + // -1, //IFLE, // - + // -2, //IF_ICMPEQ, // - + // -2, //IF_ICMPNE, // - + // -2, //IF_ICMPLT, // - + // -2, //IF_ICMPGE, // - + // -2, //IF_ICMPGT, // - + // -2, //IF_ICMPLE, // - + // -2, //IF_ACMPEQ, // - + // -2, //IF_ACMPNE, // - + // 0, //GOTO, // - + // 1, //JSR, // - + // 0, //RET, // visitVarInsn + // -1, //TABLESWITCH, // visiTableSwitchInsn + // -1, //LOOKUPSWITCH, // visitLookupSwitch + // -1, //IRETURN, // visitInsn + // -2, //LRETURN, // - + // -1, //FRETURN, // - + // -2, //DRETURN, // - + // -1, //ARETURN, // - + // 0, //RETURN, // - + // NA, //GETSTATIC, // visitFieldInsn + // NA, //PUTSTATIC, // - + // NA, //GETFIELD, // - + // NA, //PUTFIELD, // - + // NA, //INVOKEVIRTUAL, // visitMethodInsn + // NA, //INVOKESPECIAL, // - + // NA, //INVOKESTATIC, // - + // NA, //INVOKEINTERFACE, // - + // NA, //INVOKEDYNAMIC, // visitInvokeDynamicInsn + // 1, //NEW, // visitTypeInsn + // 0, //NEWARRAY, // visitIntInsn + // 0, //ANEWARRAY, // visitTypeInsn + // 0, //ARRAYLENGTH, // visitInsn + // NA, //ATHROW, // - + // 0, //CHECKCAST, // visitTypeInsn + // 0, //INSTANCEOF, // - + // -1, //MONITORENTER, // visitInsn + // -1, //MONITOREXIT, // - + // NA, //WIDE, // NOT VISITED + // NA, //MULTIANEWARRAY, // visitMultiANewArrayInsn + // -1, //IFNULL, // visitJumpInsn + // -1, //IFNONNULL, // - + // NA, //GOTO_W, // - + // NA, //JSR_W, // - + // }; + // for (i = 0; i < b.length; ++i) { + // System.err.print((char)('E' + b[i])); + // } + // System.err.println(); + } + + /** + * The label (i.e. basic block) to which these input and output stack map + * frames correspond. + */ + Label owner; + + /** + * The input stack map frame locals. + */ + int[] inputLocals; + + /** + * The input stack map frame stack. + */ + int[] inputStack; + + /** + * The output stack map frame locals. + */ + private int[] outputLocals; + + /** + * The output stack map frame stack. + */ + private int[] outputStack; + + /** + * Relative size of the output stack. The exact semantics of this field + * depends on the algorithm that is used. + * + * When only the maximum stack size is computed, this field is the size of + * the output stack relatively to the top of the input stack. + * + * When the stack map frames are completely computed, this field is the + * actual number of types in {@link #outputStack}. + */ + private int outputStackTop; + + /** + * Number of types that are initialized in the basic block. + * + * @see #initializations + */ + private int initializationCount; + + /** + * The types that are initialized in the basic block. A constructor + * invocation on an UNINITIALIZED or UNINITIALIZED_THIS type must replace + * every occurence of this type in the local variables and in the + * operand stack. This cannot be done during the first phase of the + * algorithm since, during this phase, the local variables and the operand + * stack are not completely computed. It is therefore necessary to store the + * types on which constructors are invoked in the basic block, in order to + * do this replacement during the second phase of the algorithm, where the + * frames are fully computed. Note that this array can contain types that + * are relative to input locals or to the input stack (see below for the + * description of the algorithm). + */ + private int[] initializations; + + /** + * Returns the output frame local variable type at the given index. + * + * @param local + * the index of the local that must be returned. + * @return the output frame local variable type at the given index. + */ + private int get(final int local) { + if (outputLocals == null || local >= outputLocals.length) { + // this local has never been assigned in this basic block, + // so it is still equal to its value in the input frame + return LOCAL | local; + } else { + int type = outputLocals[local]; + if (type == 0) { + // this local has never been assigned in this basic block, + // so it is still equal to its value in the input frame + type = outputLocals[local] = LOCAL | local; + } + return type; + } + } + + /** + * Sets the output frame local variable type at the given index. + * + * @param local + * the index of the local that must be set. + * @param type + * the value of the local that must be set. + */ + private void set(final int local, final int type) { + // creates and/or resizes the output local variables array if necessary + if (outputLocals == null) { + outputLocals = new int[10]; + } + int n = outputLocals.length; + if (local >= n) { + int[] t = new int[Math.max(local + 1, 2 * n)]; + System.arraycopy(outputLocals, 0, t, 0, n); + outputLocals = t; + } + // sets the local variable + outputLocals[local] = type; + } + + /** + * Pushes a new type onto the output frame stack. + * + * @param type + * the type that must be pushed. + */ + private void push(final int type) { + // creates and/or resizes the output stack array if necessary + if (outputStack == null) { + outputStack = new int[10]; + } + int n = outputStack.length; + if (outputStackTop >= n) { + int[] t = new int[Math.max(outputStackTop + 1, 2 * n)]; + System.arraycopy(outputStack, 0, t, 0, n); + outputStack = t; + } + // pushes the type on the output stack + outputStack[outputStackTop++] = type; + // updates the maximun height reached by the output stack, if needed + int top = owner.inputStackTop + outputStackTop; + if (top > owner.outputStackMax) { + owner.outputStackMax = top; + } + } + + /** + * Pushes a new type onto the output frame stack. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param desc + * the descriptor of the type to be pushed. Can also be a method + * descriptor (in this case this method pushes its return type + * onto the output frame stack). + */ + private void push(final ClassWriter cw, final String desc) { + int type = type(cw, desc); + if (type != 0) { + push(type); + if (type == LONG || type == DOUBLE) { + push(TOP); + } + } + } + + /** + * Returns the int encoding of the given type. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param desc + * a type descriptor. + * @return the int encoding of the given type. + */ + private static int type(final ClassWriter cw, final String desc) { + String t; + int index = desc.charAt(0) == '(' ? desc.indexOf(')') + 1 : 0; + switch (desc.charAt(index)) { + case 'V': + return 0; + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + return INTEGER; + case 'F': + return FLOAT; + case 'J': + return LONG; + case 'D': + return DOUBLE; + case 'L': + // stores the internal name, not the descriptor! + t = desc.substring(index + 1, desc.length() - 1); + return OBJECT | cw.addType(t); + // case '[': + default: + // extracts the dimensions and the element type + int data; + int dims = index + 1; + while (desc.charAt(dims) == '[') { + ++dims; + } + switch (desc.charAt(dims)) { + case 'Z': + data = BOOLEAN; + break; + case 'C': + data = CHAR; + break; + case 'B': + data = BYTE; + break; + case 'S': + data = SHORT; + break; + case 'I': + data = INTEGER; + break; + case 'F': + data = FLOAT; + break; + case 'J': + data = LONG; + break; + case 'D': + data = DOUBLE; + break; + // case 'L': + default: + // stores the internal name, not the descriptor + t = desc.substring(dims + 1, desc.length() - 1); + data = OBJECT | cw.addType(t); + } + return (dims - index) << 28 | data; + } + } + + /** + * Pops a type from the output frame stack and returns its value. + * + * @return the type that has been popped from the output frame stack. + */ + private int pop() { + if (outputStackTop > 0) { + return outputStack[--outputStackTop]; + } else { + // if the output frame stack is empty, pops from the input stack + return STACK | -(--owner.inputStackTop); + } + } + + /** + * Pops the given number of types from the output frame stack. + * + * @param elements + * the number of types that must be popped. + */ + private void pop(final int elements) { + if (outputStackTop >= elements) { + outputStackTop -= elements; + } else { + // if the number of elements to be popped is greater than the number + // of elements in the output stack, clear it, and pops the remaining + // elements from the input stack. + owner.inputStackTop -= elements - outputStackTop; + outputStackTop = 0; + } + } + + /** + * Pops a type from the output frame stack. + * + * @param desc + * the descriptor of the type to be popped. Can also be a method + * descriptor (in this case this method pops the types + * corresponding to the method arguments). + */ + private void pop(final String desc) { + char c = desc.charAt(0); + if (c == '(') { + pop((Type.getArgumentsAndReturnSizes(desc) >> 2) - 1); + } else if (c == 'J' || c == 'D') { + pop(2); + } else { + pop(1); + } + } + + /** + * Adds a new type to the list of types on which a constructor is invoked in + * the basic block. + * + * @param var + * a type on a which a constructor is invoked. + */ + private void init(final int var) { + // creates and/or resizes the initializations array if necessary + if (initializations == null) { + initializations = new int[2]; + } + int n = initializations.length; + if (initializationCount >= n) { + int[] t = new int[Math.max(initializationCount + 1, 2 * n)]; + System.arraycopy(initializations, 0, t, 0, n); + initializations = t; + } + // stores the type to be initialized + initializations[initializationCount++] = var; + } + + /** + * Replaces the given type with the appropriate type if it is one of the + * types on which a constructor is invoked in the basic block. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param t + * a type + * @return t or, if t is one of the types on which a constructor is invoked + * in the basic block, the type corresponding to this constructor. + */ + private int init(final ClassWriter cw, final int t) { + int s; + if (t == UNINITIALIZED_THIS) { + s = OBJECT | cw.addType(cw.thisName); + } else if ((t & (DIM | BASE_KIND)) == UNINITIALIZED) { + String type = cw.typeTable[t & BASE_VALUE].strVal1; + s = OBJECT | cw.addType(type); + } else { + return t; + } + for (int j = 0; j < initializationCount; ++j) { + int u = initializations[j]; + int dim = u & DIM; + int kind = u & KIND; + if (kind == LOCAL) { + u = dim + inputLocals[u & VALUE]; + } else if (kind == STACK) { + u = dim + inputStack[inputStack.length - (u & VALUE)]; + } + if (t == u) { + return s; + } + } + return t; + } + + /** + * Initializes the input frame of the first basic block from the method + * descriptor. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param access + * the access flags of the method to which this label belongs. + * @param args + * the formal parameter types of this method. + * @param maxLocals + * the maximum number of local variables of this method. + */ + void initInputFrame(final ClassWriter cw, final int access, + final Type[] args, final int maxLocals) { + inputLocals = new int[maxLocals]; + inputStack = new int[0]; + int i = 0; + if ((access & Opcodes.ACC_STATIC) == 0) { + if ((access & MethodWriter.ACC_CONSTRUCTOR) == 0) { + inputLocals[i++] = OBJECT | cw.addType(cw.thisName); + } else { + inputLocals[i++] = UNINITIALIZED_THIS; + } + } + for (int j = 0; j < args.length; ++j) { + int t = type(cw, args[j].getDescriptor()); + inputLocals[i++] = t; + if (t == LONG || t == DOUBLE) { + inputLocals[i++] = TOP; + } + } + while (i < maxLocals) { + inputLocals[i++] = TOP; + } + } + + /** + * Simulates the action of the given instruction on the output stack frame. + * + * @param opcode + * the opcode of the instruction. + * @param arg + * the operand of the instruction, if any. + * @param cw + * the class writer to which this label belongs. + * @param item + * the operand of the instructions, if any. + */ + void execute(final int opcode, final int arg, final ClassWriter cw, + final Item item) { + int t1, t2, t3, t4; + switch (opcode) { + case Opcodes.NOP: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.GOTO: + case Opcodes.RETURN: + break; + case Opcodes.ACONST_NULL: + push(NULL); + break; + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.BIPUSH: + case Opcodes.SIPUSH: + case Opcodes.ILOAD: + push(INTEGER); + break; + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.LLOAD: + push(LONG); + push(TOP); + break; + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.FLOAD: + push(FLOAT); + break; + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.DLOAD: + push(DOUBLE); + push(TOP); + break; + case Opcodes.LDC: + switch (item.type) { + case ClassWriter.INT: + push(INTEGER); + break; + case ClassWriter.LONG: + push(LONG); + push(TOP); + break; + case ClassWriter.FLOAT: + push(FLOAT); + break; + case ClassWriter.DOUBLE: + push(DOUBLE); + push(TOP); + break; + case ClassWriter.CLASS: + push(OBJECT | cw.addType("java/lang/Class")); + break; + case ClassWriter.STR: + push(OBJECT | cw.addType("java/lang/String")); + break; + case ClassWriter.MTYPE: + push(OBJECT | cw.addType("java/lang/invoke/MethodType")); + break; + // case ClassWriter.HANDLE_BASE + [1..9]: + default: + push(OBJECT | cw.addType("java/lang/invoke/MethodHandle")); + } + break; + case Opcodes.ALOAD: + push(get(arg)); + break; + case Opcodes.IALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + pop(2); + push(INTEGER); + break; + case Opcodes.LALOAD: + case Opcodes.D2L: + pop(2); + push(LONG); + push(TOP); + break; + case Opcodes.FALOAD: + pop(2); + push(FLOAT); + break; + case Opcodes.DALOAD: + case Opcodes.L2D: + pop(2); + push(DOUBLE); + push(TOP); + break; + case Opcodes.AALOAD: + pop(1); + t1 = pop(); + push(ELEMENT_OF + t1); + break; + case Opcodes.ISTORE: + case Opcodes.FSTORE: + case Opcodes.ASTORE: + t1 = pop(); + set(arg, t1); + if (arg > 0) { + t2 = get(arg - 1); + // if t2 is of kind STACK or LOCAL we cannot know its size! + if (t2 == LONG || t2 == DOUBLE) { + set(arg - 1, TOP); + } else if ((t2 & KIND) != BASE) { + set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE); + } + } + break; + case Opcodes.LSTORE: + case Opcodes.DSTORE: + pop(1); + t1 = pop(); + set(arg, t1); + set(arg + 1, TOP); + if (arg > 0) { + t2 = get(arg - 1); + // if t2 is of kind STACK or LOCAL we cannot know its size! + if (t2 == LONG || t2 == DOUBLE) { + set(arg - 1, TOP); + } else if ((t2 & KIND) != BASE) { + set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE); + } + } + break; + case Opcodes.IASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.FASTORE: + case Opcodes.AASTORE: + pop(3); + break; + case Opcodes.LASTORE: + case Opcodes.DASTORE: + pop(4); + break; + case Opcodes.POP: + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IRETURN: + case Opcodes.FRETURN: + case Opcodes.ARETURN: + case Opcodes.TABLESWITCH: + case Opcodes.LOOKUPSWITCH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + pop(1); + break; + case Opcodes.POP2: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.LRETURN: + case Opcodes.DRETURN: + pop(2); + break; + case Opcodes.DUP: + t1 = pop(); + push(t1); + push(t1); + break; + case Opcodes.DUP_X1: + t1 = pop(); + t2 = pop(); + push(t1); + push(t2); + push(t1); + break; + case Opcodes.DUP_X2: + t1 = pop(); + t2 = pop(); + t3 = pop(); + push(t1); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.DUP2: + t1 = pop(); + t2 = pop(); + push(t2); + push(t1); + push(t2); + push(t1); + break; + case Opcodes.DUP2_X1: + t1 = pop(); + t2 = pop(); + t3 = pop(); + push(t2); + push(t1); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.DUP2_X2: + t1 = pop(); + t2 = pop(); + t3 = pop(); + t4 = pop(); + push(t2); + push(t1); + push(t4); + push(t3); + push(t2); + push(t1); + break; + case Opcodes.SWAP: + t1 = pop(); + t2 = pop(); + push(t1); + push(t2); + break; + case Opcodes.IADD: + case Opcodes.ISUB: + case Opcodes.IMUL: + case Opcodes.IDIV: + case Opcodes.IREM: + case Opcodes.IAND: + case Opcodes.IOR: + case Opcodes.IXOR: + case Opcodes.ISHL: + case Opcodes.ISHR: + case Opcodes.IUSHR: + case Opcodes.L2I: + case Opcodes.D2I: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + pop(2); + push(INTEGER); + break; + case Opcodes.LADD: + case Opcodes.LSUB: + case Opcodes.LMUL: + case Opcodes.LDIV: + case Opcodes.LREM: + case Opcodes.LAND: + case Opcodes.LOR: + case Opcodes.LXOR: + pop(4); + push(LONG); + push(TOP); + break; + case Opcodes.FADD: + case Opcodes.FSUB: + case Opcodes.FMUL: + case Opcodes.FDIV: + case Opcodes.FREM: + case Opcodes.L2F: + case Opcodes.D2F: + pop(2); + push(FLOAT); + break; + case Opcodes.DADD: + case Opcodes.DSUB: + case Opcodes.DMUL: + case Opcodes.DDIV: + case Opcodes.DREM: + pop(4); + push(DOUBLE); + push(TOP); + break; + case Opcodes.LSHL: + case Opcodes.LSHR: + case Opcodes.LUSHR: + pop(3); + push(LONG); + push(TOP); + break; + case Opcodes.IINC: + set(arg, INTEGER); + break; + case Opcodes.I2L: + case Opcodes.F2L: + pop(1); + push(LONG); + push(TOP); + break; + case Opcodes.I2F: + pop(1); + push(FLOAT); + break; + case Opcodes.I2D: + case Opcodes.F2D: + pop(1); + push(DOUBLE); + push(TOP); + break; + case Opcodes.F2I: + case Opcodes.ARRAYLENGTH: + case Opcodes.INSTANCEOF: + pop(1); + push(INTEGER); + break; + case Opcodes.LCMP: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + pop(4); + push(INTEGER); + break; + case Opcodes.JSR: + case Opcodes.RET: + throw new RuntimeException( + "JSR/RET are not supported with computeFrames option"); + case Opcodes.GETSTATIC: + push(cw, item.strVal3); + break; + case Opcodes.PUTSTATIC: + pop(item.strVal3); + break; + case Opcodes.GETFIELD: + pop(1); + push(cw, item.strVal3); + break; + case Opcodes.PUTFIELD: + pop(item.strVal3); + pop(); + break; + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: + pop(item.strVal3); + if (opcode != Opcodes.INVOKESTATIC) { + t1 = pop(); + if (opcode == Opcodes.INVOKESPECIAL + && item.strVal2.charAt(0) == '<') { + init(t1); + } + } + push(cw, item.strVal3); + break; + case Opcodes.INVOKEDYNAMIC: + pop(item.strVal2); + push(cw, item.strVal2); + break; + case Opcodes.NEW: + push(UNINITIALIZED | cw.addUninitializedType(item.strVal1, arg)); + break; + case Opcodes.NEWARRAY: + pop(); + switch (arg) { + case Opcodes.T_BOOLEAN: + push(ARRAY_OF | BOOLEAN); + break; + case Opcodes.T_CHAR: + push(ARRAY_OF | CHAR); + break; + case Opcodes.T_BYTE: + push(ARRAY_OF | BYTE); + break; + case Opcodes.T_SHORT: + push(ARRAY_OF | SHORT); + break; + case Opcodes.T_INT: + push(ARRAY_OF | INTEGER); + break; + case Opcodes.T_FLOAT: + push(ARRAY_OF | FLOAT); + break; + case Opcodes.T_DOUBLE: + push(ARRAY_OF | DOUBLE); + break; + // case Opcodes.T_LONG: + default: + push(ARRAY_OF | LONG); + break; + } + break; + case Opcodes.ANEWARRAY: + String s = item.strVal1; + pop(); + if (s.charAt(0) == '[') { + push(cw, '[' + s); + } else { + push(ARRAY_OF | OBJECT | cw.addType(s)); + } + break; + case Opcodes.CHECKCAST: + s = item.strVal1; + pop(); + if (s.charAt(0) == '[') { + push(cw, s); + } else { + push(OBJECT | cw.addType(s)); + } + break; + // case Opcodes.MULTIANEWARRAY: + default: + pop(arg); + push(cw, item.strVal1); + break; + } + } + + /** + * Merges the input frame of the given basic block with the input and output + * frames of this basic block. Returns true if the input frame of + * the given label has been changed by this operation. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param frame + * the basic block whose input frame must be updated. + * @param edge + * the kind of the {@link Edge} between this label and 'label'. + * See {@link Edge#info}. + * @return true if the input frame of the given label has been + * changed by this operation. + */ + boolean merge(final ClassWriter cw, final Frame frame, final int edge) { + boolean changed = false; + int i, s, dim, kind, t; + + int nLocal = inputLocals.length; + int nStack = inputStack.length; + if (frame.inputLocals == null) { + frame.inputLocals = new int[nLocal]; + changed = true; + } + + for (i = 0; i < nLocal; ++i) { + if (outputLocals != null && i < outputLocals.length) { + s = outputLocals[i]; + if (s == 0) { + t = inputLocals[i]; + } else { + dim = s & DIM; + kind = s & KIND; + if (kind == BASE) { + t = s; + } else { + if (kind == LOCAL) { + t = dim + inputLocals[s & VALUE]; + } else { + t = dim + inputStack[nStack - (s & VALUE)]; + } + if ((s & TOP_IF_LONG_OR_DOUBLE) != 0 + && (t == LONG || t == DOUBLE)) { + t = TOP; + } + } + } + } else { + t = inputLocals[i]; + } + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputLocals, i); + } + + if (edge > 0) { + for (i = 0; i < nLocal; ++i) { + t = inputLocals[i]; + changed |= merge(cw, t, frame.inputLocals, i); + } + if (frame.inputStack == null) { + frame.inputStack = new int[1]; + changed = true; + } + changed |= merge(cw, edge, frame.inputStack, 0); + return changed; + } + + int nInputStack = inputStack.length + owner.inputStackTop; + if (frame.inputStack == null) { + frame.inputStack = new int[nInputStack + outputStackTop]; + changed = true; + } + + for (i = 0; i < nInputStack; ++i) { + t = inputStack[i]; + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputStack, i); + } + for (i = 0; i < outputStackTop; ++i) { + s = outputStack[i]; + dim = s & DIM; + kind = s & KIND; + if (kind == BASE) { + t = s; + } else { + if (kind == LOCAL) { + t = dim + inputLocals[s & VALUE]; + } else { + t = dim + inputStack[nStack - (s & VALUE)]; + } + if ((s & TOP_IF_LONG_OR_DOUBLE) != 0 + && (t == LONG || t == DOUBLE)) { + t = TOP; + } + } + if (initializations != null) { + t = init(cw, t); + } + changed |= merge(cw, t, frame.inputStack, nInputStack + i); + } + return changed; + } + + /** + * Merges the type at the given index in the given type array with the given + * type. Returns true if the type array has been modified by this + * operation. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param t + * the type with which the type array element must be merged. + * @param types + * an array of types. + * @param index + * the index of the type that must be merged in 'types'. + * @return true if the type array has been modified by this + * operation. + */ + private static boolean merge(final ClassWriter cw, int t, + final int[] types, final int index) { + int u = types[index]; + if (u == t) { + // if the types are equal, merge(u,t)=u, so there is no change + return false; + } + if ((t & ~DIM) == NULL) { + if (u == NULL) { + return false; + } + t = NULL; + } + if (u == 0) { + // if types[index] has never been assigned, merge(u,t)=t + types[index] = t; + return true; + } + int v; + if ((u & BASE_KIND) == OBJECT || (u & DIM) != 0) { + // if u is a reference type of any dimension + if (t == NULL) { + // if t is the NULL type, merge(u,t)=u, so there is no change + return false; + } else if ((t & (DIM | BASE_KIND)) == (u & (DIM | BASE_KIND))) { + // if t and u have the same dimension and same base kind + if ((u & BASE_KIND) == OBJECT) { + // if t is also a reference type, and if u and t have the + // same dimension merge(u,t) = dim(t) | common parent of the + // element types of u and t + v = (t & DIM) | OBJECT + | cw.getMergedType(t & BASE_VALUE, u & BASE_VALUE); + } else { + // if u and t are array types, but not with the same element + // type, merge(u,t) = dim(u) - 1 | java/lang/Object + int vdim = ELEMENT_OF + (u & DIM); + v = vdim | OBJECT | cw.addType("java/lang/Object"); + } + } else if ((t & BASE_KIND) == OBJECT || (t & DIM) != 0) { + // if t is any other reference or array type, the merged type + // is min(udim, tdim) | java/lang/Object, where udim is the + // array dimension of u, minus 1 if u is an array type with a + // primitive element type (and similarly for tdim). + int tdim = (((t & DIM) == 0 || (t & BASE_KIND) == OBJECT) ? 0 + : ELEMENT_OF) + (t & DIM); + int udim = (((u & DIM) == 0 || (u & BASE_KIND) == OBJECT) ? 0 + : ELEMENT_OF) + (u & DIM); + v = Math.min(tdim, udim) | OBJECT + | cw.addType("java/lang/Object"); + } else { + // if t is any other type, merge(u,t)=TOP + v = TOP; + } + } else if (u == NULL) { + // if u is the NULL type, merge(u,t)=t, + // or TOP if t is not a reference type + v = (t & BASE_KIND) == OBJECT || (t & DIM) != 0 ? t : TOP; + } else { + // if u is any other type, merge(u,t)=TOP whatever t + v = TOP; + } + if (u != v) { + types[index] = v; + return true; + } + return false; + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/Handle.java b/blade-core/src/main/java/org/objectweb/asm/Handle.java new file mode 100644 index 000000000..ddc0368d6 --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/Handle.java @@ -0,0 +1,222 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.objectweb.asm; + +/** + * A reference to a field or a method. + * + * @author Remi Forax + * @author Eric Bruneton + */ +public final class Handle { + + /** + * The kind of field or method designated by this Handle. Should be + * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + */ + final int tag; + + /** + * The internal name of the class that owns the field or method designated + * by this handle. + */ + final String owner; + + /** + * The name of the field or method designated by this handle. + */ + final String name; + + /** + * The descriptor of the field or method designated by this handle. + */ + final String desc; + + + /** + * Indicate if the owner is an interface or not. + */ + final boolean itf; + + /** + * Constructs a new field or method handle. + * + * @param tag + * the kind of field or method designated by this Handle. Must be + * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the class that owns the field or method + * designated by this handle. + * @param name + * the name of the field or method designated by this handle. + * @param desc + * the descriptor of the field or method designated by this + * handle. + * + * @deprecated this constructor has been superseded + * by {@link #Handle(int, String, String, String, boolean)}. + */ + @Deprecated + public Handle(int tag, String owner, String name, String desc) { + this(tag, owner, name, desc, tag == Opcodes.H_INVOKEINTERFACE); + } + + /** + * Constructs a new field or method handle. + * + * @param tag + * the kind of field or method designated by this Handle. Must be + * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the class that owns the field or method + * designated by this handle. + * @param name + * the name of the field or method designated by this handle. + * @param desc + * the descriptor of the field or method designated by this + * handle. + * @param itf + * true if the owner is an interface. + */ + public Handle(int tag, String owner, String name, String desc, boolean itf) { + this.tag = tag; + this.owner = owner; + this.name = name; + this.desc = desc; + this.itf = itf; + } + + /** + * Returns the kind of field or method designated by this handle. + * + * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + */ + public int getTag() { + return tag; + } + + /** + * Returns the internal name of the class that owns the field or method + * designated by this handle. + * + * @return the internal name of the class that owns the field or method + * designated by this handle. + */ + public String getOwner() { + return owner; + } + + /** + * Returns the name of the field or method designated by this handle. + * + * @return the name of the field or method designated by this handle. + */ + public String getName() { + return name; + } + + /** + * Returns the descriptor of the field or method designated by this handle. + * + * @return the descriptor of the field or method designated by this handle. + */ + public String getDesc() { + return desc; + } + + /** + * Returns true if the owner of the field or method designated + * by this handle is an interface. + * + * @return true if the owner of the field or method designated + * by this handle is an interface. + */ + public boolean isInterface() { + return itf; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Handle)) { + return false; + } + Handle h = (Handle) obj; + return tag == h.tag && itf == h.itf && owner.equals(h.owner) + && name.equals(h.name) && desc.equals(h.desc); + } + + @Override + public int hashCode() { + return tag + (itf? 64: 0) + owner.hashCode() * name.hashCode() * desc.hashCode(); + } + + /** + * Returns the textual representation of this handle. The textual + * representation is: + * + *
+     * for a reference to a class:
+     * owner '.' name desc ' ' '(' tag ')'
+     * for a reference to an interface:
+     * owner '.' name desc ' ' '(' tag ' ' itf ')'
+     * 
+ * + * . As this format is unambiguous, it can be parsed if necessary. + */ + @Override + public String toString() { + return owner + '.' + name + desc + " (" + tag + (itf? " itf": "") + ')'; + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/Handler.java b/blade-core/src/main/java/org/objectweb/asm/Handler.java new file mode 100644 index 000000000..b24591d81 --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/Handler.java @@ -0,0 +1,121 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * Information about an exception handler block. + * + * @author Eric Bruneton + */ +class Handler { + + /** + * Beginning of the exception handler's scope (inclusive). + */ + Label start; + + /** + * End of the exception handler's scope (exclusive). + */ + Label end; + + /** + * Beginning of the exception handler's code. + */ + Label handler; + + /** + * Internal name of the type of exceptions handled by this handler, or + * null to catch any exceptions. + */ + String desc; + + /** + * Constant pool index of the internal name of the type of exceptions + * handled by this handler, or 0 to catch any exceptions. + */ + int type; + + /** + * Next exception handler block info. + */ + Handler next; + + /** + * Removes the range between start and end from the given exception + * handlers. + * + * @param h + * an exception handler list. + * @param start + * the start of the range to be removed. + * @param end + * the end of the range to be removed. Maybe null. + * @return the exception handler list with the start-end range removed. + */ + static Handler remove(Handler h, Label start, Label end) { + if (h == null) { + return null; + } else { + h.next = remove(h.next, start, end); + } + int hstart = h.start.position; + int hend = h.end.position; + int s = start.position; + int e = end == null ? Integer.MAX_VALUE : end.position; + // if [hstart,hend[ and [s,e[ intervals intersect... + if (s < hend && e > hstart) { + if (s <= hstart) { + if (e >= hend) { + // [hstart,hend[ fully included in [s,e[, h removed + h = h.next; + } else { + // [hstart,hend[ minus [s,e[ = [e,hend[ + h.start = end; + } + } else if (e >= hend) { + // [hstart,hend[ minus [s,e[ = [hstart,s[ + h.end = start; + } else { + // [hstart,hend[ minus [s,e[ = [hstart,s[ + [e,hend[ + Handler g = new Handler(); + g.start = end; + g.end = h.end; + g.handler = h.handler; + g.desc = h.desc; + g.type = h.type; + g.next = h.next; + h.end = start; + h.next = g; + } + } + return h; + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/Item.java b/blade-core/src/main/java/org/objectweb/asm/Item.java new file mode 100644 index 000000000..917524dde --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/Item.java @@ -0,0 +1,313 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * A constant pool item. Constant pool items can be created with the 'newXXX' + * methods in the {@link ClassWriter} class. + * + * @author Eric Bruneton + */ +final class Item { + + /** + * Index of this item in the constant pool. + */ + int index; + + /** + * Type of this constant pool item. A single class is used to represent all + * constant pool item types, in order to minimize the bytecode size of this + * package. The value of this field is one of {@link ClassWriter#INT}, + * {@link ClassWriter#LONG}, {@link ClassWriter#FLOAT}, + * {@link ClassWriter#DOUBLE}, {@link ClassWriter#UTF8}, + * {@link ClassWriter#STR}, {@link ClassWriter#CLASS}, + * {@link ClassWriter#NAME_TYPE}, {@link ClassWriter#FIELD}, + * {@link ClassWriter#METH}, {@link ClassWriter#IMETH}, + * {@link ClassWriter#MTYPE}, {@link ClassWriter#INDY}. + * + * MethodHandle constant 9 variations are stored using a range of 9 values + * from {@link ClassWriter#HANDLE_BASE} + 1 to + * {@link ClassWriter#HANDLE_BASE} + 9. + * + * Special Item types are used for Items that are stored in the ClassWriter + * {@link ClassWriter#typeTable}, instead of the constant pool, in order to + * avoid clashes with normal constant pool items in the ClassWriter constant + * pool's hash table. These special item types are + * {@link ClassWriter#TYPE_NORMAL}, {@link ClassWriter#TYPE_UNINIT} and + * {@link ClassWriter#TYPE_MERGED}. + */ + int type; + + /** + * Value of this item, for an integer item. + */ + int intVal; + + /** + * Value of this item, for a long item. + */ + long longVal; + + /** + * First part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal1; + + /** + * Second part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal2; + + /** + * Third part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal3; + + /** + * The hash code value of this constant pool item. + */ + int hashCode; + + /** + * Link to another constant pool item, used for collision lists in the + * constant pool's hash table. + */ + Item next; + + /** + * Constructs an uninitialized {@link Item}. + */ + Item() { + } + + /** + * Constructs an uninitialized {@link Item} for constant pool element at + * given position. + * + * @param index + * index of the item to be constructed. + */ + Item(final int index) { + this.index = index; + } + + /** + * Constructs a copy of the given item. + * + * @param index + * index of the item to be constructed. + * @param i + * the item that must be copied into the item to be constructed. + */ + Item(final int index, final Item i) { + this.index = index; + type = i.type; + intVal = i.intVal; + longVal = i.longVal; + strVal1 = i.strVal1; + strVal2 = i.strVal2; + strVal3 = i.strVal3; + hashCode = i.hashCode; + } + + /** + * Sets this item to an integer item. + * + * @param intVal + * the value of this item. + */ + void set(final int intVal) { + this.type = ClassWriter.INT; + this.intVal = intVal; + this.hashCode = 0x7FFFFFFF & (type + intVal); + } + + /** + * Sets this item to a long item. + * + * @param longVal + * the value of this item. + */ + void set(final long longVal) { + this.type = ClassWriter.LONG; + this.longVal = longVal; + this.hashCode = 0x7FFFFFFF & (type + (int) longVal); + } + + /** + * Sets this item to a float item. + * + * @param floatVal + * the value of this item. + */ + void set(final float floatVal) { + this.type = ClassWriter.FLOAT; + this.intVal = Float.floatToRawIntBits(floatVal); + this.hashCode = 0x7FFFFFFF & (type + (int) floatVal); + } + + /** + * Sets this item to a double item. + * + * @param doubleVal + * the value of this item. + */ + void set(final double doubleVal) { + this.type = ClassWriter.DOUBLE; + this.longVal = Double.doubleToRawLongBits(doubleVal); + this.hashCode = 0x7FFFFFFF & (type + (int) doubleVal); + } + + /** + * Sets this item to an item that do not hold a primitive value. + * + * @param type + * the type of this item. + * @param strVal1 + * first part of the value of this item. + * @param strVal2 + * second part of the value of this item. + * @param strVal3 + * third part of the value of this item. + */ + @SuppressWarnings("fallthrough") + void set(final int type, final String strVal1, final String strVal2, + final String strVal3) { + this.type = type; + this.strVal1 = strVal1; + this.strVal2 = strVal2; + this.strVal3 = strVal3; + switch (type) { + case ClassWriter.CLASS: + this.intVal = 0; // intVal of a class must be zero, see visitInnerClass + case ClassWriter.UTF8: + case ClassWriter.STR: + case ClassWriter.MTYPE: + case ClassWriter.TYPE_NORMAL: + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()); + return; + case ClassWriter.NAME_TYPE: { + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode() + * strVal2.hashCode()); + return; + } + // ClassWriter.FIELD: + // ClassWriter.METH: + // ClassWriter.IMETH: + // ClassWriter.HANDLE_BASE + 1..9 + default: + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode() + * strVal2.hashCode() * strVal3.hashCode()); + } + } + + /** + * Sets the item to an InvokeDynamic item. + * + * @param name + * invokedynamic's name. + * @param desc + * invokedynamic's desc. + * @param bsmIndex + * zero based index into the class attribute BootrapMethods. + */ + void set(String name, String desc, int bsmIndex) { + this.type = ClassWriter.INDY; + this.longVal = bsmIndex; + this.strVal1 = name; + this.strVal2 = desc; + this.hashCode = 0x7FFFFFFF & (ClassWriter.INDY + bsmIndex + * strVal1.hashCode() * strVal2.hashCode()); + } + + /** + * Sets the item to a BootstrapMethod item. + * + * @param position + * position in byte in the class attribute BootrapMethods. + * @param hashCode + * hashcode of the item. This hashcode is processed from the + * hashcode of the bootstrap method and the hashcode of all + * bootstrap arguments. + */ + void set(int position, int hashCode) { + this.type = ClassWriter.BSM; + this.intVal = position; + this.hashCode = hashCode; + } + + /** + * Indicates if the given item is equal to this one. This method assumes + * that the two items have the same {@link #type}. + * + * @param i + * the item to be compared to this one. Both items must have the + * same {@link #type}. + * @return true if the given item if equal to this one, + * false otherwise. + */ + boolean isEqualTo(final Item i) { + switch (type) { + case ClassWriter.UTF8: + case ClassWriter.STR: + case ClassWriter.CLASS: + case ClassWriter.MTYPE: + case ClassWriter.TYPE_NORMAL: + return i.strVal1.equals(strVal1); + case ClassWriter.TYPE_MERGED: + case ClassWriter.LONG: + case ClassWriter.DOUBLE: + return i.longVal == longVal; + case ClassWriter.INT: + case ClassWriter.FLOAT: + return i.intVal == intVal; + case ClassWriter.TYPE_UNINIT: + return i.intVal == intVal && i.strVal1.equals(strVal1); + case ClassWriter.NAME_TYPE: + return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2); + case ClassWriter.INDY: { + return i.longVal == longVal && i.strVal1.equals(strVal1) + && i.strVal2.equals(strVal2); + } + // case ClassWriter.FIELD: + // case ClassWriter.METH: + // case ClassWriter.IMETH: + // case ClassWriter.HANDLE_BASE + 1..9 + default: + return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2) + && i.strVal3.equals(strVal3); + } + } + +} diff --git a/blade-core/src/main/java/org/objectweb/asm/Label.java b/blade-core/src/main/java/org/objectweb/asm/Label.java new file mode 100644 index 000000000..6bca6fbe8 --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/Label.java @@ -0,0 +1,565 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * A label represents a position in the bytecode of a method. Labels are used + * for jump, goto, and switch instructions, and for try catch blocks. A label + * designates the instruction that is just after. Note however that there + * can be other elements between a label and the instruction it designates (such + * as other labels, stack map frames, line numbers, etc.). + * + * @author Eric Bruneton + */ +public class Label { + + /** + * Indicates if this label is only used for debug attributes. Such a label + * is not the start of a basic block, the target of a jump instruction, or + * an exception handler. It can be safely ignored in control flow graph + * analysis algorithms (for optimization purposes). + */ + static final int DEBUG = 1; + + /** + * Indicates if the position of this label is known. + */ + static final int RESOLVED = 2; + + /** + * Indicates if this label has been updated, after instruction resizing. + */ + static final int RESIZED = 4; + + /** + * Indicates if this basic block has been pushed in the basic block stack. + * See {@link MethodWriter#visitMaxs visitMaxs}. + */ + static final int PUSHED = 8; + + /** + * Indicates if this label is the target of a jump instruction, or the start + * of an exception handler. + */ + static final int TARGET = 16; + + /** + * Indicates if a stack map frame must be stored for this label. + */ + static final int STORE = 32; + + /** + * Indicates if this label corresponds to a reachable basic block. + */ + static final int REACHABLE = 64; + + /** + * Indicates if this basic block ends with a JSR instruction. + */ + static final int JSR = 128; + + /** + * Indicates if this basic block ends with a RET instruction. + */ + static final int RET = 256; + + /** + * Indicates if this basic block is the start of a subroutine. + */ + static final int SUBROUTINE = 512; + + /** + * Indicates if this subroutine basic block has been visited by a + * visitSubroutine(null, ...) call. + */ + static final int VISITED = 1024; + + /** + * Indicates if this subroutine basic block has been visited by a + * visitSubroutine(!null, ...) call. + */ + static final int VISITED2 = 2048; + + /** + * Field used to associate user information to a label. Warning: this field + * is used by the ASM tree package. In order to use it with the ASM tree + * package you must override the + * {@link org.objectweb.asm.tree.MethodNode#getLabelNode} method. + */ + public Object info; + + /** + * Flags that indicate the status of this label. + * + * @see #DEBUG + * @see #RESOLVED + * @see #RESIZED + * @see #PUSHED + * @see #TARGET + * @see #STORE + * @see #REACHABLE + * @see #JSR + * @see #RET + */ + int status; + + /** + * The line number corresponding to this label, if known. If there are + * several lines, each line is stored in a separate label, all linked via + * their next field (these links are created in ClassReader and removed just + * before visitLabel is called, so that this does not impact the rest of the + * code). + */ + int line; + + /** + * The position of this label in the code, if known. + */ + int position; + + /** + * Number of forward references to this label, times two. + */ + private int referenceCount; + + /** + * Informations about forward references. Each forward reference is + * described by two consecutive integers in this array: the first one is the + * position of the first byte of the bytecode instruction that contains the + * forward reference, while the second is the position of the first byte of + * the forward reference itself. In fact the sign of the first integer + * indicates if this reference uses 2 or 4 bytes, and its absolute value + * gives the position of the bytecode instruction. This array is also used + * as a bitset to store the subroutines to which a basic block belongs. This + * information is needed in {@linked MethodWriter#visitMaxs}, after all + * forward references have been resolved. Hence the same array can be used + * for both purposes without problems. + */ + private int[] srcAndRefPositions; + + // ------------------------------------------------------------------------ + + /* + * Fields for the control flow and data flow graph analysis algorithms (used + * to compute the maximum stack size or the stack map frames). A control + * flow graph contains one node per "basic block", and one edge per "jump" + * from one basic block to another. Each node (i.e., each basic block) is + * represented by the Label object that corresponds to the first instruction + * of this basic block. Each node also stores the list of its successors in + * the graph, as a linked list of Edge objects. + * + * The control flow analysis algorithms used to compute the maximum stack + * size or the stack map frames are similar and use two steps. The first + * step, during the visit of each instruction, builds information about the + * state of the local variables and the operand stack at the end of each + * basic block, called the "output frame", relatively to the frame + * state at the beginning of the basic block, which is called the "input + * frame", and which is unknown during this step. The second step, in + * {@link MethodWriter#visitMaxs}, is a fix point algorithm that computes + * information about the input frame of each basic block, from the input + * state of the first basic block (known from the method signature), and by + * the using the previously computed relative output frames. + * + * The algorithm used to compute the maximum stack size only computes the + * relative output and absolute input stack heights, while the algorithm + * used to compute stack map frames computes relative output frames and + * absolute input frames. + */ + + /** + * Start of the output stack relatively to the input stack. The exact + * semantics of this field depends on the algorithm that is used. + * + * When only the maximum stack size is computed, this field is the number of + * elements in the input stack. + * + * When the stack map frames are completely computed, this field is the + * offset of the first output stack element relatively to the top of the + * input stack. This offset is always negative or null. A null offset means + * that the output stack must be appended to the input stack. A -n offset + * means that the first n output stack elements must replace the top n input + * stack elements, and that the other elements must be appended to the input + * stack. + */ + int inputStackTop; + + /** + * Maximum height reached by the output stack, relatively to the top of the + * input stack. This maximum is always positive or null. + */ + int outputStackMax; + + /** + * Information about the input and output stack map frames of this basic + * block. This field is only used when {@link ClassWriter#COMPUTE_FRAMES} + * option is used. + */ + Frame frame; + + /** + * The successor of this label, in the order they are visited. This linked + * list does not include labels used for debug info only. If + * {@link ClassWriter#COMPUTE_FRAMES} option is used then, in addition, it + * does not contain successive labels that denote the same bytecode position + * (in this case only the first label appears in this list). + */ + Label successor; + + /** + * The successors of this node in the control flow graph. These successors + * are stored in a linked list of {@link Edge Edge} objects, linked to each + * other by their {@link Edge#next} field. + */ + Edge successors; + + /** + * The next basic block in the basic block stack. This stack is used in the + * main loop of the fix point algorithm used in the second step of the + * control flow analysis algorithms. It is also used in + * {@link #visitSubroutine} to avoid using a recursive method, and in + * ClassReader to temporarily store multiple source lines for a label. + * + * @see MethodWriter#visitMaxs + */ + Label next; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new label. + */ + public Label() { + } + + // ------------------------------------------------------------------------ + // Methods to compute offsets and to manage forward references + // ------------------------------------------------------------------------ + + /** + * Returns the offset corresponding to this label. This offset is computed + * from the start of the method's bytecode. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class + * generators or adapters. + * + * @return the offset corresponding to this label. + * @throws IllegalStateException + * if this label is not resolved yet. + */ + public int getOffset() { + if ((status & RESOLVED) == 0) { + throw new IllegalStateException( + "Label offset position has not been resolved yet"); + } + return position; + } + + /** + * Puts a reference to this label in the bytecode of a method. If the + * position of the label is known, the offset is computed and written + * directly. Otherwise, a null offset is written and a new forward reference + * is declared for this label. + * + * @param owner + * the code writer that calls this method. + * @param out + * the bytecode of the method. + * @param source + * the position of first byte of the bytecode instruction that + * contains this label. + * @param wideOffset + * true if the reference must be stored in 4 bytes, or + * false if it must be stored with 2 bytes. + * @throws IllegalArgumentException + * if this label has not been created by the given code writer. + */ + void put(final MethodWriter owner, final ByteVector out, final int source, + final boolean wideOffset) { + if ((status & RESOLVED) == 0) { + if (wideOffset) { + addReference(-1 - source, out.length); + out.putInt(-1); + } else { + addReference(source, out.length); + out.putShort(-1); + } + } else { + if (wideOffset) { + out.putInt(position - source); + } else { + out.putShort(position - source); + } + } + } + + /** + * Adds a forward reference to this label. This method must be called only + * for a true forward reference, i.e. only if this label is not resolved + * yet. For backward references, the offset of the reference can be, and + * must be, computed and stored directly. + * + * @param sourcePosition + * the position of the referencing instruction. This position + * will be used to compute the offset of this forward reference. + * @param referencePosition + * the position where the offset for this forward reference must + * be stored. + */ + private void addReference(final int sourcePosition, + final int referencePosition) { + if (srcAndRefPositions == null) { + srcAndRefPositions = new int[6]; + } + if (referenceCount >= srcAndRefPositions.length) { + int[] a = new int[srcAndRefPositions.length + 6]; + System.arraycopy(srcAndRefPositions, 0, a, 0, + srcAndRefPositions.length); + srcAndRefPositions = a; + } + srcAndRefPositions[referenceCount++] = sourcePosition; + srcAndRefPositions[referenceCount++] = referencePosition; + } + + /** + * Resolves all forward references to this label. This method must be called + * when this label is added to the bytecode of the method, i.e. when its + * position becomes known. This method fills in the blanks that where left + * in the bytecode by each forward reference previously added to this label. + * + * @param owner + * the code writer that calls this method. + * @param position + * the position of this label in the bytecode. + * @param data + * the bytecode of the method. + * @return true if a blank that was left for this label was to + * small to store the offset. In such a case the corresponding jump + * instruction is replaced with a pseudo instruction (using unused + * opcodes) using an unsigned two bytes offset. These pseudo + * instructions will need to be replaced with true instructions with + * wider offsets (4 bytes instead of 2). This is done in + * {@link MethodWriter#resizeInstructions}. + * @throws IllegalArgumentException + * if this label has already been resolved, or if it has not + * been created by the given code writer. + */ + boolean resolve(final MethodWriter owner, final int position, + final byte[] data) { + boolean needUpdate = false; + this.status |= RESOLVED; + this.position = position; + int i = 0; + while (i < referenceCount) { + int source = srcAndRefPositions[i++]; + int reference = srcAndRefPositions[i++]; + int offset; + if (source >= 0) { + offset = position - source; + if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) { + /* + * changes the opcode of the jump instruction, in order to + * be able to find it later (see resizeInstructions in + * MethodWriter). These temporary opcodes are similar to + * jump instruction opcodes, except that the 2 bytes offset + * is unsigned (and can therefore represent values from 0 to + * 65535, which is sufficient since the size of a method is + * limited to 65535 bytes). + */ + int opcode = data[reference - 1] & 0xFF; + if (opcode <= Opcodes.JSR) { + // changes IFEQ ... JSR to opcodes 202 to 217 + data[reference - 1] = (byte) (opcode + 49); + } else { + // changes IFNULL and IFNONNULL to opcodes 218 and 219 + data[reference - 1] = (byte) (opcode + 20); + } + needUpdate = true; + } + data[reference++] = (byte) (offset >>> 8); + data[reference] = (byte) offset; + } else { + offset = position + source + 1; + data[reference++] = (byte) (offset >>> 24); + data[reference++] = (byte) (offset >>> 16); + data[reference++] = (byte) (offset >>> 8); + data[reference] = (byte) offset; + } + } + return needUpdate; + } + + /** + * Returns the first label of the series to which this label belongs. For an + * isolated label or for the first label in a series of successive labels, + * this method returns the label itself. For other labels it returns the + * first label of the series. + * + * @return the first label of the series to which this label belongs. + */ + Label getFirst() { + return !ClassReader.FRAMES || frame == null ? this : frame.owner; + } + + // ------------------------------------------------------------------------ + // Methods related to subroutines + // ------------------------------------------------------------------------ + + /** + * Returns true is this basic block belongs to the given subroutine. + * + * @param id + * a subroutine id. + * @return true is this basic block belongs to the given subroutine. + */ + boolean inSubroutine(final long id) { + if ((status & Label.VISITED) != 0) { + return (srcAndRefPositions[(int) (id >>> 32)] & (int) id) != 0; + } + return false; + } + + /** + * Returns true if this basic block and the given one belong to a common + * subroutine. + * + * @param block + * another basic block. + * @return true if this basic block and the given one belong to a common + * subroutine. + */ + boolean inSameSubroutine(final Label block) { + if ((status & VISITED) == 0 || (block.status & VISITED) == 0) { + return false; + } + for (int i = 0; i < srcAndRefPositions.length; ++i) { + if ((srcAndRefPositions[i] & block.srcAndRefPositions[i]) != 0) { + return true; + } + } + return false; + } + + /** + * Marks this basic block as belonging to the given subroutine. + * + * @param id + * a subroutine id. + * @param nbSubroutines + * the total number of subroutines in the method. + */ + void addToSubroutine(final long id, final int nbSubroutines) { + if ((status & VISITED) == 0) { + status |= VISITED; + srcAndRefPositions = new int[nbSubroutines / 32 + 1]; + } + srcAndRefPositions[(int) (id >>> 32)] |= (int) id; + } + + /** + * Finds the basic blocks that belong to a given subroutine, and marks these + * blocks as belonging to this subroutine. This method follows the control + * flow graph to find all the blocks that are reachable from the current + * block WITHOUT following any JSR target. + * + * @param JSR + * a JSR block that jumps to this subroutine. If this JSR is not + * null it is added to the successor of the RET blocks found in + * the subroutine. + * @param id + * the id of this subroutine. + * @param nbSubroutines + * the total number of subroutines in the method. + */ + void visitSubroutine(final Label JSR, final long id, final int nbSubroutines) { + // user managed stack of labels, to avoid using a recursive method + // (recursivity can lead to stack overflow with very large methods) + Label stack = this; + while (stack != null) { + // removes a label l from the stack + Label l = stack; + stack = l.next; + l.next = null; + + if (JSR != null) { + if ((l.status & VISITED2) != 0) { + continue; + } + l.status |= VISITED2; + // adds JSR to the successors of l, if it is a RET block + if ((l.status & RET) != 0) { + if (!l.inSameSubroutine(JSR)) { + Edge e = new Edge(); + e.info = l.inputStackTop; + e.successor = JSR.successors.successor; + e.next = l.successors; + l.successors = e; + } + } + } else { + // if the l block already belongs to subroutine 'id', continue + if (l.inSubroutine(id)) { + continue; + } + // marks the l block as belonging to subroutine 'id' + l.addToSubroutine(id, nbSubroutines); + } + // pushes each successor of l on the stack, except JSR targets + Edge e = l.successors; + while (e != null) { + // if the l block is a JSR block, then 'l.successors.next' leads + // to the JSR target (see {@link #visitJumpInsn}) and must + // therefore not be followed + if ((l.status & Label.JSR) == 0 || e != l.successors.next) { + // pushes e.successor on the stack if it not already added + if (e.successor.next == null) { + e.successor.next = stack; + stack = e.successor; + } + } + e = e.next; + } + } + } + + // ------------------------------------------------------------------------ + // Overriden Object methods + // ------------------------------------------------------------------------ + + /** + * Returns a string representation of this label. + * + * @return a string representation of this label. + */ + @Override + public String toString() { + return "L" + System.identityHashCode(this); + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/MethodVisitor.java b/blade-core/src/main/java/org/objectweb/asm/MethodVisitor.java new file mode 100644 index 000000000..f0927e8be --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/MethodVisitor.java @@ -0,0 +1,881 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * A visitor to visit a Java method. The methods of this class must be called in + * the following order: ( visitParameter )* [ + * visitAnnotationDefault ] ( visitAnnotation | + * visitParameterAnnotation visitTypeAnnotation | + * visitAttribute )* [ visitCode ( visitFrame | + * visitXInsn | visitLabel | + * visitInsnAnnotation | visitTryCatchBlock | + * visitTryCatchAnnotation | visitLocalVariable | + * visitLocalVariableAnnotation | visitLineNumber )* + * visitMaxs ] visitEnd. In addition, the + * visitXInsn and visitLabel methods must be called in + * the sequential order of the bytecode instructions of the visited code, + * visitInsnAnnotation must be called after the annotated + * instruction, visitTryCatchBlock must be called before the + * labels passed as arguments have been visited, + * visitTryCatchBlockAnnotation must be called after the + * corresponding try catch block has been visited, and the + * visitLocalVariable, visitLocalVariableAnnotation and + * visitLineNumber methods must be called after the labels + * passed as arguments have been visited. + * + * @author Eric Bruneton + */ +public abstract class MethodVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + protected final int api; + + /** + * The method visitor to which this visitor must delegate method calls. May + * be null. + */ + protected MethodVisitor mv; + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + */ + public MethodVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + * @param mv + * the method visitor to which this visitor must delegate method + * calls. May be null. + */ + public MethodVisitor(final int api, final MethodVisitor mv) { + if (api != Opcodes.ASM4 && api != Opcodes.ASM5) { + throw new IllegalArgumentException(); + } + this.api = api; + this.mv = mv; + } + + // ------------------------------------------------------------------------- + // Parameters, annotations and non standard attributes + // ------------------------------------------------------------------------- + + /** + * Visits a parameter of this method. + * + * @param name + * parameter name or null if none is provided. + * @param access + * the parameter's access flags, only ACC_FINAL, + * ACC_SYNTHETIC or/and ACC_MANDATED are + * allowed (see {@link Opcodes}). + */ + public void visitParameter(String name, int access) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (mv != null) { + mv.visitParameter(name, access); + } + } + + /** + * Visits the default value of this annotation interface method. + * + * @return a visitor to the visit the actual default value of this + * annotation interface method, or null if this visitor is + * not interested in visiting this default value. The 'name' + * parameters passed to the methods of this annotation visitor are + * ignored. Moreover, exacly one visit method must be called on this + * annotation visitor, followed by visitEnd. + */ + public AnnotationVisitor visitAnnotationDefault() { + if (mv != null) { + return mv.visitAnnotationDefault(); + } + return null; + } + + /** + * Visits an annotation of this method. + * + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (mv != null) { + return mv.visitAnnotation(desc, visible); + } + return null; + } + + /** + * Visits an annotation on a type in the method signature. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#METHOD_TYPE_PARAMETER + * METHOD_TYPE_PARAMETER}, + * {@link TypeReference#METHOD_TYPE_PARAMETER_BOUND + * METHOD_TYPE_PARAMETER_BOUND}, + * {@link TypeReference#METHOD_RETURN METHOD_RETURN}, + * {@link TypeReference#METHOD_RECEIVER METHOD_RECEIVER}, + * {@link TypeReference#METHOD_FORMAL_PARAMETER + * METHOD_FORMAL_PARAMETER} or {@link TypeReference#THROWS + * THROWS}. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (mv != null) { + return mv.visitTypeAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + + /** + * Visits an annotation of a parameter this method. + * + * @param parameter + * the parameter index. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitParameterAnnotation(int parameter, + String desc, boolean visible) { + if (mv != null) { + return mv.visitParameterAnnotation(parameter, desc, visible); + } + return null; + } + + /** + * Visits a non standard attribute of this method. + * + * @param attr + * an attribute. + */ + public void visitAttribute(Attribute attr) { + if (mv != null) { + mv.visitAttribute(attr); + } + } + + /** + * Starts the visit of the method's code, if any (i.e. non abstract method). + */ + public void visitCode() { + if (mv != null) { + mv.visitCode(); + } + } + + /** + * Visits the current state of the local variables and operand stack + * elements. This method must(*) be called just before any + * instruction i that follows an unconditional branch instruction + * such as GOTO or THROW, that is the target of a jump instruction, or that + * starts an exception handler block. The visited types must describe the + * values of the local variables and of the operand stack elements just + * before i is executed.
+ *
+ * (*) this is mandatory only for classes whose version is greater than or + * equal to {@link Opcodes#V1_6 V1_6}.
+ *
+ * The frames of a method must be given either in expanded form, or in + * compressed form (all frames must use the same format, i.e. you must not + * mix expanded and compressed frames within a single method): + *
    + *
  • In expanded form, all frames must have the F_NEW type.
  • + *
  • In compressed form, frames are basically "deltas" from the state of + * the previous frame: + *
      + *
    • {@link Opcodes#F_SAME} representing frame with exactly the same + * locals as the previous frame and with the empty stack.
    • + *
    • {@link Opcodes#F_SAME1} representing frame with exactly the same + * locals as the previous frame and with single value on the stack ( + * nStack is 1 and stack[0] contains value for the + * type of the stack item).
    • + *
    • {@link Opcodes#F_APPEND} representing frame with current locals are + * the same as the locals in the previous frame, except that additional + * locals are defined (nLocal is 1, 2 or 3 and + * local elements contains values representing added types).
    • + *
    • {@link Opcodes#F_CHOP} representing frame with current locals are the + * same as the locals in the previous frame, except that the last 1-3 locals + * are absent and with the empty stack (nLocals is 1, 2 or 3).
    • + *
    • {@link Opcodes#F_FULL} representing complete frame data.
    • + *
    + *
  • + *
+ *
+ * In both cases the first frame, corresponding to the method's parameters + * and access flags, is implicit and must not be visited. Also, it is + * illegal to visit two or more frames for the same code location (i.e., at + * least one instruction must be visited between two calls to visitFrame). + * + * @param type + * the type of this stack map frame. Must be + * {@link Opcodes#F_NEW} for expanded frames, or + * {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, + * {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or + * {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for + * compressed frames. + * @param nLocal + * the number of local variables in the visited frame. + * @param local + * the local variable types in this frame. This array must not be + * modified. Primitive types are represented by + * {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, + * {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, + * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are + * represented by a single element). Reference types are + * represented by String objects (representing internal names), + * and uninitialized types by Label objects (this label + * designates the NEW instruction that created this uninitialized + * value). + * @param nStack + * the number of operand stack elements in the visited frame. + * @param stack + * the operand stack types in this frame. This array must not be + * modified. Its content has the same format as the "local" + * array. + * @throws IllegalStateException + * if a frame is visited just after another one, without any + * instruction between the two (unless this frame is a + * Opcodes#F_SAME frame, in which case it is silently ignored). + */ + public void visitFrame(int type, int nLocal, Object[] local, int nStack, + Object[] stack) { + if (mv != null) { + mv.visitFrame(type, nLocal, local, nStack, stack); + } + } + + // ------------------------------------------------------------------------- + // Normal instructions + // ------------------------------------------------------------------------- + + /** + * Visits a zero operand instruction. + * + * @param opcode + * the opcode of the instruction to be visited. This opcode is + * either NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, + * ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, + * FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, + * LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, + * IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, + * SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, + * DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, + * IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM, + * FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, + * IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, + * L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, + * LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, + * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, + * or MONITOREXIT. + */ + public void visitInsn(int opcode) { + if (mv != null) { + mv.visitInsn(opcode); + } + } + + /** + * Visits an instruction with a single int operand. + * + * @param opcode + * the opcode of the instruction to be visited. This opcode is + * either BIPUSH, SIPUSH or NEWARRAY. + * @param operand + * the operand of the instruction to be visited.
+ * When opcode is BIPUSH, operand value should be between + * Byte.MIN_VALUE and Byte.MAX_VALUE.
+ * When opcode is SIPUSH, operand value should be between + * Short.MIN_VALUE and Short.MAX_VALUE.
+ * When opcode is NEWARRAY, operand value should be one of + * {@link Opcodes#T_BOOLEAN}, {@link Opcodes#T_CHAR}, + * {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE}, + * {@link Opcodes#T_BYTE}, {@link Opcodes#T_SHORT}, + * {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}. + */ + public void visitIntInsn(int opcode, int operand) { + if (mv != null) { + mv.visitIntInsn(opcode, operand); + } + } + + /** + * Visits a local variable instruction. A local variable instruction is an + * instruction that loads or stores the value of a local variable. + * + * @param opcode + * the opcode of the local variable instruction to be visited. + * This opcode is either ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, + * ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET. + * @param var + * the operand of the instruction to be visited. This operand is + * the index of a local variable. + */ + public void visitVarInsn(int opcode, int var) { + if (mv != null) { + mv.visitVarInsn(opcode, var); + } + } + + /** + * Visits a type instruction. A type instruction is an instruction that + * takes the internal name of a class as parameter. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. + * @param type + * the operand of the instruction to be visited. This operand + * must be the internal name of an object or array class (see + * {@link Type#getInternalName() getInternalName}). + */ + public void visitTypeInsn(int opcode, String type) { + if (mv != null) { + mv.visitTypeInsn(opcode, type); + } + } + + /** + * Visits a field instruction. A field instruction is an instruction that + * loads or stores the value of a field of an object. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD. + * @param owner + * the internal name of the field's owner class (see + * {@link Type#getInternalName() getInternalName}). + * @param name + * the field's name. + * @param desc + * the field's descriptor (see {@link Type Type}). + */ + public void visitFieldInsn(int opcode, String owner, String name, + String desc) { + if (mv != null) { + mv.visitFieldInsn(opcode, owner, name, desc); + } + } + + /** + * Visits a method instruction. A method instruction is an instruction that + * invokes a method. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or + * INVOKEINTERFACE. + * @param owner + * the internal name of the method's owner class (see + * {@link Type#getInternalName() getInternalName}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type Type}). + */ + @Deprecated + public void visitMethodInsn(int opcode, String owner, String name, + String desc) { + if (api >= Opcodes.ASM5) { + boolean itf = opcode == Opcodes.INVOKEINTERFACE; + visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, desc); + } + } + + /** + * Visits a method instruction. A method instruction is an instruction that + * invokes a method. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or + * INVOKEINTERFACE. + * @param owner + * the internal name of the method's owner class (see + * {@link Type#getInternalName() getInternalName}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type Type}). + * @param itf + * if the method's owner class is an interface. + */ + public void visitMethodInsn(int opcode, String owner, String name, + String desc, boolean itf) { + if (api < Opcodes.ASM5) { + if (itf != (opcode == Opcodes.INVOKEINTERFACE)) { + throw new IllegalArgumentException( + "INVOKESPECIAL/STATIC on interfaces require ASM 5"); + } + visitMethodInsn(opcode, owner, name, desc); + return; + } + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + + /** + * Visits an invokedynamic instruction. + * + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type Type}). + * @param bsm + * the bootstrap method. + * @param bsmArgs + * the bootstrap method constant arguments. Each argument must be + * an {@link Integer}, {@link Float}, {@link Long}, + * {@link Double}, {@link String}, {@link Type} or {@link Handle} + * value. This method is allowed to modify the content of the + * array so a caller should expect that this array may change. + */ + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, + Object... bsmArgs) { + if (mv != null) { + mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + } + + /** + * Visits a jump instruction. A jump instruction is an instruction that may + * jump to another instruction. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, + * IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, + * IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL. + * @param label + * the operand of the instruction to be visited. This operand is + * a label that designates the instruction to which the jump + * instruction may jump. + */ + public void visitJumpInsn(int opcode, Label label) { + if (mv != null) { + mv.visitJumpInsn(opcode, label); + } + } + + /** + * Visits a label. A label designates the instruction that will be visited + * just after it. + * + * @param label + * a {@link Label Label} object. + */ + public void visitLabel(Label label) { + if (mv != null) { + mv.visitLabel(label); + } + } + + // ------------------------------------------------------------------------- + // Special instructions + // ------------------------------------------------------------------------- + + /** + * Visits a LDC instruction. Note that new constant types may be added in + * future versions of the Java Virtual Machine. To easily detect new + * constant types, implementations of this method should check for + * unexpected constant types, like this: + * + *
+     * if (cst instanceof Integer) {
+     *     // ...
+     * } else if (cst instanceof Float) {
+     *     // ...
+     * } else if (cst instanceof Long) {
+     *     // ...
+     * } else if (cst instanceof Double) {
+     *     // ...
+     * } else if (cst instanceof String) {
+     *     // ...
+     * } else if (cst instanceof Type) {
+     *     int sort = ((Type) cst).getSort();
+     *     if (sort == Type.OBJECT) {
+     *         // ...
+     *     } else if (sort == Type.ARRAY) {
+     *         // ...
+     *     } else if (sort == Type.METHOD) {
+     *         // ...
+     *     } else {
+     *         // throw an exception
+     *     }
+     * } else if (cst instanceof Handle) {
+     *     // ...
+     * } else {
+     *     // throw an exception
+     * }
+     * 
+ * + * @param cst + * the constant to be loaded on the stack. This parameter must be + * a non null {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double}, a {@link String}, a {@link Type} of OBJECT or + * ARRAY sort for .class constants, for classes whose + * version is 49.0, a {@link Type} of METHOD sort or a + * {@link Handle} for MethodType and MethodHandle constants, for + * classes whose version is 51.0. + */ + public void visitLdcInsn(Object cst) { + if (mv != null) { + mv.visitLdcInsn(cst); + } + } + + /** + * Visits an IINC instruction. + * + * @param var + * index of the local variable to be incremented. + * @param increment + * amount to increment the local variable by. + */ + public void visitIincInsn(int var, int increment) { + if (mv != null) { + mv.visitIincInsn(var, increment); + } + } + + /** + * Visits a TABLESWITCH instruction. + * + * @param min + * the minimum key value. + * @param max + * the maximum key value. + * @param dflt + * beginning of the default handler block. + * @param labels + * beginnings of the handler blocks. labels[i] is the + * beginning of the handler block for the min + i key. + */ + public void visitTableSwitchInsn(int min, int max, Label dflt, + Label... labels) { + if (mv != null) { + mv.visitTableSwitchInsn(min, max, dflt, labels); + } + } + + /** + * Visits a LOOKUPSWITCH instruction. + * + * @param dflt + * beginning of the default handler block. + * @param keys + * the values of the keys. + * @param labels + * beginnings of the handler blocks. labels[i] is the + * beginning of the handler block for the keys[i] key. + */ + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + if (mv != null) { + mv.visitLookupSwitchInsn(dflt, keys, labels); + } + } + + /** + * Visits a MULTIANEWARRAY instruction. + * + * @param desc + * an array type descriptor (see {@link Type Type}). + * @param dims + * number of dimensions of the array to allocate. + */ + public void visitMultiANewArrayInsn(String desc, int dims) { + if (mv != null) { + mv.visitMultiANewArrayInsn(desc, dims); + } + } + + /** + * Visits an annotation on an instruction. This method must be called just + * after the annotated instruction. It can be called several times + * for the same instruction. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#INSTANCEOF INSTANCEOF}, + * {@link TypeReference#NEW NEW}, + * {@link TypeReference#CONSTRUCTOR_REFERENCE + * CONSTRUCTOR_REFERENCE}, {@link TypeReference#METHOD_REFERENCE + * METHOD_REFERENCE}, {@link TypeReference#CAST CAST}, + * {@link TypeReference#CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, + * {@link TypeReference#METHOD_INVOCATION_TYPE_ARGUMENT + * METHOD_INVOCATION_TYPE_ARGUMENT}, + * {@link TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or + * {@link TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT + * METHOD_REFERENCE_TYPE_ARGUMENT}. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitInsnAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (mv != null) { + return mv.visitInsnAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + + // ------------------------------------------------------------------------- + // Exceptions table entries, debug information, max stack and max locals + // ------------------------------------------------------------------------- + + /** + * Visits a try catch block. + * + * @param start + * beginning of the exception handler's scope (inclusive). + * @param end + * end of the exception handler's scope (exclusive). + * @param handler + * beginning of the exception handler's code. + * @param type + * internal name of the type of exceptions handled by the + * handler, or null to catch any exceptions (for + * "finally" blocks). + * @throws IllegalArgumentException + * if one of the labels has already been visited by this visitor + * (by the {@link #visitLabel visitLabel} method). + */ + public void visitTryCatchBlock(Label start, Label end, Label handler, + String type) { + if (mv != null) { + mv.visitTryCatchBlock(start, end, handler, type); + } + } + + /** + * Visits an annotation on an exception handler type. This method must be + * called after the {@link #visitTryCatchBlock} for the annotated + * exception handler. It can be called several times for the same exception + * handler. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#EXCEPTION_PARAMETER + * EXCEPTION_PARAMETER}. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (mv != null) { + return mv.visitTryCatchAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + + /** + * Visits a local variable declaration. + * + * @param name + * the name of a local variable. + * @param desc + * the type descriptor of this local variable. + * @param signature + * the type signature of this local variable. May be + * null if the local variable type does not use generic + * types. + * @param start + * the first instruction corresponding to the scope of this local + * variable (inclusive). + * @param end + * the last instruction corresponding to the scope of this local + * variable (exclusive). + * @param index + * the local variable's index. + * @throws IllegalArgumentException + * if one of the labels has not already been visited by this + * visitor (by the {@link #visitLabel visitLabel} method). + */ + public void visitLocalVariable(String name, String desc, String signature, + Label start, Label end, int index) { + if (mv != null) { + mv.visitLocalVariable(name, desc, signature, start, end, index); + } + } + + /** + * Visits an annotation on a local variable type. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#LOCAL_VARIABLE + * LOCAL_VARIABLE} or {@link TypeReference#RESOURCE_VARIABLE + * RESOURCE_VARIABLE}. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + * @param start + * the fist instructions corresponding to the continuous ranges + * that make the scope of this local variable (inclusive). + * @param end + * the last instructions corresponding to the continuous ranges + * that make the scope of this local variable (exclusive). This + * array must have the same size as the 'start' array. + * @param index + * the local variable's index in each range. This array must have + * the same size as the 'start' array. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, Label[] start, Label[] end, int[] index, + String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (mv != null) { + return mv.visitLocalVariableAnnotation(typeRef, typePath, start, + end, index, desc, visible); + } + return null; + } + + /** + * Visits a line number declaration. + * + * @param line + * a line number. This number refers to the source file from + * which the class was compiled. + * @param start + * the first instruction corresponding to this line number. + * @throws IllegalArgumentException + * if start has not already been visited by this + * visitor (by the {@link #visitLabel visitLabel} method). + */ + public void visitLineNumber(int line, Label start) { + if (mv != null) { + mv.visitLineNumber(line, start); + } + } + + /** + * Visits the maximum stack size and the maximum number of local variables + * of the method. + * + * @param maxStack + * maximum stack size of the method. + * @param maxLocals + * maximum number of local variables for the method. + */ + public void visitMaxs(int maxStack, int maxLocals) { + if (mv != null) { + mv.visitMaxs(maxStack, maxLocals); + } + } + + /** + * Visits the end of the method. This method, which is the last one to be + * called, is used to inform the visitor that all the annotations and + * attributes of the method have been visited. + */ + public void visitEnd() { + if (mv != null) { + mv.visitEnd(); + } + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/MethodWriter.java b/blade-core/src/main/java/org/objectweb/asm/MethodWriter.java new file mode 100644 index 000000000..5d30ddc0e --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/MethodWriter.java @@ -0,0 +1,2915 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * A {@link MethodVisitor} that generates methods in bytecode form. Each visit + * method of this class appends the bytecode corresponding to the visited + * instruction to a byte vector, in the order these methods are called. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +class MethodWriter extends MethodVisitor { + + /** + * Pseudo access flag used to denote constructors. + */ + static final int ACC_CONSTRUCTOR = 0x80000; + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is zero. + */ + static final int SAME_FRAME = 0; // to 63 (0-3f) + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is 1 + */ + static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 (40-7f) + + /** + * Reserved for future use + */ + static final int RESERVED = 128; + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is 1. Offset is bigger then 63; + */ + static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7 + + /** + * Frame where current locals are the same as the locals in the previous + * frame, except that the k last locals are absent. The value of k is given + * by the formula 251-frame_type. + */ + static final int CHOP_FRAME = 248; // to 250 (f8-fA) + + /** + * Frame has exactly the same locals as the previous stack map frame and + * number of stack items is zero. Offset is bigger then 63; + */ + static final int SAME_FRAME_EXTENDED = 251; // fb + + /** + * Frame where current locals are the same as the locals in the previous + * frame, except that k additional locals are defined. The value of k is + * given by the formula frame_type-251. + */ + static final int APPEND_FRAME = 252; // to 254 // fc-fe + + /** + * Full frame + */ + static final int FULL_FRAME = 255; // ff + + /** + * Indicates that the stack map frames must be recomputed from scratch. In + * this case the maximum stack size and number of local variables is also + * recomputed from scratch. + * + * @see #compute + */ + private static final int FRAMES = 0; + + /** + * Indicates that the maximum stack size and number of local variables must + * be automatically computed. + * + * @see #compute + */ + private static final int MAXS = 1; + + /** + * Indicates that nothing must be automatically computed. + * + * @see #compute + */ + private static final int NOTHING = 2; + + /** + * The class writer to which this method must be added. + */ + final ClassWriter cw; + + /** + * Access flags of this method. + */ + private int access; + + /** + * The index of the constant pool item that contains the name of this + * method. + */ + private final int name; + + /** + * The index of the constant pool item that contains the descriptor of this + * method. + */ + private final int desc; + + /** + * The descriptor of this method. + */ + private final String descriptor; + + /** + * The signature of this method. + */ + String signature; + + /** + * If not zero, indicates that the code of this method must be copied from + * the ClassReader associated to this writer in cw.cr. More + * precisely, this field gives the index of the first byte to copied from + * cw.cr.b. + */ + int classReaderOffset; + + /** + * If not zero, indicates that the code of this method must be copied from + * the ClassReader associated to this writer in cw.cr. More + * precisely, this field gives the number of bytes to copied from + * cw.cr.b. + */ + int classReaderLength; + + /** + * Number of exceptions that can be thrown by this method. + */ + int exceptionCount; + + /** + * The exceptions that can be thrown by this method. More precisely, this + * array contains the indexes of the constant pool items that contain the + * internal names of these exception classes. + */ + int[] exceptions; + + /** + * The annotation default attribute of this method. May be null. + */ + private ByteVector annd; + + /** + * The runtime visible annotations of this method. May be null. + */ + private AnnotationWriter anns; + + /** + * The runtime invisible annotations of this method. May be null. + */ + private AnnotationWriter ianns; + + /** + * The runtime visible type annotations of this method. May be null + * . + */ + private AnnotationWriter tanns; + + /** + * The runtime invisible type annotations of this method. May be + * null. + */ + private AnnotationWriter itanns; + + /** + * The runtime visible parameter annotations of this method. May be + * null. + */ + private AnnotationWriter[] panns; + + /** + * The runtime invisible parameter annotations of this method. May be + * null. + */ + private AnnotationWriter[] ipanns; + + /** + * The number of synthetic parameters of this method. + */ + private int synthetics; + + /** + * The non standard attributes of the method. + */ + private Attribute attrs; + + /** + * The bytecode of this method. + */ + private ByteVector code = new ByteVector(); + + /** + * Maximum stack size of this method. + */ + private int maxStack; + + /** + * Maximum number of local variables for this method. + */ + private int maxLocals; + + /** + * Number of local variables in the current stack map frame. + */ + private int currentLocals; + + /** + * Number of stack map frames in the StackMapTable attribute. + */ + private int frameCount; + + /** + * The StackMapTable attribute. + */ + private ByteVector stackMap; + + /** + * The offset of the last frame that was written in the StackMapTable + * attribute. + */ + private int previousFrameOffset; + + /** + * The last frame that was written in the StackMapTable attribute. + * + * @see #frame + */ + private int[] previousFrame; + + /** + * The current stack map frame. The first element contains the offset of the + * instruction to which the frame corresponds, the second element is the + * number of locals and the third one is the number of stack elements. The + * local variables start at index 3 and are followed by the operand stack + * values. In summary frame[0] = offset, frame[1] = nLocal, frame[2] = + * nStack, frame[3] = nLocal. All types are encoded as integers, with the + * same format as the one used in {@link Label}, but limited to BASE types. + */ + private int[] frame; + + /** + * Number of elements in the exception handler list. + */ + private int handlerCount; + + /** + * The first element in the exception handler list. + */ + private Handler firstHandler; + + /** + * The last element in the exception handler list. + */ + private Handler lastHandler; + + /** + * Number of entries in the MethodParameters attribute. + */ + private int methodParametersCount; + + /** + * The MethodParameters attribute. + */ + private ByteVector methodParameters; + + /** + * Number of entries in the LocalVariableTable attribute. + */ + private int localVarCount; + + /** + * The LocalVariableTable attribute. + */ + private ByteVector localVar; + + /** + * Number of entries in the LocalVariableTypeTable attribute. + */ + private int localVarTypeCount; + + /** + * The LocalVariableTypeTable attribute. + */ + private ByteVector localVarType; + + /** + * Number of entries in the LineNumberTable attribute. + */ + private int lineNumberCount; + + /** + * The LineNumberTable attribute. + */ + private ByteVector lineNumber; + + /** + * The start offset of the last visited instruction. + */ + private int lastCodeOffset; + + /** + * The runtime visible type annotations of the code. May be null. + */ + private AnnotationWriter ctanns; + + /** + * The runtime invisible type annotations of the code. May be null. + */ + private AnnotationWriter ictanns; + + /** + * The non standard attributes of the method's code. + */ + private Attribute cattrs; + + /** + * Indicates if some jump instructions are too small and need to be resized. + */ + private boolean resize; + + /** + * The number of subroutines in this method. + */ + private int subroutines; + + // ------------------------------------------------------------------------ + + /* + * Fields for the control flow graph analysis algorithm (used to compute the + * maximum stack size). A control flow graph contains one node per "basic + * block", and one edge per "jump" from one basic block to another. Each + * node (i.e., each basic block) is represented by the Label object that + * corresponds to the first instruction of this basic block. Each node also + * stores the list of its successors in the graph, as a linked list of Edge + * objects. + */ + + /** + * Indicates what must be automatically computed. + * + * @see #FRAMES + * @see #MAXS + * @see #NOTHING + */ + private final int compute; + + /** + * A list of labels. This list is the list of basic blocks in the method, + * i.e. a list of Label objects linked to each other by their + * {@link Label#successor} field, in the order they are visited by + * {@link MethodVisitor#visitLabel}, and starting with the first basic + * block. + */ + private Label labels; + + /** + * The previous basic block. + */ + private Label previousBlock; + + /** + * The current basic block. + */ + private Label currentBlock; + + /** + * The (relative) stack size after the last visited instruction. This size + * is relative to the beginning of the current basic block, i.e., the true + * stack size after the last visited instruction is equal to the + * {@link Label#inputStackTop beginStackSize} of the current basic block + * plus stackSize. + */ + private int stackSize; + + /** + * The (relative) maximum stack size after the last visited instruction. + * This size is relative to the beginning of the current basic block, i.e., + * the true maximum stack size after the last visited instruction is equal + * to the {@link Label#inputStackTop beginStackSize} of the current basic + * block plus stackSize. + */ + private int maxStackSize; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link MethodWriter}. + * + * @param cw + * the class writer in which the method must be added. + * @param access + * the method's access flags (see {@link Opcodes}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type}). + * @param signature + * the method's signature. May be null. + * @param exceptions + * the internal names of the method's exceptions. May be + * null. + * @param computeMaxs + * true if the maximum stack size and number of local + * variables must be automatically computed. + * @param computeFrames + * true if the stack map tables must be recomputed from + * scratch. + */ + MethodWriter(final ClassWriter cw, final int access, final String name, + final String desc, final String signature, + final String[] exceptions, final boolean computeMaxs, + final boolean computeFrames) { + super(Opcodes.ASM5); + if (cw.firstMethod == null) { + cw.firstMethod = this; + } else { + cw.lastMethod.mv = this; + } + cw.lastMethod = this; + this.cw = cw; + this.access = access; + if ("".equals(name)) { + this.access |= ACC_CONSTRUCTOR; + } + this.name = cw.newUTF8(name); + this.desc = cw.newUTF8(desc); + this.descriptor = desc; + if (ClassReader.SIGNATURES) { + this.signature = signature; + } + if (exceptions != null && exceptions.length > 0) { + exceptionCount = exceptions.length; + this.exceptions = new int[exceptionCount]; + for (int i = 0; i < exceptionCount; ++i) { + this.exceptions[i] = cw.newClass(exceptions[i]); + } + } + this.compute = computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING); + if (computeMaxs || computeFrames) { + // updates maxLocals + int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2; + if ((access & Opcodes.ACC_STATIC) != 0) { + --size; + } + maxLocals = size; + currentLocals = size; + // creates and visits the label for the first basic block + labels = new Label(); + labels.status |= Label.PUSHED; + visitLabel(labels); + } + } + + // ------------------------------------------------------------------------ + // Implementation of the MethodVisitor abstract class + // ------------------------------------------------------------------------ + + @Override + public void visitParameter(String name, int access) { + if (methodParameters == null) { + methodParameters = new ByteVector(); + } + ++methodParametersCount; + methodParameters.putShort((name == null) ? 0 : cw.newUTF8(name)) + .putShort(access); + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + if (!ClassReader.ANNOTATIONS) { + return null; + } + annd = new ByteVector(); + return new AnnotationWriter(cw, false, annd, null, 0); + } + + @Override + public AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + aw.next = anns; + anns = aw; + } else { + aw.next = ianns; + ianns = aw; + } + return aw; + } + + @Override + public AnnotationVisitor visitTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = tanns; + tanns = aw; + } else { + aw.next = itanns; + itanns = aw; + } + return aw; + } + + @Override + public AnnotationVisitor visitParameterAnnotation(final int parameter, + final String desc, final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + if ("Ljava/lang/Synthetic;".equals(desc)) { + // workaround for a bug in javac with synthetic parameters + // see ClassReader.readParameterAnnotations + synthetics = Math.max(synthetics, parameter + 1); + return new AnnotationWriter(cw, false, bv, null, 0); + } + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2); + if (visible) { + if (panns == null) { + panns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + aw.next = panns[parameter]; + panns[parameter] = aw; + } else { + if (ipanns == null) { + ipanns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length]; + } + aw.next = ipanns[parameter]; + ipanns[parameter] = aw; + } + return aw; + } + + @Override + public void visitAttribute(final Attribute attr) { + if (attr.isCodeAttribute()) { + attr.next = cattrs; + cattrs = attr; + } else { + attr.next = attrs; + attrs = attr; + } + } + + @Override + public void visitCode() { + } + + @Override + public void visitFrame(final int type, final int nLocal, + final Object[] local, final int nStack, final Object[] stack) { + if (!ClassReader.FRAMES || compute == FRAMES) { + return; + } + + if (type == Opcodes.F_NEW) { + if (previousFrame == null) { + visitImplicitFirstFrame(); + } + currentLocals = nLocal; + int frameIndex = startFrame(code.length, nLocal, nStack); + for (int i = 0; i < nLocal; ++i) { + if (local[i] instanceof String) { + frame[frameIndex++] = Frame.OBJECT + | cw.addType((String) local[i]); + } else if (local[i] instanceof Integer) { + frame[frameIndex++] = ((Integer) local[i]).intValue(); + } else { + frame[frameIndex++] = Frame.UNINITIALIZED + | cw.addUninitializedType("", + ((Label) local[i]).position); + } + } + for (int i = 0; i < nStack; ++i) { + if (stack[i] instanceof String) { + frame[frameIndex++] = Frame.OBJECT + | cw.addType((String) stack[i]); + } else if (stack[i] instanceof Integer) { + frame[frameIndex++] = ((Integer) stack[i]).intValue(); + } else { + frame[frameIndex++] = Frame.UNINITIALIZED + | cw.addUninitializedType("", + ((Label) stack[i]).position); + } + } + endFrame(); + } else { + int delta; + if (stackMap == null) { + stackMap = new ByteVector(); + delta = code.length; + } else { + delta = code.length - previousFrameOffset - 1; + if (delta < 0) { + if (type == Opcodes.F_SAME) { + return; + } else { + throw new IllegalStateException(); + } + } + } + + switch (type) { + case Opcodes.F_FULL: + currentLocals = nLocal; + stackMap.putByte(FULL_FRAME).putShort(delta).putShort(nLocal); + for (int i = 0; i < nLocal; ++i) { + writeFrameType(local[i]); + } + stackMap.putShort(nStack); + for (int i = 0; i < nStack; ++i) { + writeFrameType(stack[i]); + } + break; + case Opcodes.F_APPEND: + currentLocals += nLocal; + stackMap.putByte(SAME_FRAME_EXTENDED + nLocal).putShort(delta); + for (int i = 0; i < nLocal; ++i) { + writeFrameType(local[i]); + } + break; + case Opcodes.F_CHOP: + currentLocals -= nLocal; + stackMap.putByte(SAME_FRAME_EXTENDED - nLocal).putShort(delta); + break; + case Opcodes.F_SAME: + if (delta < 64) { + stackMap.putByte(delta); + } else { + stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta); + } + break; + case Opcodes.F_SAME1: + if (delta < 64) { + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta); + } else { + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + .putShort(delta); + } + writeFrameType(stack[0]); + break; + } + + previousFrameOffset = code.length; + ++frameCount; + } + + maxStack = Math.max(maxStack, nStack); + maxLocals = Math.max(maxLocals, currentLocals); + } + + @Override + public void visitInsn(final int opcode) { + lastCodeOffset = code.length; + // adds the instruction to the bytecode of the method + code.putByte(opcode); + // update currentBlock + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, null, null); + } else { + // updates current and max stack sizes + int size = stackSize + Frame.SIZE[opcode]; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + // if opcode == ATHROW or xRETURN, ends current block (no successor) + if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) + || opcode == Opcodes.ATHROW) { + noSuccessor(); + } + } + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + lastCodeOffset = code.length; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, operand, null, null); + } else if (opcode != Opcodes.NEWARRAY) { + // updates current and max stack sizes only for NEWARRAY + // (stack size variation = 0 for BIPUSH or SIPUSH) + int size = stackSize + 1; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + if (opcode == Opcodes.SIPUSH) { + code.put12(opcode, operand); + } else { // BIPUSH or NEWARRAY + code.put11(opcode, operand); + } + } + + @Override + public void visitVarInsn(final int opcode, final int var) { + lastCodeOffset = code.length; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, var, null, null); + } else { + // updates current and max stack sizes + if (opcode == Opcodes.RET) { + // no stack change, but end of current block (no successor) + currentBlock.status |= Label.RET; + // save 'stackSize' here for future use + // (see {@link #findSubroutineSuccessors}) + currentBlock.inputStackTop = stackSize; + noSuccessor(); + } else { // xLOAD or xSTORE + int size = stackSize + Frame.SIZE[opcode]; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + } + if (compute != NOTHING) { + // updates max locals + int n; + if (opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD + || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) { + n = var + 2; + } else { + n = var + 1; + } + if (n > maxLocals) { + maxLocals = n; + } + } + // adds the instruction to the bytecode of the method + if (var < 4 && opcode != Opcodes.RET) { + int opt; + if (opcode < Opcodes.ISTORE) { + /* ILOAD_0 */ + opt = 26 + ((opcode - Opcodes.ILOAD) << 2) + var; + } else { + /* ISTORE_0 */ + opt = 59 + ((opcode - Opcodes.ISTORE) << 2) + var; + } + code.putByte(opt); + } else if (var >= 256) { + code.putByte(196 /* WIDE */).put12(opcode, var); + } else { + code.put11(opcode, var); + } + if (opcode >= Opcodes.ISTORE && compute == FRAMES && handlerCount > 0) { + visitLabel(new Label()); + } + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + lastCodeOffset = code.length; + Item i = cw.newClassItem(type); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, code.length, cw, i); + } else if (opcode == Opcodes.NEW) { + // updates current and max stack sizes only if opcode == NEW + // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF) + int size = stackSize + 1; + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(opcode, i.index); + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, + final String name, final String desc) { + lastCodeOffset = code.length; + Item i = cw.newFieldItem(owner, name, desc); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, cw, i); + } else { + int size; + // computes the stack size variation + char c = desc.charAt(0); + switch (opcode) { + case Opcodes.GETSTATIC: + size = stackSize + (c == 'D' || c == 'J' ? 2 : 1); + break; + case Opcodes.PUTSTATIC: + size = stackSize + (c == 'D' || c == 'J' ? -2 : -1); + break; + case Opcodes.GETFIELD: + size = stackSize + (c == 'D' || c == 'J' ? 1 : 0); + break; + // case Constants.PUTFIELD: + default: + size = stackSize + (c == 'D' || c == 'J' ? -3 : -2); + break; + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(opcode, i.index); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { + lastCodeOffset = code.length; + Item i = cw.newMethodItem(owner, name, desc, itf); + int argSize = i.intVal; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, cw, i); + } else { + /* + * computes the stack size variation. In order not to recompute + * several times this variation for the same Item, we use the + * intVal field of this item to store this variation, once it + * has been computed. More precisely this intVal field stores + * the sizes of the arguments and of the return value + * corresponding to desc. + */ + if (argSize == 0) { + // the above sizes have not been computed yet, + // so we compute them... + argSize = Type.getArgumentsAndReturnSizes(desc); + // ... and we save them in order + // not to recompute them in the future + i.intVal = argSize; + } + int size; + if (opcode == Opcodes.INVOKESTATIC) { + size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1; + } else { + size = stackSize - (argSize >> 2) + (argSize & 0x03); + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + if (opcode == Opcodes.INVOKEINTERFACE) { + if (argSize == 0) { + argSize = Type.getArgumentsAndReturnSizes(desc); + i.intVal = argSize; + } + code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0); + } else { + code.put12(opcode, i.index); + } + } + + @Override + public void visitInvokeDynamicInsn(final String name, final String desc, + final Handle bsm, final Object... bsmArgs) { + lastCodeOffset = code.length; + Item i = cw.newInvokeDynamicItem(name, desc, bsm, bsmArgs); + int argSize = i.intVal; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, cw, i); + } else { + /* + * computes the stack size variation. In order not to recompute + * several times this variation for the same Item, we use the + * intVal field of this item to store this variation, once it + * has been computed. More precisely this intVal field stores + * the sizes of the arguments and of the return value + * corresponding to desc. + */ + if (argSize == 0) { + // the above sizes have not been computed yet, + // so we compute them... + argSize = Type.getArgumentsAndReturnSizes(desc); + // ... and we save them in order + // not to recompute them in the future + i.intVal = argSize; + } + int size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1; + + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + code.put12(Opcodes.INVOKEDYNAMIC, i.index); + code.putShort(0); + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + lastCodeOffset = code.length; + Label nextInsn = null; + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(opcode, 0, null, null); + // 'label' is the target of a jump instruction + label.getFirst().status |= Label.TARGET; + // adds 'label' as a successor of this basic block + addSuccessor(Edge.NORMAL, label); + if (opcode != Opcodes.GOTO) { + // creates a Label for the next basic block + nextInsn = new Label(); + } + } else { + if (opcode == Opcodes.JSR) { + if ((label.status & Label.SUBROUTINE) == 0) { + label.status |= Label.SUBROUTINE; + ++subroutines; + } + currentBlock.status |= Label.JSR; + addSuccessor(stackSize + 1, label); + // creates a Label for the next basic block + nextInsn = new Label(); + /* + * note that, by construction in this method, a JSR block + * has at least two successors in the control flow graph: + * the first one leads the next instruction after the JSR, + * while the second one leads to the JSR target. + */ + } else { + // updates current stack size (max stack size unchanged + // because stack size variation always negative in this + // case) + stackSize += Frame.SIZE[opcode]; + addSuccessor(stackSize, label); + } + } + } + // adds the instruction to the bytecode of the method + if ((label.status & Label.RESOLVED) != 0 + && label.position - code.length < Short.MIN_VALUE) { + /* + * case of a backward jump with an offset < -32768. In this case we + * automatically replace GOTO with GOTO_W, JSR with JSR_W and IFxxx + * with IFNOTxxx GOTO_W , where IFNOTxxx is the + * "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) and where + * designates the instruction just after the GOTO_W. + */ + if (opcode == Opcodes.GOTO) { + code.putByte(200); // GOTO_W + } else if (opcode == Opcodes.JSR) { + code.putByte(201); // JSR_W + } else { + // if the IF instruction is transformed into IFNOT GOTO_W the + // next instruction becomes the target of the IFNOT instruction + if (nextInsn != null) { + nextInsn.status |= Label.TARGET; + } + code.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1 + : opcode ^ 1); + code.putShort(8); // jump offset + code.putByte(200); // GOTO_W + } + label.put(this, code, code.length - 1, true); + } else { + /* + * case of a backward jump with an offset >= -32768, or of a forward + * jump with, of course, an unknown offset. In these cases we store + * the offset in 2 bytes (which will be increased in + * resizeInstructions, if needed). + */ + code.putByte(opcode); + label.put(this, code, code.length - 1, false); + } + if (currentBlock != null) { + if (nextInsn != null) { + // if the jump instruction is not a GOTO, the next instruction + // is also a successor of this instruction. Calling visitLabel + // adds the label of this next instruction as a successor of the + // current block, and starts a new basic block + visitLabel(nextInsn); + } + if (opcode == Opcodes.GOTO) { + noSuccessor(); + } + } + } + + @Override + public void visitLabel(final Label label) { + // resolves previous forward references to label, if any + resize |= label.resolve(this, code.length, code.data); + // updates currentBlock + if ((label.status & Label.DEBUG) != 0) { + return; + } + if (compute == FRAMES) { + if (currentBlock != null) { + if (label.position == currentBlock.position) { + // successive labels, do not start a new basic block + currentBlock.status |= (label.status & Label.TARGET); + label.frame = currentBlock.frame; + return; + } + // ends current block (with one new successor) + addSuccessor(Edge.NORMAL, label); + } + // begins a new current block + currentBlock = label; + if (label.frame == null) { + label.frame = new Frame(); + label.frame.owner = label; + } + // updates the basic block list + if (previousBlock != null) { + if (label.position == previousBlock.position) { + previousBlock.status |= (label.status & Label.TARGET); + label.frame = previousBlock.frame; + currentBlock = previousBlock; + return; + } + previousBlock.successor = label; + } + previousBlock = label; + } else if (compute == MAXS) { + if (currentBlock != null) { + // ends current block (with one new successor) + currentBlock.outputStackMax = maxStackSize; + addSuccessor(stackSize, label); + } + // begins a new current block + currentBlock = label; + // resets the relative current and max stack sizes + stackSize = 0; + maxStackSize = 0; + // updates the basic block list + if (previousBlock != null) { + previousBlock.successor = label; + } + previousBlock = label; + } + } + + @Override + public void visitLdcInsn(final Object cst) { + lastCodeOffset = code.length; + Item i = cw.newConstItem(cst); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.LDC, 0, cw, i); + } else { + int size; + // computes the stack size variation + if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) { + size = stackSize + 2; + } else { + size = stackSize + 1; + } + // updates current and max stack sizes + if (size > maxStackSize) { + maxStackSize = size; + } + stackSize = size; + } + } + // adds the instruction to the bytecode of the method + int index = i.index; + if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) { + code.put12(20 /* LDC2_W */, index); + } else if (index >= 256) { + code.put12(19 /* LDC_W */, index); + } else { + code.put11(Opcodes.LDC, index); + } + } + + @Override + public void visitIincInsn(final int var, final int increment) { + lastCodeOffset = code.length; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.IINC, var, null, null); + } + } + if (compute != NOTHING) { + // updates max locals + int n = var + 1; + if (n > maxLocals) { + maxLocals = n; + } + } + // adds the instruction to the bytecode of the method + if ((var > 255) || (increment > 127) || (increment < -128)) { + code.putByte(196 /* WIDE */).put12(Opcodes.IINC, var) + .putShort(increment); + } else { + code.putByte(Opcodes.IINC).put11(var, increment); + } + } + + @Override + public void visitTableSwitchInsn(final int min, final int max, + final Label dflt, final Label... labels) { + lastCodeOffset = code.length; + // adds the instruction to the bytecode of the method + int source = code.length; + code.putByte(Opcodes.TABLESWITCH); + code.putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(this, code, source, true); + code.putInt(min).putInt(max); + for (int i = 0; i < labels.length; ++i) { + labels[i].put(this, code, source, true); + } + // updates currentBlock + visitSwitchInsn(dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, + final Label[] labels) { + lastCodeOffset = code.length; + // adds the instruction to the bytecode of the method + int source = code.length; + code.putByte(Opcodes.LOOKUPSWITCH); + code.putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(this, code, source, true); + code.putInt(labels.length); + for (int i = 0; i < labels.length; ++i) { + code.putInt(keys[i]); + labels[i].put(this, code, source, true); + } + // updates currentBlock + visitSwitchInsn(dflt, labels); + } + + private void visitSwitchInsn(final Label dflt, final Label[] labels) { + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null); + // adds current block successors + addSuccessor(Edge.NORMAL, dflt); + dflt.getFirst().status |= Label.TARGET; + for (int i = 0; i < labels.length; ++i) { + addSuccessor(Edge.NORMAL, labels[i]); + labels[i].getFirst().status |= Label.TARGET; + } + } else { + // updates current stack size (max stack size unchanged) + --stackSize; + // adds current block successors + addSuccessor(stackSize, dflt); + for (int i = 0; i < labels.length; ++i) { + addSuccessor(stackSize, labels[i]); + } + } + // ends current block + noSuccessor(); + } + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + lastCodeOffset = code.length; + Item i = cw.newClassItem(desc); + // Label currentBlock = this.currentBlock; + if (currentBlock != null) { + if (compute == FRAMES) { + currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i); + } else { + // updates current stack size (max stack size unchanged because + // stack size variation always negative or null) + stackSize += 1 - dims; + } + } + // adds the instruction to the bytecode of the method + code.put12(Opcodes.MULTIANEWARRAY, i.index).putByte(dims); + } + + @Override + public AnnotationVisitor visitInsnAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + typeRef = (typeRef & 0xFF0000FF) | (lastCodeOffset << 8); + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = ctanns; + ctanns = aw; + } else { + aw.next = ictanns; + ictanns = aw; + } + return aw; + } + + @Override + public void visitTryCatchBlock(final Label start, final Label end, + final Label handler, final String type) { + ++handlerCount; + Handler h = new Handler(); + h.start = start; + h.end = end; + h.handler = handler; + h.desc = type; + h.type = type != null ? cw.newClass(type) : 0; + if (lastHandler == null) { + firstHandler = h; + } else { + lastHandler.next = h; + } + lastHandler = h; + } + + @Override + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = ctanns; + ctanns = aw; + } else { + aw.next = ictanns; + ictanns = aw; + } + return aw; + } + + @Override + public void visitLocalVariable(final String name, final String desc, + final String signature, final Label start, final Label end, + final int index) { + if (signature != null) { + if (localVarType == null) { + localVarType = new ByteVector(); + } + ++localVarTypeCount; + localVarType.putShort(start.position) + .putShort(end.position - start.position) + .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(signature)) + .putShort(index); + } + if (localVar == null) { + localVar = new ByteVector(); + } + ++localVarCount; + localVar.putShort(start.position) + .putShort(end.position - start.position) + .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(desc)) + .putShort(index); + if (compute != NOTHING) { + // updates max locals + char c = desc.charAt(0); + int n = index + (c == 'J' || c == 'D' ? 2 : 1); + if (n > maxLocals) { + maxLocals = n; + } + } + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, Label[] start, Label[] end, int[] index, + String desc, boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + bv.putByte(typeRef >>> 24).putShort(start.length); + for (int i = 0; i < start.length; ++i) { + bv.putShort(start[i].position) + .putShort(end[i].position - start[i].position) + .putShort(index[i]); + } + if (typePath == null) { + bv.putByte(0); + } else { + int length = typePath.b[typePath.offset] * 2 + 1; + bv.putByteArray(typePath.b, typePath.offset, length); + } + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = ctanns; + ctanns = aw; + } else { + aw.next = ictanns; + ictanns = aw; + } + return aw; + } + + @Override + public void visitLineNumber(final int line, final Label start) { + if (lineNumber == null) { + lineNumber = new ByteVector(); + } + ++lineNumberCount; + lineNumber.putShort(start.position); + lineNumber.putShort(line); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + if (resize) { + // replaces the temporary jump opcodes introduced by Label.resolve. + if (ClassReader.RESIZE) { + resizeInstructions(); + } else { + throw new RuntimeException("Method code too large!"); + } + } + if (ClassReader.FRAMES && compute == FRAMES) { + // completes the control flow graph with exception handler blocks + Handler handler = firstHandler; + while (handler != null) { + Label l = handler.start.getFirst(); + Label h = handler.handler.getFirst(); + Label e = handler.end.getFirst(); + // computes the kind of the edges to 'h' + String t = handler.desc == null ? "java/lang/Throwable" + : handler.desc; + int kind = Frame.OBJECT | cw.addType(t); + // h is an exception handler + h.status |= Label.TARGET; + // adds 'h' as a successor of labels between 'start' and 'end' + while (l != e) { + // creates an edge to 'h' + Edge b = new Edge(); + b.info = kind; + b.successor = h; + // adds it to the successors of 'l' + b.next = l.successors; + l.successors = b; + // goes to the next label + l = l.successor; + } + handler = handler.next; + } + + // creates and visits the first (implicit) frame + Frame f = labels.frame; + Type[] args = Type.getArgumentTypes(descriptor); + f.initInputFrame(cw, access, args, this.maxLocals); + visitFrame(f); + + /* + * fix point algorithm: mark the first basic block as 'changed' + * (i.e. put it in the 'changed' list) and, while there are changed + * basic blocks, choose one, mark it as unchanged, and update its + * successors (which can be changed in the process). + */ + int max = 0; + Label changed = labels; + while (changed != null) { + // removes a basic block from the list of changed basic blocks + Label l = changed; + changed = changed.next; + l.next = null; + f = l.frame; + // a reachable jump target must be stored in the stack map + if ((l.status & Label.TARGET) != 0) { + l.status |= Label.STORE; + } + // all visited labels are reachable, by definition + l.status |= Label.REACHABLE; + // updates the (absolute) maximum stack size + int blockMax = f.inputStack.length + l.outputStackMax; + if (blockMax > max) { + max = blockMax; + } + // updates the successors of the current basic block + Edge e = l.successors; + while (e != null) { + Label n = e.successor.getFirst(); + boolean change = f.merge(cw, n.frame, e.info); + if (change && n.next == null) { + // if n has changed and is not already in the 'changed' + // list, adds it to this list + n.next = changed; + changed = n; + } + e = e.next; + } + } + + // visits all the frames that must be stored in the stack map + Label l = labels; + while (l != null) { + f = l.frame; + if ((l.status & Label.STORE) != 0) { + visitFrame(f); + } + if ((l.status & Label.REACHABLE) == 0) { + // finds start and end of dead basic block + Label k = l.successor; + int start = l.position; + int end = (k == null ? code.length : k.position) - 1; + // if non empty basic block + if (end >= start) { + max = Math.max(max, 1); + // replaces instructions with NOP ... NOP ATHROW + for (int i = start; i < end; ++i) { + code.data[i] = Opcodes.NOP; + } + code.data[end] = (byte) Opcodes.ATHROW; + // emits a frame for this unreachable block + int frameIndex = startFrame(start, 0, 1); + frame[frameIndex] = Frame.OBJECT + | cw.addType("java/lang/Throwable"); + endFrame(); + // removes the start-end range from the exception + // handlers + firstHandler = Handler.remove(firstHandler, l, k); + } + } + l = l.successor; + } + + handler = firstHandler; + handlerCount = 0; + while (handler != null) { + handlerCount += 1; + handler = handler.next; + } + + this.maxStack = max; + } else if (compute == MAXS) { + // completes the control flow graph with exception handler blocks + Handler handler = firstHandler; + while (handler != null) { + Label l = handler.start; + Label h = handler.handler; + Label e = handler.end; + // adds 'h' as a successor of labels between 'start' and 'end' + while (l != e) { + // creates an edge to 'h' + Edge b = new Edge(); + b.info = Edge.EXCEPTION; + b.successor = h; + // adds it to the successors of 'l' + if ((l.status & Label.JSR) == 0) { + b.next = l.successors; + l.successors = b; + } else { + // if l is a JSR block, adds b after the first two edges + // to preserve the hypothesis about JSR block successors + // order (see {@link #visitJumpInsn}) + b.next = l.successors.next.next; + l.successors.next.next = b; + } + // goes to the next label + l = l.successor; + } + handler = handler.next; + } + + if (subroutines > 0) { + // completes the control flow graph with the RET successors + /* + * first step: finds the subroutines. This step determines, for + * each basic block, to which subroutine(s) it belongs. + */ + // finds the basic blocks that belong to the "main" subroutine + int id = 0; + labels.visitSubroutine(null, 1, subroutines); + // finds the basic blocks that belong to the real subroutines + Label l = labels; + while (l != null) { + if ((l.status & Label.JSR) != 0) { + // the subroutine is defined by l's TARGET, not by l + Label subroutine = l.successors.next.successor; + // if this subroutine has not been visited yet... + if ((subroutine.status & Label.VISITED) == 0) { + // ...assigns it a new id and finds its basic blocks + id += 1; + subroutine.visitSubroutine(null, (id / 32L) << 32 + | (1L << (id % 32)), subroutines); + } + } + l = l.successor; + } + // second step: finds the successors of RET blocks + l = labels; + while (l != null) { + if ((l.status & Label.JSR) != 0) { + Label L = labels; + while (L != null) { + L.status &= ~Label.VISITED2; + L = L.successor; + } + // the subroutine is defined by l's TARGET, not by l + Label subroutine = l.successors.next.successor; + subroutine.visitSubroutine(l, 0, subroutines); + } + l = l.successor; + } + } + + /* + * control flow analysis algorithm: while the block stack is not + * empty, pop a block from this stack, update the max stack size, + * compute the true (non relative) begin stack size of the + * successors of this block, and push these successors onto the + * stack (unless they have already been pushed onto the stack). + * Note: by hypothesis, the {@link Label#inputStackTop} of the + * blocks in the block stack are the true (non relative) beginning + * stack sizes of these blocks. + */ + int max = 0; + Label stack = labels; + while (stack != null) { + // pops a block from the stack + Label l = stack; + stack = stack.next; + // computes the true (non relative) max stack size of this block + int start = l.inputStackTop; + int blockMax = start + l.outputStackMax; + // updates the global max stack size + if (blockMax > max) { + max = blockMax; + } + // analyzes the successors of the block + Edge b = l.successors; + if ((l.status & Label.JSR) != 0) { + // ignores the first edge of JSR blocks (virtual successor) + b = b.next; + } + while (b != null) { + l = b.successor; + // if this successor has not already been pushed... + if ((l.status & Label.PUSHED) == 0) { + // computes its true beginning stack size... + l.inputStackTop = b.info == Edge.EXCEPTION ? 1 : start + + b.info; + // ...and pushes it onto the stack + l.status |= Label.PUSHED; + l.next = stack; + stack = l; + } + b = b.next; + } + } + this.maxStack = Math.max(maxStack, max); + } else { + this.maxStack = maxStack; + this.maxLocals = maxLocals; + } + } + + @Override + public void visitEnd() { + } + + // ------------------------------------------------------------------------ + // Utility methods: control flow analysis algorithm + // ------------------------------------------------------------------------ + + /** + * Adds a successor to the {@link #currentBlock currentBlock} block. + * + * @param info + * information about the control flow edge to be added. + * @param successor + * the successor block to be added to the current block. + */ + private void addSuccessor(final int info, final Label successor) { + // creates and initializes an Edge object... + Edge b = new Edge(); + b.info = info; + b.successor = successor; + // ...and adds it to the successor list of the currentBlock block + b.next = currentBlock.successors; + currentBlock.successors = b; + } + + /** + * Ends the current basic block. This method must be used in the case where + * the current basic block does not have any successor. + */ + private void noSuccessor() { + if (compute == FRAMES) { + Label l = new Label(); + l.frame = new Frame(); + l.frame.owner = l; + l.resolve(this, code.length, code.data); + previousBlock.successor = l; + previousBlock = l; + } else { + currentBlock.outputStackMax = maxStackSize; + } + currentBlock = null; + } + + // ------------------------------------------------------------------------ + // Utility methods: stack map frames + // ------------------------------------------------------------------------ + + /** + * Visits a frame that has been computed from scratch. + * + * @param f + * the frame that must be visited. + */ + private void visitFrame(final Frame f) { + int i, t; + int nTop = 0; + int nLocal = 0; + int nStack = 0; + int[] locals = f.inputLocals; + int[] stacks = f.inputStack; + // computes the number of locals (ignores TOP types that are just after + // a LONG or a DOUBLE, and all trailing TOP types) + for (i = 0; i < locals.length; ++i) { + t = locals[i]; + if (t == Frame.TOP) { + ++nTop; + } else { + nLocal += nTop + 1; + nTop = 0; + } + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + // computes the stack size (ignores TOP types that are just after + // a LONG or a DOUBLE) + for (i = 0; i < stacks.length; ++i) { + t = stacks[i]; + ++nStack; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + // visits the frame and its content + int frameIndex = startFrame(f.owner.position, nLocal, nStack); + for (i = 0; nLocal > 0; ++i, --nLocal) { + t = locals[i]; + frame[frameIndex++] = t; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + for (i = 0; i < stacks.length; ++i) { + t = stacks[i]; + frame[frameIndex++] = t; + if (t == Frame.LONG || t == Frame.DOUBLE) { + ++i; + } + } + endFrame(); + } + + /** + * Visit the implicit first frame of this method. + */ + private void visitImplicitFirstFrame() { + // There can be at most descriptor.length() + 1 locals + int frameIndex = startFrame(0, descriptor.length() + 1, 0); + if ((access & Opcodes.ACC_STATIC) == 0) { + if ((access & ACC_CONSTRUCTOR) == 0) { + frame[frameIndex++] = Frame.OBJECT | cw.addType(cw.thisName); + } else { + frame[frameIndex++] = 6; // Opcodes.UNINITIALIZED_THIS; + } + } + int i = 1; + loop: while (true) { + int j = i; + switch (descriptor.charAt(i++)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + frame[frameIndex++] = 1; // Opcodes.INTEGER; + break; + case 'F': + frame[frameIndex++] = 2; // Opcodes.FLOAT; + break; + case 'J': + frame[frameIndex++] = 4; // Opcodes.LONG; + break; + case 'D': + frame[frameIndex++] = 3; // Opcodes.DOUBLE; + break; + case '[': + while (descriptor.charAt(i) == '[') { + ++i; + } + if (descriptor.charAt(i) == 'L') { + ++i; + while (descriptor.charAt(i) != ';') { + ++i; + } + } + frame[frameIndex++] = Frame.OBJECT + | cw.addType(descriptor.substring(j, ++i)); + break; + case 'L': + while (descriptor.charAt(i) != ';') { + ++i; + } + frame[frameIndex++] = Frame.OBJECT + | cw.addType(descriptor.substring(j + 1, i++)); + break; + default: + break loop; + } + } + frame[1] = frameIndex - 3; + endFrame(); + } + + /** + * Starts the visit of a stack map frame. + * + * @param offset + * the offset of the instruction to which the frame corresponds. + * @param nLocal + * the number of local variables in the frame. + * @param nStack + * the number of stack elements in the frame. + * @return the index of the next element to be written in this frame. + */ + private int startFrame(final int offset, final int nLocal, final int nStack) { + int n = 3 + nLocal + nStack; + if (frame == null || frame.length < n) { + frame = new int[n]; + } + frame[0] = offset; + frame[1] = nLocal; + frame[2] = nStack; + return 3; + } + + /** + * Checks if the visit of the current frame {@link #frame} is finished, and + * if yes, write it in the StackMapTable attribute. + */ + private void endFrame() { + if (previousFrame != null) { // do not write the first frame + if (stackMap == null) { + stackMap = new ByteVector(); + } + writeFrame(); + ++frameCount; + } + previousFrame = frame; + frame = null; + } + + /** + * Compress and writes the current frame {@link #frame} in the StackMapTable + * attribute. + */ + private void writeFrame() { + int clocalsSize = frame[1]; + int cstackSize = frame[2]; + if ((cw.version & 0xFFFF) < Opcodes.V1_6) { + stackMap.putShort(frame[0]).putShort(clocalsSize); + writeFrameTypes(3, 3 + clocalsSize); + stackMap.putShort(cstackSize); + writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize); + return; + } + int localsSize = previousFrame[1]; + int type = FULL_FRAME; + int k = 0; + int delta; + if (frameCount == 0) { + delta = frame[0]; + } else { + delta = frame[0] - previousFrame[0] - 1; + } + if (cstackSize == 0) { + k = clocalsSize - localsSize; + switch (k) { + case -3: + case -2: + case -1: + type = CHOP_FRAME; + localsSize = clocalsSize; + break; + case 0: + type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED; + break; + case 1: + case 2: + case 3: + type = APPEND_FRAME; + break; + } + } else if (clocalsSize == localsSize && cstackSize == 1) { + type = delta < 63 ? SAME_LOCALS_1_STACK_ITEM_FRAME + : SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED; + } + if (type != FULL_FRAME) { + // verify if locals are the same + int l = 3; + for (int j = 0; j < localsSize; j++) { + if (frame[l] != previousFrame[l]) { + type = FULL_FRAME; + break; + } + l++; + } + } + switch (type) { + case SAME_FRAME: + stackMap.putByte(delta); + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME: + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta); + writeFrameTypes(3 + clocalsSize, 4 + clocalsSize); + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED: + stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED).putShort( + delta); + writeFrameTypes(3 + clocalsSize, 4 + clocalsSize); + break; + case SAME_FRAME_EXTENDED: + stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta); + break; + case CHOP_FRAME: + stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta); + break; + case APPEND_FRAME: + stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta); + writeFrameTypes(3 + localsSize, 3 + clocalsSize); + break; + // case FULL_FRAME: + default: + stackMap.putByte(FULL_FRAME).putShort(delta).putShort(clocalsSize); + writeFrameTypes(3, 3 + clocalsSize); + stackMap.putShort(cstackSize); + writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize); + } + } + + /** + * Writes some types of the current frame {@link #frame} into the + * StackMapTableAttribute. This method converts types from the format used + * in {@link Label} to the format used in StackMapTable attributes. In + * particular, it converts type table indexes to constant pool indexes. + * + * @param start + * index of the first type in {@link #frame} to write. + * @param end + * index of last type in {@link #frame} to write (exclusive). + */ + private void writeFrameTypes(final int start, final int end) { + for (int i = start; i < end; ++i) { + int t = frame[i]; + int d = t & Frame.DIM; + if (d == 0) { + int v = t & Frame.BASE_VALUE; + switch (t & Frame.BASE_KIND) { + case Frame.OBJECT: + stackMap.putByte(7).putShort( + cw.newClass(cw.typeTable[v].strVal1)); + break; + case Frame.UNINITIALIZED: + stackMap.putByte(8).putShort(cw.typeTable[v].intVal); + break; + default: + stackMap.putByte(v); + } + } else { + StringBuilder sb = new StringBuilder(); + d >>= 28; + while (d-- > 0) { + sb.append('['); + } + if ((t & Frame.BASE_KIND) == Frame.OBJECT) { + sb.append('L'); + sb.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1); + sb.append(';'); + } else { + switch (t & 0xF) { + case 1: + sb.append('I'); + break; + case 2: + sb.append('F'); + break; + case 3: + sb.append('D'); + break; + case 9: + sb.append('Z'); + break; + case 10: + sb.append('B'); + break; + case 11: + sb.append('C'); + break; + case 12: + sb.append('S'); + break; + default: + sb.append('J'); + } + } + stackMap.putByte(7).putShort(cw.newClass(sb.toString())); + } + } + } + + private void writeFrameType(final Object type) { + if (type instanceof String) { + stackMap.putByte(7).putShort(cw.newClass((String) type)); + } else if (type instanceof Integer) { + stackMap.putByte(((Integer) type).intValue()); + } else { + stackMap.putByte(8).putShort(((Label) type).position); + } + } + + // ------------------------------------------------------------------------ + // Utility methods: dump bytecode array + // ------------------------------------------------------------------------ + + /** + * Returns the size of the bytecode of this method. + * + * @return the size of the bytecode of this method. + */ + final int getSize() { + if (classReaderOffset != 0) { + return 6 + classReaderLength; + } + int size = 8; + if (code.length > 0) { + if (code.length > 65535) { + throw new RuntimeException("Method code too large!"); + } + cw.newUTF8("Code"); + size += 18 + code.length + 8 * handlerCount; + if (localVar != null) { + cw.newUTF8("LocalVariableTable"); + size += 8 + localVar.length; + } + if (localVarType != null) { + cw.newUTF8("LocalVariableTypeTable"); + size += 8 + localVarType.length; + } + if (lineNumber != null) { + cw.newUTF8("LineNumberTable"); + size += 8 + lineNumber.length; + } + if (stackMap != null) { + boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6; + cw.newUTF8(zip ? "StackMapTable" : "StackMap"); + size += 8 + stackMap.length; + } + if (ClassReader.ANNOTATIONS && ctanns != null) { + cw.newUTF8("RuntimeVisibleTypeAnnotations"); + size += 8 + ctanns.getSize(); + } + if (ClassReader.ANNOTATIONS && ictanns != null) { + cw.newUTF8("RuntimeInvisibleTypeAnnotations"); + size += 8 + ictanns.getSize(); + } + if (cattrs != null) { + size += cattrs.getSize(cw, code.data, code.length, maxStack, + maxLocals); + } + } + if (exceptionCount > 0) { + cw.newUTF8("Exceptions"); + size += 8 + 2 * exceptionCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + cw.newUTF8("Synthetic"); + size += 6; + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + cw.newUTF8("Deprecated"); + size += 6; + } + if (ClassReader.SIGNATURES && signature != null) { + cw.newUTF8("Signature"); + cw.newUTF8(signature); + size += 8; + } + if (methodParameters != null) { + cw.newUTF8("MethodParameters"); + size += 7 + methodParameters.length; + } + if (ClassReader.ANNOTATIONS && annd != null) { + cw.newUTF8("AnnotationDefault"); + size += 6 + annd.length; + } + if (ClassReader.ANNOTATIONS && anns != null) { + cw.newUTF8("RuntimeVisibleAnnotations"); + size += 8 + anns.getSize(); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + cw.newUTF8("RuntimeInvisibleAnnotations"); + size += 8 + ianns.getSize(); + } + if (ClassReader.ANNOTATIONS && tanns != null) { + cw.newUTF8("RuntimeVisibleTypeAnnotations"); + size += 8 + tanns.getSize(); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + cw.newUTF8("RuntimeInvisibleTypeAnnotations"); + size += 8 + itanns.getSize(); + } + if (ClassReader.ANNOTATIONS && panns != null) { + cw.newUTF8("RuntimeVisibleParameterAnnotations"); + size += 7 + 2 * (panns.length - synthetics); + for (int i = panns.length - 1; i >= synthetics; --i) { + size += panns[i] == null ? 0 : panns[i].getSize(); + } + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + cw.newUTF8("RuntimeInvisibleParameterAnnotations"); + size += 7 + 2 * (ipanns.length - synthetics); + for (int i = ipanns.length - 1; i >= synthetics; --i) { + size += ipanns[i] == null ? 0 : ipanns[i].getSize(); + } + } + if (attrs != null) { + size += attrs.getSize(cw, null, 0, -1, -1); + } + return size; + } + + /** + * Puts the bytecode of this method in the given byte vector. + * + * @param out + * the byte vector into which the bytecode of this method must be + * copied. + */ + final void put(final ByteVector out) { + final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC; + int mask = ACC_CONSTRUCTOR | Opcodes.ACC_DEPRECATED + | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE + | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR); + out.putShort(access & ~mask).putShort(name).putShort(desc); + if (classReaderOffset != 0) { + out.putByteArray(cw.cr.b, classReaderOffset, classReaderLength); + return; + } + int attributeCount = 0; + if (code.length > 0) { + ++attributeCount; + } + if (exceptionCount > 0) { + ++attributeCount; + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + ++attributeCount; + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + } + if (ClassReader.SIGNATURES && signature != null) { + ++attributeCount; + } + if (methodParameters != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && annd != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && anns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ianns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && tanns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && itanns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && panns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + ++attributeCount; + } + if (attrs != null) { + attributeCount += attrs.getCount(); + } + out.putShort(attributeCount); + if (code.length > 0) { + int size = 12 + code.length + 8 * handlerCount; + if (localVar != null) { + size += 8 + localVar.length; + } + if (localVarType != null) { + size += 8 + localVarType.length; + } + if (lineNumber != null) { + size += 8 + lineNumber.length; + } + if (stackMap != null) { + size += 8 + stackMap.length; + } + if (ClassReader.ANNOTATIONS && ctanns != null) { + size += 8 + ctanns.getSize(); + } + if (ClassReader.ANNOTATIONS && ictanns != null) { + size += 8 + ictanns.getSize(); + } + if (cattrs != null) { + size += cattrs.getSize(cw, code.data, code.length, maxStack, + maxLocals); + } + out.putShort(cw.newUTF8("Code")).putInt(size); + out.putShort(maxStack).putShort(maxLocals); + out.putInt(code.length).putByteArray(code.data, 0, code.length); + out.putShort(handlerCount); + if (handlerCount > 0) { + Handler h = firstHandler; + while (h != null) { + out.putShort(h.start.position).putShort(h.end.position) + .putShort(h.handler.position).putShort(h.type); + h = h.next; + } + } + attributeCount = 0; + if (localVar != null) { + ++attributeCount; + } + if (localVarType != null) { + ++attributeCount; + } + if (lineNumber != null) { + ++attributeCount; + } + if (stackMap != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ctanns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ictanns != null) { + ++attributeCount; + } + if (cattrs != null) { + attributeCount += cattrs.getCount(); + } + out.putShort(attributeCount); + if (localVar != null) { + out.putShort(cw.newUTF8("LocalVariableTable")); + out.putInt(localVar.length + 2).putShort(localVarCount); + out.putByteArray(localVar.data, 0, localVar.length); + } + if (localVarType != null) { + out.putShort(cw.newUTF8("LocalVariableTypeTable")); + out.putInt(localVarType.length + 2).putShort(localVarTypeCount); + out.putByteArray(localVarType.data, 0, localVarType.length); + } + if (lineNumber != null) { + out.putShort(cw.newUTF8("LineNumberTable")); + out.putInt(lineNumber.length + 2).putShort(lineNumberCount); + out.putByteArray(lineNumber.data, 0, lineNumber.length); + } + if (stackMap != null) { + boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6; + out.putShort(cw.newUTF8(zip ? "StackMapTable" : "StackMap")); + out.putInt(stackMap.length + 2).putShort(frameCount); + out.putByteArray(stackMap.data, 0, stackMap.length); + } + if (ClassReader.ANNOTATIONS && ctanns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations")); + ctanns.put(out); + } + if (ClassReader.ANNOTATIONS && ictanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations")); + ictanns.put(out); + } + if (cattrs != null) { + cattrs.put(cw, code.data, code.length, maxLocals, maxStack, out); + } + } + if (exceptionCount > 0) { + out.putShort(cw.newUTF8("Exceptions")).putInt( + 2 * exceptionCount + 2); + out.putShort(exceptionCount); + for (int i = 0; i < exceptionCount; ++i) { + out.putShort(exceptions[i]); + } + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + if ((cw.version & 0xFFFF) < Opcodes.V1_5 + || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) { + out.putShort(cw.newUTF8("Synthetic")).putInt(0); + } + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + out.putShort(cw.newUTF8("Deprecated")).putInt(0); + } + if (ClassReader.SIGNATURES && signature != null) { + out.putShort(cw.newUTF8("Signature")).putInt(2) + .putShort(cw.newUTF8(signature)); + } + if (methodParameters != null) { + out.putShort(cw.newUTF8("MethodParameters")); + out.putInt(methodParameters.length + 1).putByte( + methodParametersCount); + out.putByteArray(methodParameters.data, 0, methodParameters.length); + } + if (ClassReader.ANNOTATIONS && annd != null) { + out.putShort(cw.newUTF8("AnnotationDefault")); + out.putInt(annd.length); + out.putByteArray(annd.data, 0, annd.length); + } + if (ClassReader.ANNOTATIONS && anns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleAnnotations")); + anns.put(out); + } + if (ClassReader.ANNOTATIONS && ianns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); + ianns.put(out); + } + if (ClassReader.ANNOTATIONS && tanns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations")); + tanns.put(out); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations")); + itanns.put(out); + } + if (ClassReader.ANNOTATIONS && panns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleParameterAnnotations")); + AnnotationWriter.put(panns, synthetics, out); + } + if (ClassReader.ANNOTATIONS && ipanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleParameterAnnotations")); + AnnotationWriter.put(ipanns, synthetics, out); + } + if (attrs != null) { + attrs.put(cw, null, 0, -1, -1, out); + } + } + + // ------------------------------------------------------------------------ + // Utility methods: instruction resizing (used to handle GOTO_W and JSR_W) + // ------------------------------------------------------------------------ + + /** + * Resizes and replaces the temporary instructions inserted by + * {@link Label#resolve} for wide forward jumps, while keeping jump offsets + * and instruction addresses consistent. This may require to resize other + * existing instructions, or even to introduce new instructions: for + * example, increasing the size of an instruction by 2 at the middle of a + * method can increases the offset of an IFEQ instruction from 32766 to + * 32768, in which case IFEQ 32766 must be replaced with IFNEQ 8 GOTO_W + * 32765. This, in turn, may require to increase the size of another jump + * instruction, and so on... All these operations are handled automatically + * by this method. + *

+ * This method must be called after all the method that is being built + * has been visited. In particular, the {@link Label Label} objects used + * to construct the method are no longer valid after this method has been + * called. + */ + private void resizeInstructions() { + byte[] b = code.data; // bytecode of the method + int u, v, label; // indexes in b + int i, j; // loop indexes + /* + * 1st step: As explained above, resizing an instruction may require to + * resize another one, which may require to resize yet another one, and + * so on. The first step of the algorithm consists in finding all the + * instructions that need to be resized, without modifying the code. + * This is done by the following "fix point" algorithm: + * + * Parse the code to find the jump instructions whose offset will need + * more than 2 bytes to be stored (the future offset is computed from + * the current offset and from the number of bytes that will be inserted + * or removed between the source and target instructions). For each such + * instruction, adds an entry in (a copy of) the indexes and sizes + * arrays (if this has not already been done in a previous iteration!). + * + * If at least one entry has been added during the previous step, go + * back to the beginning, otherwise stop. + * + * In fact the real algorithm is complicated by the fact that the size + * of TABLESWITCH and LOOKUPSWITCH instructions depends on their + * position in the bytecode (because of padding). In order to ensure the + * convergence of the algorithm, the number of bytes to be added or + * removed from these instructions is over estimated during the previous + * loop, and computed exactly only after the loop is finished (this + * requires another pass to parse the bytecode of the method). + */ + int[] allIndexes = new int[0]; // copy of indexes + int[] allSizes = new int[0]; // copy of sizes + boolean[] resize; // instructions to be resized + int newOffset; // future offset of a jump instruction + + resize = new boolean[code.length]; + + // 3 = loop again, 2 = loop ended, 1 = last pass, 0 = done + int state = 3; + do { + if (state == 3) { + state = 2; + } + u = 0; + while (u < b.length) { + int opcode = b[u] & 0xFF; // opcode of current instruction + int insert = 0; // bytes to be added after this instruction + + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + u += 1; + break; + case ClassWriter.LABEL_INSN: + if (opcode > 201) { + // converts temporary opcodes 202 to 217, 218 and + // 219 to IFEQ ... JSR (inclusive), IFNULL and + // IFNONNULL + opcode = opcode < 218 ? opcode - 49 : opcode - 20; + label = u + readUnsignedShort(b, u + 1); + } else { + label = u + readShort(b, u + 1); + } + newOffset = getNewOffset(allIndexes, allSizes, u, label); + if (newOffset < Short.MIN_VALUE + || newOffset > Short.MAX_VALUE) { + if (!resize[u]) { + if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) { + // two additional bytes will be required to + // replace this GOTO or JSR instruction with + // a GOTO_W or a JSR_W + insert = 2; + } else { + // five additional bytes will be required to + // replace this IFxxx instruction with + // IFNOTxxx GOTO_W , where IFNOTxxx + // is the "opposite" opcode of IFxxx (i.e., + // IFNE for IFEQ) and where designates + // the instruction just after the GOTO_W. + insert = 5; + } + resize[u] = true; + } + } + u += 3; + break; + case ClassWriter.LABELW_INSN: + u += 5; + break; + case ClassWriter.TABL_INSN: + if (state == 1) { + // true number of bytes to be added (or removed) + // from this instruction = (future number of padding + // bytes - current number of padding byte) - + // previously over estimated variation = + // = ((3 - newOffset%4) - (3 - u%4)) - u%4 + // = (-newOffset%4 + u%4) - u%4 + // = -(newOffset & 3) + newOffset = getNewOffset(allIndexes, allSizes, 0, u); + insert = -(newOffset & 3); + } else if (!resize[u]) { + // over estimation of the number of bytes to be + // added to this instruction = 3 - current number + // of padding bytes = 3 - (3 - u%4) = u%4 = u & 3 + insert = u & 3; + resize[u] = true; + } + // skips instruction + u = u + 4 - (u & 3); + u += 4 * (readInt(b, u + 8) - readInt(b, u + 4) + 1) + 12; + break; + case ClassWriter.LOOK_INSN: + if (state == 1) { + // like TABL_INSN + newOffset = getNewOffset(allIndexes, allSizes, 0, u); + insert = -(newOffset & 3); + } else if (!resize[u]) { + // like TABL_INSN + insert = u & 3; + resize[u] = true; + } + // skips instruction + u = u + 4 - (u & 3); + u += 8 * readInt(b, u + 4) + 8; + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + u += 6; + } else { + u += 4; + } + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + u += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + u += 3; + break; + case ClassWriter.ITFMETH_INSN: + case ClassWriter.INDYMETH_INSN: + u += 5; + break; + // case ClassWriter.MANA_INSN: + default: + u += 4; + break; + } + if (insert != 0) { + // adds a new (u, insert) entry in the allIndexes and + // allSizes arrays + int[] newIndexes = new int[allIndexes.length + 1]; + int[] newSizes = new int[allSizes.length + 1]; + System.arraycopy(allIndexes, 0, newIndexes, 0, + allIndexes.length); + System.arraycopy(allSizes, 0, newSizes, 0, allSizes.length); + newIndexes[allIndexes.length] = u; + newSizes[allSizes.length] = insert; + allIndexes = newIndexes; + allSizes = newSizes; + if (insert > 0) { + state = 3; + } + } + } + if (state < 3) { + --state; + } + } while (state != 0); + + // 2nd step: + // copies the bytecode of the method into a new bytevector, updates the + // offsets, and inserts (or removes) bytes as requested. + + ByteVector newCode = new ByteVector(code.length); + + u = 0; + while (u < code.length) { + int opcode = b[u] & 0xFF; + switch (ClassWriter.TYPE[opcode]) { + case ClassWriter.NOARG_INSN: + case ClassWriter.IMPLVAR_INSN: + newCode.putByte(opcode); + u += 1; + break; + case ClassWriter.LABEL_INSN: + if (opcode > 201) { + // changes temporary opcodes 202 to 217 (inclusive), 218 + // and 219 to IFEQ ... JSR (inclusive), IFNULL and + // IFNONNULL + opcode = opcode < 218 ? opcode - 49 : opcode - 20; + label = u + readUnsignedShort(b, u + 1); + } else { + label = u + readShort(b, u + 1); + } + newOffset = getNewOffset(allIndexes, allSizes, u, label); + if (resize[u]) { + // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx + // with IFNOTxxx GOTO_W , where IFNOTxxx is + // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) + // and where designates the instruction just after + // the GOTO_W. + if (opcode == Opcodes.GOTO) { + newCode.putByte(200); // GOTO_W + } else if (opcode == Opcodes.JSR) { + newCode.putByte(201); // JSR_W + } else { + newCode.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1 + : opcode ^ 1); + newCode.putShort(8); // jump offset + newCode.putByte(200); // GOTO_W + // newOffset now computed from start of GOTO_W + newOffset -= 3; + } + newCode.putInt(newOffset); + } else { + newCode.putByte(opcode); + newCode.putShort(newOffset); + } + u += 3; + break; + case ClassWriter.LABELW_INSN: + label = u + readInt(b, u + 1); + newOffset = getNewOffset(allIndexes, allSizes, u, label); + newCode.putByte(opcode); + newCode.putInt(newOffset); + u += 5; + break; + case ClassWriter.TABL_INSN: + // skips 0 to 3 padding bytes + v = u; + u = u + 4 - (v & 3); + // reads and copies instruction + newCode.putByte(Opcodes.TABLESWITCH); + newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + j = readInt(b, u); + u += 4; + newCode.putInt(j); + j = readInt(b, u) - j + 1; + u += 4; + newCode.putInt(readInt(b, u - 4)); + for (; j > 0; --j) { + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + } + break; + case ClassWriter.LOOK_INSN: + // skips 0 to 3 padding bytes + v = u; + u = u + 4 - (v & 3); + // reads and copies instruction + newCode.putByte(Opcodes.LOOKUPSWITCH); + newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + j = readInt(b, u); + u += 4; + newCode.putInt(j); + for (; j > 0; --j) { + newCode.putInt(readInt(b, u)); + u += 4; + label = v + readInt(b, u); + u += 4; + newOffset = getNewOffset(allIndexes, allSizes, v, label); + newCode.putInt(newOffset); + } + break; + case ClassWriter.WIDE_INSN: + opcode = b[u + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + newCode.putByteArray(b, u, 6); + u += 6; + } else { + newCode.putByteArray(b, u, 4); + u += 4; + } + break; + case ClassWriter.VAR_INSN: + case ClassWriter.SBYTE_INSN: + case ClassWriter.LDC_INSN: + newCode.putByteArray(b, u, 2); + u += 2; + break; + case ClassWriter.SHORT_INSN: + case ClassWriter.LDCW_INSN: + case ClassWriter.FIELDORMETH_INSN: + case ClassWriter.TYPE_INSN: + case ClassWriter.IINC_INSN: + newCode.putByteArray(b, u, 3); + u += 3; + break; + case ClassWriter.ITFMETH_INSN: + case ClassWriter.INDYMETH_INSN: + newCode.putByteArray(b, u, 5); + u += 5; + break; + // case MANA_INSN: + default: + newCode.putByteArray(b, u, 4); + u += 4; + break; + } + } + + // updates the stack map frame labels + if (compute == FRAMES) { + Label l = labels; + while (l != null) { + /* + * Detects the labels that are just after an IF instruction that + * has been resized with the IFNOT GOTO_W pattern. These labels + * are now the target of a jump instruction (the IFNOT + * instruction). Note that we need the original label position + * here. getNewOffset must therefore never have been called for + * this label. + */ + u = l.position - 3; + if (u >= 0 && resize[u]) { + l.status |= Label.TARGET; + } + getNewOffset(allIndexes, allSizes, l); + l = l.successor; + } + // Update the offsets in the uninitialized types + if (cw.typeTable != null) { + for (i = 0; i < cw.typeTable.length; ++i) { + Item item = cw.typeTable[i]; + if (item != null && item.type == ClassWriter.TYPE_UNINIT) { + item.intVal = getNewOffset(allIndexes, allSizes, 0, + item.intVal); + } + } + } + // The stack map frames are not serialized yet, so we don't need + // to update them. They will be serialized in visitMaxs. + } else if (frameCount > 0) { + /* + * Resizing an existing stack map frame table is really hard. Not + * only the table must be parsed to update the offets, but new + * frames may be needed for jump instructions that were inserted by + * this method. And updating the offsets or inserting frames can + * change the format of the following frames, in case of packed + * frames. In practice the whole table must be recomputed. For this + * the frames are marked as potentially invalid. This will cause the + * whole class to be reread and rewritten with the COMPUTE_FRAMES + * option (see the ClassWriter.toByteArray method). This is not very + * efficient but is much easier and requires much less code than any + * other method I can think of. + */ + cw.invalidFrames = true; + } + // updates the exception handler block labels + Handler h = firstHandler; + while (h != null) { + getNewOffset(allIndexes, allSizes, h.start); + getNewOffset(allIndexes, allSizes, h.end); + getNewOffset(allIndexes, allSizes, h.handler); + h = h.next; + } + // updates the instructions addresses in the + // local var and line number tables + for (i = 0; i < 2; ++i) { + ByteVector bv = i == 0 ? localVar : localVarType; + if (bv != null) { + b = bv.data; + u = 0; + while (u < bv.length) { + label = readUnsignedShort(b, u); + newOffset = getNewOffset(allIndexes, allSizes, 0, label); + writeShort(b, u, newOffset); + label += readUnsignedShort(b, u + 2); + newOffset = getNewOffset(allIndexes, allSizes, 0, label) + - newOffset; + writeShort(b, u + 2, newOffset); + u += 10; + } + } + } + if (lineNumber != null) { + b = lineNumber.data; + u = 0; + while (u < lineNumber.length) { + writeShort( + b, + u, + getNewOffset(allIndexes, allSizes, 0, + readUnsignedShort(b, u))); + u += 4; + } + } + // updates the labels of the other attributes + Attribute attr = cattrs; + while (attr != null) { + Label[] labels = attr.getLabels(); + if (labels != null) { + for (i = labels.length - 1; i >= 0; --i) { + getNewOffset(allIndexes, allSizes, labels[i]); + } + } + attr = attr.next; + } + + // replaces old bytecodes with new ones + code = newCode; + } + + /** + * Reads an unsigned short value in the given byte array. + * + * @param b + * a byte array. + * @param index + * the start index of the value to be read. + * @return the read value. + */ + static int readUnsignedShort(final byte[] b, final int index) { + return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); + } + + /** + * Reads a signed short value in the given byte array. + * + * @param b + * a byte array. + * @param index + * the start index of the value to be read. + * @return the read value. + */ + static short readShort(final byte[] b, final int index) { + return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF)); + } + + /** + * Reads a signed int value in the given byte array. + * + * @param b + * a byte array. + * @param index + * the start index of the value to be read. + * @return the read value. + */ + static int readInt(final byte[] b, final int index) { + return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) + | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); + } + + /** + * Writes a short value in the given byte array. + * + * @param b + * a byte array. + * @param index + * where the first byte of the short value must be written. + * @param s + * the value to be written in the given byte array. + */ + static void writeShort(final byte[] b, final int index, final int s) { + b[index] = (byte) (s >>> 8); + b[index + 1] = (byte) s; + } + + /** + * Computes the future value of a bytecode offset. + *

+ * Note: it is possible to have several entries for the same instruction in + * the indexes and sizes: two entries (index=a,size=b) and + * (index=a,size=b') are equivalent to a single entry (index=a,size=b+b'). + * + * @param indexes + * current positions of the instructions to be resized. Each + * instruction must be designated by the index of its last + * byte, plus one (or, in other words, by the index of the + * first byte of the next instruction). + * @param sizes + * the number of bytes to be added to the above + * instructions. More precisely, for each i < len, + * sizes[i] bytes will be added at the end of the + * instruction designated by indexes[i] or, if + * sizes[i] is negative, the last | + * sizes[i]| bytes of the instruction will be removed + * (the instruction size must not become negative or + * null). + * @param begin + * index of the first byte of the source instruction. + * @param end + * index of the first byte of the target instruction. + * @return the future value of the given bytecode offset. + */ + static int getNewOffset(final int[] indexes, final int[] sizes, + final int begin, final int end) { + int offset = end - begin; + for (int i = 0; i < indexes.length; ++i) { + if (begin < indexes[i] && indexes[i] <= end) { + // forward jump + offset += sizes[i]; + } else if (end < indexes[i] && indexes[i] <= begin) { + // backward jump + offset -= sizes[i]; + } + } + return offset; + } + + /** + * Updates the offset of the given label. + * + * @param indexes + * current positions of the instructions to be resized. Each + * instruction must be designated by the index of its last + * byte, plus one (or, in other words, by the index of the + * first byte of the next instruction). + * @param sizes + * the number of bytes to be added to the above + * instructions. More precisely, for each i < len, + * sizes[i] bytes will be added at the end of the + * instruction designated by indexes[i] or, if + * sizes[i] is negative, the last | + * sizes[i]| bytes of the instruction will be removed + * (the instruction size must not become negative or + * null). + * @param label + * the label whose offset must be updated. + */ + static void getNewOffset(final int[] indexes, final int[] sizes, + final Label label) { + if ((label.status & Label.RESIZED) == 0) { + label.position = getNewOffset(indexes, sizes, 0, label.position); + label.status |= Label.RESIZED; + } + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/Opcodes.java b/blade-core/src/main/java/org/objectweb/asm/Opcodes.java new file mode 100644 index 000000000..e5c2b33fd --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/Opcodes.java @@ -0,0 +1,361 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +/** + * Defines the JVM opcodes, access flags and array type codes. This interface + * does not define all the JVM opcodes because some opcodes are automatically + * handled. For example, the xLOAD and xSTORE opcodes are automatically replaced + * by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and xSTORE_n + * opcodes are therefore not defined in this interface. Likewise for LDC, + * automatically replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and + * JSR_W. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public interface Opcodes { + + // ASM API versions + + int ASM4 = 4 << 16 | 0 << 8 | 0; + int ASM5 = 5 << 16 | 0 << 8 | 0; + + // versions + + int V1_1 = 3 << 16 | 45; + int V1_2 = 0 << 16 | 46; + int V1_3 = 0 << 16 | 47; + int V1_4 = 0 << 16 | 48; + int V1_5 = 0 << 16 | 49; + int V1_6 = 0 << 16 | 50; + int V1_7 = 0 << 16 | 51; + int V1_8 = 0 << 16 | 52; + + // access flags + + int ACC_PUBLIC = 0x0001; // class, field, method + int ACC_PRIVATE = 0x0002; // class, field, method + int ACC_PROTECTED = 0x0004; // class, field, method + int ACC_STATIC = 0x0008; // field, method + int ACC_FINAL = 0x0010; // class, field, method, parameter + int ACC_SUPER = 0x0020; // class + int ACC_SYNCHRONIZED = 0x0020; // method + int ACC_VOLATILE = 0x0040; // field + int ACC_BRIDGE = 0x0040; // method + int ACC_VARARGS = 0x0080; // method + int ACC_TRANSIENT = 0x0080; // field + int ACC_NATIVE = 0x0100; // method + int ACC_INTERFACE = 0x0200; // class + int ACC_ABSTRACT = 0x0400; // class, method + int ACC_STRICT = 0x0800; // method + int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter + int ACC_ANNOTATION = 0x2000; // class + int ACC_ENUM = 0x4000; // class(?) field inner + int ACC_MANDATED = 0x8000; // parameter + + // ASM specific pseudo access flags + + int ACC_DEPRECATED = 0x20000; // class, field, method + + // types for NEWARRAY + + int T_BOOLEAN = 4; + int T_CHAR = 5; + int T_FLOAT = 6; + int T_DOUBLE = 7; + int T_BYTE = 8; + int T_SHORT = 9; + int T_INT = 10; + int T_LONG = 11; + + // tags for Handle + + int H_GETFIELD = 1; + int H_GETSTATIC = 2; + int H_PUTFIELD = 3; + int H_PUTSTATIC = 4; + int H_INVOKEVIRTUAL = 5; + int H_INVOKESTATIC = 6; + int H_INVOKESPECIAL = 7; + int H_NEWINVOKESPECIAL = 8; + int H_INVOKEINTERFACE = 9; + + // stack map frame types + + /** + * Represents an expanded frame. See {@link ClassReader#EXPAND_FRAMES}. + */ + int F_NEW = -1; + + /** + * Represents a compressed frame with complete frame data. + */ + int F_FULL = 0; + + /** + * Represents a compressed frame where locals are the same as the locals in + * the previous frame, except that additional 1-3 locals are defined, and + * with an empty stack. + */ + int F_APPEND = 1; + + /** + * Represents a compressed frame where locals are the same as the locals in + * the previous frame, except that the last 1-3 locals are absent and with + * an empty stack. + */ + int F_CHOP = 2; + + /** + * Represents a compressed frame with exactly the same locals as the + * previous frame and with an empty stack. + */ + int F_SAME = 3; + + /** + * Represents a compressed frame with exactly the same locals as the + * previous frame and with a single value on the stack. + */ + int F_SAME1 = 4; + + Integer TOP = new Integer(0); + Integer INTEGER = new Integer(1); + Integer FLOAT = new Integer(2); + Integer DOUBLE = new Integer(3); + Integer LONG = new Integer(4); + Integer NULL = new Integer(5); + Integer UNINITIALIZED_THIS = new Integer(6); + + // opcodes // visit method (- = idem) + + int NOP = 0; // visitInsn + int ACONST_NULL = 1; // - + int ICONST_M1 = 2; // - + int ICONST_0 = 3; // - + int ICONST_1 = 4; // - + int ICONST_2 = 5; // - + int ICONST_3 = 6; // - + int ICONST_4 = 7; // - + int ICONST_5 = 8; // - + int LCONST_0 = 9; // - + int LCONST_1 = 10; // - + int FCONST_0 = 11; // - + int FCONST_1 = 12; // - + int FCONST_2 = 13; // - + int DCONST_0 = 14; // - + int DCONST_1 = 15; // - + int BIPUSH = 16; // visitIntInsn + int SIPUSH = 17; // - + int LDC = 18; // visitLdcInsn + // int LDC_W = 19; // - + // int LDC2_W = 20; // - + int ILOAD = 21; // visitVarInsn + int LLOAD = 22; // - + int FLOAD = 23; // - + int DLOAD = 24; // - + int ALOAD = 25; // - + // int ILOAD_0 = 26; // - + // int ILOAD_1 = 27; // - + // int ILOAD_2 = 28; // - + // int ILOAD_3 = 29; // - + // int LLOAD_0 = 30; // - + // int LLOAD_1 = 31; // - + // int LLOAD_2 = 32; // - + // int LLOAD_3 = 33; // - + // int FLOAD_0 = 34; // - + // int FLOAD_1 = 35; // - + // int FLOAD_2 = 36; // - + // int FLOAD_3 = 37; // - + // int DLOAD_0 = 38; // - + // int DLOAD_1 = 39; // - + // int DLOAD_2 = 40; // - + // int DLOAD_3 = 41; // - + // int ALOAD_0 = 42; // - + // int ALOAD_1 = 43; // - + // int ALOAD_2 = 44; // - + // int ALOAD_3 = 45; // - + int IALOAD = 46; // visitInsn + int LALOAD = 47; // - + int FALOAD = 48; // - + int DALOAD = 49; // - + int AALOAD = 50; // - + int BALOAD = 51; // - + int CALOAD = 52; // - + int SALOAD = 53; // - + int ISTORE = 54; // visitVarInsn + int LSTORE = 55; // - + int FSTORE = 56; // - + int DSTORE = 57; // - + int ASTORE = 58; // - + // int ISTORE_0 = 59; // - + // int ISTORE_1 = 60; // - + // int ISTORE_2 = 61; // - + // int ISTORE_3 = 62; // - + // int LSTORE_0 = 63; // - + // int LSTORE_1 = 64; // - + // int LSTORE_2 = 65; // - + // int LSTORE_3 = 66; // - + // int FSTORE_0 = 67; // - + // int FSTORE_1 = 68; // - + // int FSTORE_2 = 69; // - + // int FSTORE_3 = 70; // - + // int DSTORE_0 = 71; // - + // int DSTORE_1 = 72; // - + // int DSTORE_2 = 73; // - + // int DSTORE_3 = 74; // - + // int ASTORE_0 = 75; // - + // int ASTORE_1 = 76; // - + // int ASTORE_2 = 77; // - + // int ASTORE_3 = 78; // - + int IASTORE = 79; // visitInsn + int LASTORE = 80; // - + int FASTORE = 81; // - + int DASTORE = 82; // - + int AASTORE = 83; // - + int BASTORE = 84; // - + int CASTORE = 85; // - + int SASTORE = 86; // - + int POP = 87; // - + int POP2 = 88; // - + int DUP = 89; // - + int DUP_X1 = 90; // - + int DUP_X2 = 91; // - + int DUP2 = 92; // - + int DUP2_X1 = 93; // - + int DUP2_X2 = 94; // - + int SWAP = 95; // - + int IADD = 96; // - + int LADD = 97; // - + int FADD = 98; // - + int DADD = 99; // - + int ISUB = 100; // - + int LSUB = 101; // - + int FSUB = 102; // - + int DSUB = 103; // - + int IMUL = 104; // - + int LMUL = 105; // - + int FMUL = 106; // - + int DMUL = 107; // - + int IDIV = 108; // - + int LDIV = 109; // - + int FDIV = 110; // - + int DDIV = 111; // - + int IREM = 112; // - + int LREM = 113; // - + int FREM = 114; // - + int DREM = 115; // - + int INEG = 116; // - + int LNEG = 117; // - + int FNEG = 118; // - + int DNEG = 119; // - + int ISHL = 120; // - + int LSHL = 121; // - + int ISHR = 122; // - + int LSHR = 123; // - + int IUSHR = 124; // - + int LUSHR = 125; // - + int IAND = 126; // - + int LAND = 127; // - + int IOR = 128; // - + int LOR = 129; // - + int IXOR = 130; // - + int LXOR = 131; // - + int IINC = 132; // visitIincInsn + int I2L = 133; // visitInsn + int I2F = 134; // - + int I2D = 135; // - + int L2I = 136; // - + int L2F = 137; // - + int L2D = 138; // - + int F2I = 139; // - + int F2L = 140; // - + int F2D = 141; // - + int D2I = 142; // - + int D2L = 143; // - + int D2F = 144; // - + int I2B = 145; // - + int I2C = 146; // - + int I2S = 147; // - + int LCMP = 148; // - + int FCMPL = 149; // - + int FCMPG = 150; // - + int DCMPL = 151; // - + int DCMPG = 152; // - + int IFEQ = 153; // visitJumpInsn + int IFNE = 154; // - + int IFLT = 155; // - + int IFGE = 156; // - + int IFGT = 157; // - + int IFLE = 158; // - + int IF_ICMPEQ = 159; // - + int IF_ICMPNE = 160; // - + int IF_ICMPLT = 161; // - + int IF_ICMPGE = 162; // - + int IF_ICMPGT = 163; // - + int IF_ICMPLE = 164; // - + int IF_ACMPEQ = 165; // - + int IF_ACMPNE = 166; // - + int GOTO = 167; // - + int JSR = 168; // - + int RET = 169; // visitVarInsn + int TABLESWITCH = 170; // visiTableSwitchInsn + int LOOKUPSWITCH = 171; // visitLookupSwitch + int IRETURN = 172; // visitInsn + int LRETURN = 173; // - + int FRETURN = 174; // - + int DRETURN = 175; // - + int ARETURN = 176; // - + int RETURN = 177; // - + int GETSTATIC = 178; // visitFieldInsn + int PUTSTATIC = 179; // - + int GETFIELD = 180; // - + int PUTFIELD = 181; // - + int INVOKEVIRTUAL = 182; // visitMethodInsn + int INVOKESPECIAL = 183; // - + int INVOKESTATIC = 184; // - + int INVOKEINTERFACE = 185; // - + int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn + int NEW = 187; // visitTypeInsn + int NEWARRAY = 188; // visitIntInsn + int ANEWARRAY = 189; // visitTypeInsn + int ARRAYLENGTH = 190; // visitInsn + int ATHROW = 191; // - + int CHECKCAST = 192; // visitTypeInsn + int INSTANCEOF = 193; // - + int MONITORENTER = 194; // visitInsn + int MONITOREXIT = 195; // - + // int WIDE = 196; // NOT VISITED + int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn + int IFNULL = 198; // visitJumpInsn + int IFNONNULL = 199; // - + // int GOTO_W = 200; // - + // int JSR_W = 201; // - +} diff --git a/blade-core/src/main/java/org/objectweb/asm/Type.java b/blade-core/src/main/java/org/objectweb/asm/Type.java new file mode 100644 index 000000000..6561fea3f --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/Type.java @@ -0,0 +1,896 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.objectweb.asm; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * A Java field or method type. This class can be used to make it easier to + * manipulate type and method descriptors. + * + * @author Eric Bruneton + * @author Chris Nokleberg + */ +public class Type { + + /** + * The sort of the void type. See {@link #getSort getSort}. + */ + public static final int VOID = 0; + + /** + * The sort of the boolean type. See {@link #getSort getSort}. + */ + public static final int BOOLEAN = 1; + + /** + * The sort of the char type. See {@link #getSort getSort}. + */ + public static final int CHAR = 2; + + /** + * The sort of the byte type. See {@link #getSort getSort}. + */ + public static final int BYTE = 3; + + /** + * The sort of the short type. See {@link #getSort getSort}. + */ + public static final int SHORT = 4; + + /** + * The sort of the int type. See {@link #getSort getSort}. + */ + public static final int INT = 5; + + /** + * The sort of the float type. See {@link #getSort getSort}. + */ + public static final int FLOAT = 6; + + /** + * The sort of the long type. See {@link #getSort getSort}. + */ + public static final int LONG = 7; + + /** + * The sort of the double type. See {@link #getSort getSort}. + */ + public static final int DOUBLE = 8; + + /** + * The sort of array reference types. See {@link #getSort getSort}. + */ + public static final int ARRAY = 9; + + /** + * The sort of object reference types. See {@link #getSort getSort}. + */ + public static final int OBJECT = 10; + + /** + * The sort of method types. See {@link #getSort getSort}. + */ + public static final int METHOD = 11; + + /** + * The void type. + */ + public static final Type VOID_TYPE = new Type(VOID, null, ('V' << 24) + | (5 << 16) | (0 << 8) | 0, 1); + + /** + * The boolean type. + */ + public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, null, ('Z' << 24) + | (0 << 16) | (5 << 8) | 1, 1); + + /** + * The char type. + */ + public static final Type CHAR_TYPE = new Type(CHAR, null, ('C' << 24) + | (0 << 16) | (6 << 8) | 1, 1); + + /** + * The byte type. + */ + public static final Type BYTE_TYPE = new Type(BYTE, null, ('B' << 24) + | (0 << 16) | (5 << 8) | 1, 1); + + /** + * The short type. + */ + public static final Type SHORT_TYPE = new Type(SHORT, null, ('S' << 24) + | (0 << 16) | (7 << 8) | 1, 1); + + /** + * The int type. + */ + public static final Type INT_TYPE = new Type(INT, null, ('I' << 24) + | (0 << 16) | (0 << 8) | 1, 1); + + /** + * The float type. + */ + public static final Type FLOAT_TYPE = new Type(FLOAT, null, ('F' << 24) + | (2 << 16) | (2 << 8) | 1, 1); + + /** + * The long type. + */ + public static final Type LONG_TYPE = new Type(LONG, null, ('J' << 24) + | (1 << 16) | (1 << 8) | 2, 1); + + /** + * The double type. + */ + public static final Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D' << 24) + | (3 << 16) | (3 << 8) | 2, 1); + + // ------------------------------------------------------------------------ + // Fields + // ------------------------------------------------------------------------ + + /** + * The sort of this Java type. + */ + private final int sort; + + /** + * A buffer containing the internal name of this Java type. This field is + * only used for reference types. + */ + private final char[] buf; + + /** + * The offset of the internal name of this Java type in {@link #buf buf} or, + * for primitive types, the size, descriptor and getOpcode offsets for this + * type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset + * for IALOAD or IASTORE, byte 3 the offset for all other instructions). + */ + private final int off; + + /** + * The length of the internal name of this Java type. + */ + private final int len; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + /** + * Constructs a reference type. + * + * @param sort + * the sort of the reference type to be constructed. + * @param buf + * a buffer containing the descriptor of the previous type. + * @param off + * the offset of this descriptor in the previous buffer. + * @param len + * the length of this descriptor. + */ + private Type(final int sort, final char[] buf, final int off, final int len) { + this.sort = sort; + this.buf = buf; + this.off = off; + this.len = len; + } + + /** + * Returns the Java type corresponding to the given type descriptor. + * + * @param typeDescriptor + * a field or method type descriptor. + * @return the Java type corresponding to the given type descriptor. + */ + public static Type getType(final String typeDescriptor) { + return getType(typeDescriptor.toCharArray(), 0); + } + + /** + * Returns the Java type corresponding to the given internal name. + * + * @param internalName + * an internal name. + * @return the Java type corresponding to the given internal name. + */ + public static Type getObjectType(final String internalName) { + char[] buf = internalName.toCharArray(); + return new Type(buf[0] == '[' ? ARRAY : OBJECT, buf, 0, buf.length); + } + + /** + * Returns the Java type corresponding to the given method descriptor. + * Equivalent to Type.getType(methodDescriptor). + * + * @param methodDescriptor + * a method descriptor. + * @return the Java type corresponding to the given method descriptor. + */ + public static Type getMethodType(final String methodDescriptor) { + return getType(methodDescriptor.toCharArray(), 0); + } + + /** + * Returns the Java method type corresponding to the given argument and + * return types. + * + * @param returnType + * the return type of the method. + * @param argumentTypes + * the argument types of the method. + * @return the Java type corresponding to the given argument and return + * types. + */ + public static Type getMethodType(final Type returnType, + final Type... argumentTypes) { + return getType(getMethodDescriptor(returnType, argumentTypes)); + } + + /** + * Returns the Java type corresponding to the given class. + * + * @param c + * a class. + * @return the Java type corresponding to the given class. + */ + public static Type getType(final Class c) { + if (c.isPrimitive()) { + if (c == Integer.TYPE) { + return INT_TYPE; + } else if (c == Void.TYPE) { + return VOID_TYPE; + } else if (c == Boolean.TYPE) { + return BOOLEAN_TYPE; + } else if (c == Byte.TYPE) { + return BYTE_TYPE; + } else if (c == Character.TYPE) { + return CHAR_TYPE; + } else if (c == Short.TYPE) { + return SHORT_TYPE; + } else if (c == Double.TYPE) { + return DOUBLE_TYPE; + } else if (c == Float.TYPE) { + return FLOAT_TYPE; + } else /* if (c == Long.TYPE) */{ + return LONG_TYPE; + } + } else { + return getType(getDescriptor(c)); + } + } + + /** + * Returns the Java method type corresponding to the given constructor. + * + * @param c + * a {@link Constructor Constructor} object. + * @return the Java method type corresponding to the given constructor. + */ + public static Type getType(final Constructor c) { + return getType(getConstructorDescriptor(c)); + } + + /** + * Returns the Java method type corresponding to the given method. + * + * @param m + * a {@link Method Method} object. + * @return the Java method type corresponding to the given method. + */ + public static Type getType(final Method m) { + return getType(getMethodDescriptor(m)); + } + + /** + * Returns the Java types corresponding to the argument types of the given + * method descriptor. + * + * @param methodDescriptor + * a method descriptor. + * @return the Java types corresponding to the argument types of the given + * method descriptor. + */ + public static Type[] getArgumentTypes(final String methodDescriptor) { + char[] buf = methodDescriptor.toCharArray(); + int off = 1; + int size = 0; + while (true) { + char car = buf[off++]; + if (car == ')') { + break; + } else if (car == 'L') { + while (buf[off++] != ';') { + } + ++size; + } else if (car != '[') { + ++size; + } + } + Type[] args = new Type[size]; + off = 1; + size = 0; + while (buf[off] != ')') { + args[size] = getType(buf, off); + off += args[size].len + (args[size].sort == OBJECT ? 2 : 0); + size += 1; + } + return args; + } + + /** + * Returns the Java types corresponding to the argument types of the given + * method. + * + * @param method + * a method. + * @return the Java types corresponding to the argument types of the given + * method. + */ + public static Type[] getArgumentTypes(final Method method) { + Class[] classes = method.getParameterTypes(); + Type[] types = new Type[classes.length]; + for (int i = classes.length - 1; i >= 0; --i) { + types[i] = getType(classes[i]); + } + return types; + } + + /** + * Returns the Java type corresponding to the return type of the given + * method descriptor. + * + * @param methodDescriptor + * a method descriptor. + * @return the Java type corresponding to the return type of the given + * method descriptor. + */ + public static Type getReturnType(final String methodDescriptor) { + char[] buf = methodDescriptor.toCharArray(); + return getType(buf, methodDescriptor.indexOf(')') + 1); + } + + /** + * Returns the Java type corresponding to the return type of the given + * method. + * + * @param method + * a method. + * @return the Java type corresponding to the return type of the given + * method. + */ + public static Type getReturnType(final Method method) { + return getType(method.getReturnType()); + } + + /** + * Computes the size of the arguments and of the return value of a method. + * + * @param desc + * the descriptor of a method. + * @return the size of the arguments of the method (plus one for the + * implicit this argument), argSize, and the size of its return + * value, retSize, packed into a single int i = + * (argSize << 2) | retSize (argSize is therefore equal to + * i >> 2, and retSize to i & 0x03). + */ + public static int getArgumentsAndReturnSizes(final String desc) { + int n = 1; + int c = 1; + while (true) { + char car = desc.charAt(c++); + if (car == ')') { + car = desc.charAt(c); + return n << 2 + | (car == 'V' ? 0 : (car == 'D' || car == 'J' ? 2 : 1)); + } else if (car == 'L') { + while (desc.charAt(c++) != ';') { + } + n += 1; + } else if (car == '[') { + while ((car = desc.charAt(c)) == '[') { + ++c; + } + if (car == 'D' || car == 'J') { + n -= 1; + } + } else if (car == 'D' || car == 'J') { + n += 2; + } else { + n += 1; + } + } + } + + /** + * Returns the Java type corresponding to the given type descriptor. For + * method descriptors, buf is supposed to contain nothing more than the + * descriptor itself. + * + * @param buf + * a buffer containing a type descriptor. + * @param off + * the offset of this descriptor in the previous buffer. + * @return the Java type corresponding to the given type descriptor. + */ + private static Type getType(final char[] buf, final int off) { + int len; + switch (buf[off]) { + case 'V': + return VOID_TYPE; + case 'Z': + return BOOLEAN_TYPE; + case 'C': + return CHAR_TYPE; + case 'B': + return BYTE_TYPE; + case 'S': + return SHORT_TYPE; + case 'I': + return INT_TYPE; + case 'F': + return FLOAT_TYPE; + case 'J': + return LONG_TYPE; + case 'D': + return DOUBLE_TYPE; + case '[': + len = 1; + while (buf[off + len] == '[') { + ++len; + } + if (buf[off + len] == 'L') { + ++len; + while (buf[off + len] != ';') { + ++len; + } + } + return new Type(ARRAY, buf, off, len + 1); + case 'L': + len = 1; + while (buf[off + len] != ';') { + ++len; + } + return new Type(OBJECT, buf, off + 1, len - 1); + // case '(': + default: + return new Type(METHOD, buf, off, buf.length - off); + } + } + + // ------------------------------------------------------------------------ + // Accessors + // ------------------------------------------------------------------------ + + /** + * Returns the sort of this Java type. + * + * @return {@link #VOID VOID}, {@link #BOOLEAN BOOLEAN}, {@link #CHAR CHAR}, + * {@link #BYTE BYTE}, {@link #SHORT SHORT}, {@link #INT INT}, + * {@link #FLOAT FLOAT}, {@link #LONG LONG}, {@link #DOUBLE DOUBLE}, + * {@link #ARRAY ARRAY}, {@link #OBJECT OBJECT} or {@link #METHOD + * METHOD}. + */ + public int getSort() { + return sort; + } + + /** + * Returns the number of dimensions of this array type. This method should + * only be used for an array type. + * + * @return the number of dimensions of this array type. + */ + public int getDimensions() { + int i = 1; + while (buf[off + i] == '[') { + ++i; + } + return i; + } + + /** + * Returns the type of the elements of this array type. This method should + * only be used for an array type. + * + * @return Returns the type of the elements of this array type. + */ + public Type getElementType() { + return getType(buf, off + getDimensions()); + } + + /** + * Returns the binary name of the class corresponding to this type. This + * method must not be used on method types. + * + * @return the binary name of the class corresponding to this type. + */ + public String getClassName() { + switch (sort) { + case VOID: + return "void"; + case BOOLEAN: + return "boolean"; + case CHAR: + return "char"; + case BYTE: + return "byte"; + case SHORT: + return "short"; + case INT: + return "int"; + case FLOAT: + return "float"; + case LONG: + return "long"; + case DOUBLE: + return "double"; + case ARRAY: + StringBuilder sb = new StringBuilder(getElementType().getClassName()); + for (int i = getDimensions(); i > 0; --i) { + sb.append("[]"); + } + return sb.toString(); + case OBJECT: + return new String(buf, off, len).replace('/', '.'); + default: + return null; + } + } + + /** + * Returns the internal name of the class corresponding to this object or + * array type. The internal name of a class is its fully qualified name (as + * returned by Class.getName(), where '.' are replaced by '/'. This method + * should only be used for an object or array type. + * + * @return the internal name of the class corresponding to this object type. + */ + public String getInternalName() { + return new String(buf, off, len); + } + + /** + * Returns the argument types of methods of this type. This method should + * only be used for method types. + * + * @return the argument types of methods of this type. + */ + public Type[] getArgumentTypes() { + return getArgumentTypes(getDescriptor()); + } + + /** + * Returns the return type of methods of this type. This method should only + * be used for method types. + * + * @return the return type of methods of this type. + */ + public Type getReturnType() { + return getReturnType(getDescriptor()); + } + + /** + * Returns the size of the arguments and of the return value of methods of + * this type. This method should only be used for method types. + * + * @return the size of the arguments (plus one for the implicit this + * argument), argSize, and the size of the return value, retSize, + * packed into a single + * int i = (argSize << 2) | retSize + * (argSize is therefore equal to i >> 2, + * and retSize to i & 0x03). + */ + public int getArgumentsAndReturnSizes() { + return getArgumentsAndReturnSizes(getDescriptor()); + } + + // ------------------------------------------------------------------------ + // Conversion to type descriptors + // ------------------------------------------------------------------------ + + /** + * Returns the descriptor corresponding to this Java type. + * + * @return the descriptor corresponding to this Java type. + */ + public String getDescriptor() { + StringBuilder buf = new StringBuilder(); + getDescriptor(buf); + return buf.toString(); + } + + /** + * Returns the descriptor corresponding to the given argument and return + * types. + * + * @param returnType + * the return type of the method. + * @param argumentTypes + * the argument types of the method. + * @return the descriptor corresponding to the given argument and return + * types. + */ + public static String getMethodDescriptor(final Type returnType, + final Type... argumentTypes) { + StringBuilder buf = new StringBuilder(); + buf.append('('); + for (int i = 0; i < argumentTypes.length; ++i) { + argumentTypes[i].getDescriptor(buf); + } + buf.append(')'); + returnType.getDescriptor(buf); + return buf.toString(); + } + + /** + * Appends the descriptor corresponding to this Java type to the given + * string buffer. + * + * @param buf + * the string buffer to which the descriptor must be appended. + */ + private void getDescriptor(final StringBuilder buf) { + if (this.buf == null) { + // descriptor is in byte 3 of 'off' for primitive types (buf == + // null) + buf.append((char) ((off & 0xFF000000) >>> 24)); + } else if (sort == OBJECT) { + buf.append('L'); + buf.append(this.buf, off, len); + buf.append(';'); + } else { // sort == ARRAY || sort == METHOD + buf.append(this.buf, off, len); + } + } + + // ------------------------------------------------------------------------ + // Direct conversion from classes to type descriptors, + // without intermediate Type objects + // ------------------------------------------------------------------------ + + /** + * Returns the internal name of the given class. The internal name of a + * class is its fully qualified name, as returned by Class.getName(), where + * '.' are replaced by '/'. + * + * @param c + * an object or array class. + * @return the internal name of the given class. + */ + public static String getInternalName(final Class c) { + return c.getName().replace('.', '/'); + } + + /** + * Returns the descriptor corresponding to the given Java type. + * + * @param c + * an object class, a primitive class or an array class. + * @return the descriptor corresponding to the given class. + */ + public static String getDescriptor(final Class c) { + StringBuilder buf = new StringBuilder(); + getDescriptor(buf, c); + return buf.toString(); + } + + /** + * Returns the descriptor corresponding to the given constructor. + * + * @param c + * a {@link Constructor Constructor} object. + * @return the descriptor of the given constructor. + */ + public static String getConstructorDescriptor(final Constructor c) { + Class[] parameters = c.getParameterTypes(); + StringBuilder buf = new StringBuilder(); + buf.append('('); + for (int i = 0; i < parameters.length; ++i) { + getDescriptor(buf, parameters[i]); + } + return buf.append(")V").toString(); + } + + /** + * Returns the descriptor corresponding to the given method. + * + * @param m + * a {@link Method Method} object. + * @return the descriptor of the given method. + */ + public static String getMethodDescriptor(final Method m) { + Class[] parameters = m.getParameterTypes(); + StringBuilder buf = new StringBuilder(); + buf.append('('); + for (int i = 0; i < parameters.length; ++i) { + getDescriptor(buf, parameters[i]); + } + buf.append(')'); + getDescriptor(buf, m.getReturnType()); + return buf.toString(); + } + + /** + * Appends the descriptor of the given class to the given string buffer. + * + * @param buf + * the string buffer to which the descriptor must be appended. + * @param c + * the class whose descriptor must be computed. + */ + private static void getDescriptor(final StringBuilder buf, final Class c) { + Class d = c; + while (true) { + if (d.isPrimitive()) { + char car; + if (d == Integer.TYPE) { + car = 'I'; + } else if (d == Void.TYPE) { + car = 'V'; + } else if (d == Boolean.TYPE) { + car = 'Z'; + } else if (d == Byte.TYPE) { + car = 'B'; + } else if (d == Character.TYPE) { + car = 'C'; + } else if (d == Short.TYPE) { + car = 'S'; + } else if (d == Double.TYPE) { + car = 'D'; + } else if (d == Float.TYPE) { + car = 'F'; + } else /* if (d == Long.TYPE) */{ + car = 'J'; + } + buf.append(car); + return; + } else if (d.isArray()) { + buf.append('['); + d = d.getComponentType(); + } else { + buf.append('L'); + String name = d.getName(); + int len = name.length(); + for (int i = 0; i < len; ++i) { + char car = name.charAt(i); + buf.append(car == '.' ? '/' : car); + } + buf.append(';'); + return; + } + } + } + + // ------------------------------------------------------------------------ + // Corresponding size and opcodes + // ------------------------------------------------------------------------ + + /** + * Returns the size of values of this type. This method must not be used for + * method types. + * + * @return the size of values of this type, i.e., 2 for long and + * double, 0 for void and 1 otherwise. + */ + public int getSize() { + // the size is in byte 0 of 'off' for primitive types (buf == null) + return buf == null ? (off & 0xFF) : 1; + } + + /** + * Returns a JVM instruction opcode adapted to this Java type. This method + * must not be used for method types. + * + * @param opcode + * a JVM instruction opcode. This opcode must be one of ILOAD, + * ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, + * ISHL, ISHR, IUSHR, IAND, IOR, IXOR and IRETURN. + * @return an opcode that is similar to the given opcode, but adapted to + * this Java type. For example, if this type is float and + * opcode is IRETURN, this method returns FRETURN. + */ + public int getOpcode(final int opcode) { + if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) { + // the offset for IALOAD or IASTORE is in byte 1 of 'off' for + // primitive types (buf == null) + return opcode + (buf == null ? (off & 0xFF00) >> 8 : 4); + } else { + // the offset for other instructions is in byte 2 of 'off' for + // primitive types (buf == null) + return opcode + (buf == null ? (off & 0xFF0000) >> 16 : 4); + } + } + + // ------------------------------------------------------------------------ + // Equals, hashCode and toString + // ------------------------------------------------------------------------ + + /** + * Tests if the given object is equal to this type. + * + * @param o + * the object to be compared to this type. + * @return true if the given object is equal to this type. + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Type)) { + return false; + } + Type t = (Type) o; + if (sort != t.sort) { + return false; + } + if (sort >= ARRAY) { + if (len != t.len) { + return false; + } + for (int i = off, j = t.off, end = i + len; i < end; i++, j++) { + if (buf[i] != t.buf[j]) { + return false; + } + } + } + return true; + } + + /** + * Returns a hash code value for this type. + * + * @return a hash code value for this type. + */ + @Override + public int hashCode() { + int hc = 13 * sort; + if (sort >= ARRAY) { + for (int i = off, end = i + len; i < end; i++) { + hc = 17 * (hc + buf[i]); + } + } + return hc; + } + + /** + * Returns a string representation of this type. + * + * @return the descriptor of this type. + */ + @Override + public String toString() { + return getDescriptor(); + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/TypePath.java b/blade-core/src/main/java/org/objectweb/asm/TypePath.java new file mode 100644 index 000000000..d9c99b106 --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/TypePath.java @@ -0,0 +1,196 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2013 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.objectweb.asm; + +/** + * The path to a type argument, wildcard bound, array element type, or static + * inner type within an enclosing type. + * + * @author Eric Bruneton + */ +public class TypePath { + + /** + * A type path step that steps into the element type of an array type. See + * {@link #getStep getStep}. + */ + public final static int ARRAY_ELEMENT = 0; + + /** + * A type path step that steps into the nested type of a class type. See + * {@link #getStep getStep}. + */ + public final static int INNER_TYPE = 1; + + /** + * A type path step that steps into the bound of a wildcard type. See + * {@link #getStep getStep}. + */ + public final static int WILDCARD_BOUND = 2; + + /** + * A type path step that steps into a type argument of a generic type. See + * {@link #getStep getStep}. + */ + public final static int TYPE_ARGUMENT = 3; + + /** + * The byte array where the path is stored, in Java class file format. + */ + byte[] b; + + /** + * The offset of the first byte of the type path in 'b'. + */ + int offset; + + /** + * Creates a new type path. + * + * @param b + * the byte array containing the type path in Java class file + * format. + * @param offset + * the offset of the first byte of the type path in 'b'. + */ + TypePath(byte[] b, int offset) { + this.b = b; + this.offset = offset; + } + + /** + * Returns the length of this path. + * + * @return the length of this path. + */ + public int getLength() { + return b[offset]; + } + + /** + * Returns the value of the given step of this path. + * + * @param index + * an index between 0 and {@link #getLength()}, exclusive. + * @return {@link #ARRAY_ELEMENT ARRAY_ELEMENT}, {@link #INNER_TYPE + * INNER_TYPE}, {@link #WILDCARD_BOUND WILDCARD_BOUND}, or + * {@link #TYPE_ARGUMENT TYPE_ARGUMENT}. + */ + public int getStep(int index) { + return b[offset + 2 * index + 1]; + } + + /** + * Returns the index of the type argument that the given step is stepping + * into. This method should only be used for steps whose value is + * {@link #TYPE_ARGUMENT TYPE_ARGUMENT}. + * + * @param index + * an index between 0 and {@link #getLength()}, exclusive. + * @return the index of the type argument that the given step is stepping + * into. + */ + public int getStepArgument(int index) { + return b[offset + 2 * index + 2]; + } + + /** + * Converts a type path in string form, in the format used by + * {@link #toString()}, into a TypePath object. + * + * @param typePath + * a type path in string form, in the format used by + * {@link #toString()}. May be null or empty. + * @return the corresponding TypePath object, or null if the path is empty. + */ + public static TypePath fromString(final String typePath) { + if (typePath == null || typePath.length() == 0) { + return null; + } + int n = typePath.length(); + ByteVector out = new ByteVector(n); + out.putByte(0); + for (int i = 0; i < n;) { + char c = typePath.charAt(i++); + if (c == '[') { + out.put11(ARRAY_ELEMENT, 0); + } else if (c == '.') { + out.put11(INNER_TYPE, 0); + } else if (c == '*') { + out.put11(WILDCARD_BOUND, 0); + } else if (c >= '0' && c <= '9') { + int typeArg = c - '0'; + while (i < n && (c = typePath.charAt(i)) >= '0' && c <= '9') { + typeArg = typeArg * 10 + c - '0'; + i += 1; + } + if (i < n && typePath.charAt(i) == ';') { + i += 1; + } + out.put11(TYPE_ARGUMENT, typeArg); + } + } + out.data[0] = (byte) (out.length / 2); + return new TypePath(out.data, 0); + } + + /** + * Returns a string representation of this type path. {@link #ARRAY_ELEMENT + * ARRAY_ELEMENT} steps are represented with '[', {@link #INNER_TYPE + * INNER_TYPE} steps with '.', {@link #WILDCARD_BOUND WILDCARD_BOUND} steps + * with '*' and {@link #TYPE_ARGUMENT TYPE_ARGUMENT} steps with their type + * argument index in decimal form followed by ';'. + */ + @Override + public String toString() { + int length = getLength(); + StringBuilder result = new StringBuilder(length * 2); + for (int i = 0; i < length; ++i) { + switch (getStep(i)) { + case ARRAY_ELEMENT: + result.append('['); + break; + case INNER_TYPE: + result.append('.'); + break; + case WILDCARD_BOUND: + result.append('*'); + break; + case TYPE_ARGUMENT: + result.append(getStepArgument(i)).append(';'); + break; + default: + result.append('_'); + } + } + return result.toString(); + } +} diff --git a/blade-core/src/main/java/org/objectweb/asm/TypeReference.java b/blade-core/src/main/java/org/objectweb/asm/TypeReference.java new file mode 100644 index 000000000..dff76c0b3 --- /dev/null +++ b/blade-core/src/main/java/org/objectweb/asm/TypeReference.java @@ -0,0 +1,452 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2013 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.objectweb.asm; + +/** + * A reference to a type appearing in a class, field or method declaration, or + * on an instruction. Such a reference designates the part of the class where + * the referenced type is appearing (e.g. an 'extends', 'implements' or 'throws' + * clause, a 'new' instruction, a 'catch' clause, a type cast, a local variable + * declaration, etc). + * + * @author Eric Bruneton + */ +public class TypeReference { + + /** + * The sort of type references that target a type parameter of a generic + * class. See {@link #getSort getSort}. + */ + public final static int CLASS_TYPE_PARAMETER = 0x00; + + /** + * The sort of type references that target a type parameter of a generic + * method. See {@link #getSort getSort}. + */ + public final static int METHOD_TYPE_PARAMETER = 0x01; + + /** + * The sort of type references that target the super class of a class or one + * of the interfaces it implements. See {@link #getSort getSort}. + */ + public final static int CLASS_EXTENDS = 0x10; + + /** + * The sort of type references that target a bound of a type parameter of a + * generic class. See {@link #getSort getSort}. + */ + public final static int CLASS_TYPE_PARAMETER_BOUND = 0x11; + + /** + * The sort of type references that target a bound of a type parameter of a + * generic method. See {@link #getSort getSort}. + */ + public final static int METHOD_TYPE_PARAMETER_BOUND = 0x12; + + /** + * The sort of type references that target the type of a field. See + * {@link #getSort getSort}. + */ + public final static int FIELD = 0x13; + + /** + * The sort of type references that target the return type of a method. See + * {@link #getSort getSort}. + */ + public final static int METHOD_RETURN = 0x14; + + /** + * The sort of type references that target the receiver type of a method. + * See {@link #getSort getSort}. + */ + public final static int METHOD_RECEIVER = 0x15; + + /** + * The sort of type references that target the type of a formal parameter of + * a method. See {@link #getSort getSort}. + */ + public final static int METHOD_FORMAL_PARAMETER = 0x16; + + /** + * The sort of type references that target the type of an exception declared + * in the throws clause of a method. See {@link #getSort getSort}. + */ + public final static int THROWS = 0x17; + + /** + * The sort of type references that target the type of a local variable in a + * method. See {@link #getSort getSort}. + */ + public final static int LOCAL_VARIABLE = 0x40; + + /** + * The sort of type references that target the type of a resource variable + * in a method. See {@link #getSort getSort}. + */ + public final static int RESOURCE_VARIABLE = 0x41; + + /** + * The sort of type references that target the type of the exception of a + * 'catch' clause in a method. See {@link #getSort getSort}. + */ + public final static int EXCEPTION_PARAMETER = 0x42; + + /** + * The sort of type references that target the type declared in an + * 'instanceof' instruction. See {@link #getSort getSort}. + */ + public final static int INSTANCEOF = 0x43; + + /** + * The sort of type references that target the type of the object created by + * a 'new' instruction. See {@link #getSort getSort}. + */ + public final static int NEW = 0x44; + + /** + * The sort of type references that target the receiver type of a + * constructor reference. See {@link #getSort getSort}. + */ + public final static int CONSTRUCTOR_REFERENCE = 0x45; + + /** + * The sort of type references that target the receiver type of a method + * reference. See {@link #getSort getSort}. + */ + public final static int METHOD_REFERENCE = 0x46; + + /** + * The sort of type references that target the type declared in an explicit + * or implicit cast instruction. See {@link #getSort getSort}. + */ + public final static int CAST = 0x47; + + /** + * The sort of type references that target a type parameter of a generic + * constructor in a constructor call. See {@link #getSort getSort}. + */ + public final static int CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT = 0x48; + + /** + * The sort of type references that target a type parameter of a generic + * method in a method call. See {@link #getSort getSort}. + */ + public final static int METHOD_INVOCATION_TYPE_ARGUMENT = 0x49; + + /** + * The sort of type references that target a type parameter of a generic + * constructor in a constructor reference. See {@link #getSort getSort}. + */ + public final static int CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT = 0x4A; + + /** + * The sort of type references that target a type parameter of a generic + * method in a method reference. See {@link #getSort getSort}. + */ + public final static int METHOD_REFERENCE_TYPE_ARGUMENT = 0x4B; + + /** + * The type reference value in Java class file format. + */ + private int value; + + /** + * Creates a new TypeReference. + * + * @param typeRef + * the int encoded value of the type reference, as received in a + * visit method related to type annotations, like + * visitTypeAnnotation. + */ + public TypeReference(int typeRef) { + this.value = typeRef; + } + + /** + * Returns a type reference of the given sort. + * + * @param sort + * {@link #FIELD FIELD}, {@link #METHOD_RETURN METHOD_RETURN}, + * {@link #METHOD_RECEIVER METHOD_RECEIVER}, + * {@link #LOCAL_VARIABLE LOCAL_VARIABLE}, + * {@link #RESOURCE_VARIABLE RESOURCE_VARIABLE}, + * {@link #INSTANCEOF INSTANCEOF}, {@link #NEW NEW}, + * {@link #CONSTRUCTOR_REFERENCE CONSTRUCTOR_REFERENCE}, or + * {@link #METHOD_REFERENCE METHOD_REFERENCE}. + * @return a type reference of the given sort. + */ + public static TypeReference newTypeReference(int sort) { + return new TypeReference(sort << 24); + } + + /** + * Returns a reference to a type parameter of a generic class or method. + * + * @param sort + * {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER} or + * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}. + * @param paramIndex + * the type parameter index. + * @return a reference to the given generic class or method type parameter. + */ + public static TypeReference newTypeParameterReference(int sort, + int paramIndex) { + return new TypeReference((sort << 24) | (paramIndex << 16)); + } + + /** + * Returns a reference to a type parameter bound of a generic class or + * method. + * + * @param sort + * {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER} or + * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}. + * @param paramIndex + * the type parameter index. + * @param boundIndex + * the type bound index within the above type parameters. + * @return a reference to the given generic class or method type parameter + * bound. + */ + public static TypeReference newTypeParameterBoundReference(int sort, + int paramIndex, int boundIndex) { + return new TypeReference((sort << 24) | (paramIndex << 16) + | (boundIndex << 8)); + } + + /** + * Returns a reference to the super class or to an interface of the + * 'implements' clause of a class. + * + * @param itfIndex + * the index of an interface in the 'implements' clause of a + * class, or -1 to reference the super class of the class. + * @return a reference to the given super type of a class. + */ + public static TypeReference newSuperTypeReference(int itfIndex) { + itfIndex &= 0xFFFF; + return new TypeReference((CLASS_EXTENDS << 24) | (itfIndex << 8)); + } + + /** + * Returns a reference to the type of a formal parameter of a method. + * + * @param paramIndex + * the formal parameter index. + * + * @return a reference to the type of the given method formal parameter. + */ + public static TypeReference newFormalParameterReference(int paramIndex) { + return new TypeReference((METHOD_FORMAL_PARAMETER << 24) + | (paramIndex << 16)); + } + + /** + * Returns a reference to the type of an exception, in a 'throws' clause of + * a method. + * + * @param exceptionIndex + * the index of an exception in a 'throws' clause of a method. + * + * @return a reference to the type of the given exception. + */ + public static TypeReference newExceptionReference(int exceptionIndex) { + return new TypeReference((THROWS << 24) | (exceptionIndex << 8)); + } + + /** + * Returns a reference to the type of the exception declared in a 'catch' + * clause of a method. + * + * @param tryCatchBlockIndex + * the index of a try catch block (using the order in which they + * are visited with visitTryCatchBlock). + * + * @return a reference to the type of the given exception. + */ + public static TypeReference newTryCatchReference(int tryCatchBlockIndex) { + return new TypeReference((EXCEPTION_PARAMETER << 24) + | (tryCatchBlockIndex << 8)); + } + + /** + * Returns a reference to the type of a type argument in a constructor or + * method call or reference. + * + * @param sort + * {@link #CAST CAST}, + * {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, + * {@link #METHOD_INVOCATION_TYPE_ARGUMENT + * METHOD_INVOCATION_TYPE_ARGUMENT}, + * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or + * {@link #METHOD_REFERENCE_TYPE_ARGUMENT + * METHOD_REFERENCE_TYPE_ARGUMENT}. + * @param argIndex + * the type argument index. + * + * @return a reference to the type of the given type argument. + */ + public static TypeReference newTypeArgumentReference(int sort, int argIndex) { + return new TypeReference((sort << 24) | argIndex); + } + + /** + * Returns the sort of this type reference. + * + * @return {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER}, + * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}, + * {@link #CLASS_EXTENDS CLASS_EXTENDS}, + * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND}, + * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND}, + * {@link #FIELD FIELD}, {@link #METHOD_RETURN METHOD_RETURN}, + * {@link #METHOD_RECEIVER METHOD_RECEIVER}, + * {@link #METHOD_FORMAL_PARAMETER METHOD_FORMAL_PARAMETER}, + * {@link #THROWS THROWS}, {@link #LOCAL_VARIABLE LOCAL_VARIABLE}, + * {@link #RESOURCE_VARIABLE RESOURCE_VARIABLE}, + * {@link #EXCEPTION_PARAMETER EXCEPTION_PARAMETER}, + * {@link #INSTANCEOF INSTANCEOF}, {@link #NEW NEW}, + * {@link #CONSTRUCTOR_REFERENCE CONSTRUCTOR_REFERENCE}, + * {@link #METHOD_REFERENCE METHOD_REFERENCE}, {@link #CAST CAST}, + * {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, + * {@link #METHOD_INVOCATION_TYPE_ARGUMENT + * METHOD_INVOCATION_TYPE_ARGUMENT}, + * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or + * {@link #METHOD_REFERENCE_TYPE_ARGUMENT + * METHOD_REFERENCE_TYPE_ARGUMENT}. + */ + public int getSort() { + return value >>> 24; + } + + /** + * Returns the index of the type parameter referenced by this type + * reference. This method must only be used for type references whose sort + * is {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER}, + * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}, + * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND} or + * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND}. + * + * @return a type parameter index. + */ + public int getTypeParameterIndex() { + return (value & 0x00FF0000) >> 16; + } + + /** + * Returns the index of the type parameter bound, within the type parameter + * {@link #getTypeParameterIndex}, referenced by this type reference. This + * method must only be used for type references whose sort is + * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND} or + * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND}. + * + * @return a type parameter bound index. + */ + public int getTypeParameterBoundIndex() { + return (value & 0x0000FF00) >> 8; + } + + /** + * Returns the index of the "super type" of a class that is referenced by + * this type reference. This method must only be used for type references + * whose sort is {@link #CLASS_EXTENDS CLASS_EXTENDS}. + * + * @return the index of an interface in the 'implements' clause of a class, + * or -1 if this type reference references the type of the super + * class. + */ + public int getSuperTypeIndex() { + return (short) ((value & 0x00FFFF00) >> 8); + } + + /** + * Returns the index of the formal parameter whose type is referenced by + * this type reference. This method must only be used for type references + * whose sort is {@link #METHOD_FORMAL_PARAMETER METHOD_FORMAL_PARAMETER}. + * + * @return a formal parameter index. + */ + public int getFormalParameterIndex() { + return (value & 0x00FF0000) >> 16; + } + + /** + * Returns the index of the exception, in a 'throws' clause of a method, + * whose type is referenced by this type reference. This method must only be + * used for type references whose sort is {@link #THROWS THROWS}. + * + * @return the index of an exception in the 'throws' clause of a method. + */ + public int getExceptionIndex() { + return (value & 0x00FFFF00) >> 8; + } + + /** + * Returns the index of the try catch block (using the order in which they + * are visited with visitTryCatchBlock), whose 'catch' type is referenced by + * this type reference. This method must only be used for type references + * whose sort is {@link #EXCEPTION_PARAMETER EXCEPTION_PARAMETER} . + * + * @return the index of an exception in the 'throws' clause of a method. + */ + public int getTryCatchBlockIndex() { + return (value & 0x00FFFF00) >> 8; + } + + /** + * Returns the index of the type argument referenced by this type reference. + * This method must only be used for type references whose sort is + * {@link #CAST CAST}, {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, + * {@link #METHOD_INVOCATION_TYPE_ARGUMENT METHOD_INVOCATION_TYPE_ARGUMENT}, + * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or + * {@link #METHOD_REFERENCE_TYPE_ARGUMENT METHOD_REFERENCE_TYPE_ARGUMENT}. + * + * @return a type parameter index. + */ + public int getTypeArgumentIndex() { + return value & 0xFF; + } + + /** + * Returns the int encoded value of this type reference, suitable for use in + * visit methods related to type annotations, like visitTypeAnnotation. + * + * @return the int encoded value of this type reference. + */ + public int getValue() { + return value; + } +} diff --git a/blade-core/src/main/java/standard.flf b/blade-core/src/main/java/standard.flf new file mode 100644 index 000000000..b1fc5eb17 --- /dev/null +++ b/blade-core/src/main/java/standard.flf @@ -0,0 +1,2227 @@ +flf2a$ 6 5 16 15 11 0 24463 229 +Standard by Glenn Chappell & Ian Chai 3/93 -- based on Frank's .sig +Includes ISO Latin-1 +figlet release 2.1 -- 12 Aug 1994 +Modified for figlet 2.2 by John Cowan + to add Latin-{2,3,4,5} support (Unicode U+0100-017F). +Permission is hereby given to modify this font, as long as the +modifier's name is placed on a comment line. + +Modified by Paul Burton 12/96 to include new parameter +supported by FIGlet and FIGWin. May also be slightly modified for better use +of new full-width/kern/smush alternatives, but default output is NOT changed. + $@ + $@ + $@ + $@ + $@ + $@@ + _ @ + | |@ + | |@ + |_|@ + (_)@ + @@ + _ _ @ + ( | )@ + V V @ + $ @ + $ @ + @@ + _ _ @ + _| || |_ @ + |_ .. _|@ + |_ _|@ + |_||_| @ + @@ + _ @ + | | @ + / __)@ + \__ \@ + ( /@ + |_| @@ + _ __@ + (_)/ /@ + / / @ + / /_ @ + /_/(_)@ + @@ + ___ @ + ( _ ) @ + / _ \/\@ + | (_> <@ + \___/\/@ + @@ + _ @ + ( )@ + |/ @ + $ @ + $ @ + @@ + __@ + / /@ + | | @ + | | @ + | | @ + \_\@@ + __ @ + \ \ @ + | |@ + | |@ + | |@ + /_/ @@ + @ + __/\__@ + \ /@ + /_ _\@ + \/ @ + @@ + @ + _ @ + _| |_ @ + |_ _|@ + |_| @ + @@ + @ + @ + @ + _ @ + ( )@ + |/ @@ + @ + @ + _____ @ + |_____|@ + $ @ + @@ + @ + @ + @ + _ @ + (_)@ + @@ + __@ + / /@ + / / @ + / / @ + /_/ @ + @@ + ___ @ + / _ \ @ + | | | |@ + | |_| |@ + \___/ @ + @@ + _ @ + / |@ + | |@ + | |@ + |_|@ + @@ + ____ @ + |___ \ @ + __) |@ + / __/ @ + |_____|@ + @@ + _____ @ + |___ / @ + |_ \ @ + ___) |@ + |____/ @ + @@ + _ _ @ + | || | @ + | || |_ @ + |__ _|@ + |_| @ + @@ + ____ @ + | ___| @ + |___ \ @ + ___) |@ + |____/ @ + @@ + __ @ + / /_ @ + | '_ \ @ + | (_) |@ + \___/ @ + @@ + _____ @ + |___ |@ + / / @ + / / @ + /_/ @ + @@ + ___ @ + ( _ ) @ + / _ \ @ + | (_) |@ + \___/ @ + @@ + ___ @ + / _ \ @ + | (_) |@ + \__, |@ + /_/ @ + @@ + @ + _ @ + (_)@ + _ @ + (_)@ + @@ + @ + _ @ + (_)@ + _ @ + ( )@ + |/ @@ + __@ + / /@ + / / @ + \ \ @ + \_\@ + @@ + @ + _____ @ + |_____|@ + |_____|@ + $ @ + @@ + __ @ + \ \ @ + \ \@ + / /@ + /_/ @ + @@ + ___ @ + |__ \@ + / /@ + |_| @ + (_) @ + @@ + ____ @ + / __ \ @ + / / _` |@ + | | (_| |@ + \ \__,_|@ + \____/ @@ + _ @ + / \ @ + / _ \ @ + / ___ \ @ + /_/ \_\@ + @@ + ____ @ + | __ ) @ + | _ \ @ + | |_) |@ + |____/ @ + @@ + ____ @ + / ___|@ + | | @ + | |___ @ + \____|@ + @@ + ____ @ + | _ \ @ + | | | |@ + | |_| |@ + |____/ @ + @@ + _____ @ + | ____|@ + | _| @ + | |___ @ + |_____|@ + @@ + _____ @ + | ___|@ + | |_ @ + | _| @ + |_| @ + @@ + ____ @ + / ___|@ + | | _ @ + | |_| |@ + \____|@ + @@ + _ _ @ + | | | |@ + | |_| |@ + | _ |@ + |_| |_|@ + @@ + ___ @ + |_ _|@ + | | @ + | | @ + |___|@ + @@ + _ @ + | |@ + _ | |@ + | |_| |@ + \___/ @ + @@ + _ __@ + | |/ /@ + | ' / @ + | . \ @ + |_|\_\@ + @@ + _ @ + | | @ + | | @ + | |___ @ + |_____|@ + @@ + __ __ @ + | \/ |@ + | |\/| |@ + | | | |@ + |_| |_|@ + @@ + _ _ @ + | \ | |@ + | \| |@ + | |\ |@ + |_| \_|@ + @@ + ___ @ + / _ \ @ + | | | |@ + | |_| |@ + \___/ @ + @@ + ____ @ + | _ \ @ + | |_) |@ + | __/ @ + |_| @ + @@ + ___ @ + / _ \ @ + | | | |@ + | |_| |@ + \__\_\@ + @@ + ____ @ + | _ \ @ + | |_) |@ + | _ < @ + |_| \_\@ + @@ + ____ @ + / ___| @ + \___ \ @ + ___) |@ + |____/ @ + @@ + _____ @ + |_ _|@ + | | @ + | | @ + |_| @ + @@ + _ _ @ + | | | |@ + | | | |@ + | |_| |@ + \___/ @ + @@ + __ __@ + \ \ / /@ + \ \ / / @ + \ V / @ + \_/ @ + @@ + __ __@ + \ \ / /@ + \ \ /\ / / @ + \ V V / @ + \_/\_/ @ + @@ + __ __@ + \ \/ /@ + \ / @ + / \ @ + /_/\_\@ + @@ + __ __@ + \ \ / /@ + \ V / @ + | | @ + |_| @ + @@ + _____@ + |__ /@ + / / @ + / /_ @ + /____|@ + @@ + __ @ + | _|@ + | | @ + | | @ + | | @ + |__|@@ + __ @ + \ \ @ + \ \ @ + \ \ @ + \_\@ + @@ + __ @ + |_ |@ + | |@ + | |@ + | |@ + |__|@@ + /\ @ + |/\|@ + $ @ + $ @ + $ @ + @@ + @ + @ + @ + @ + _____ @ + |_____|@@ + _ @ + ( )@ + \|@ + $ @ + $ @ + @@ + @ + __ _ @ + / _` |@ + | (_| |@ + \__,_|@ + @@ + _ @ + | |__ @ + | '_ \ @ + | |_) |@ + |_.__/ @ + @@ + @ + ___ @ + / __|@ + | (__ @ + \___|@ + @@ + _ @ + __| |@ + / _` |@ + | (_| |@ + \__,_|@ + @@ + @ + ___ @ + / _ \@ + | __/@ + \___|@ + @@ + __ @ + / _|@ + | |_ @ + | _|@ + |_| @ + @@ + @ + __ _ @ + / _` |@ + | (_| |@ + \__, |@ + |___/ @@ + _ @ + | |__ @ + | '_ \ @ + | | | |@ + |_| |_|@ + @@ + _ @ + (_)@ + | |@ + | |@ + |_|@ + @@ + _ @ + (_)@ + | |@ + | |@ + _/ |@ + |__/ @@ + _ @ + | | __@ + | |/ /@ + | < @ + |_|\_\@ + @@ + _ @ + | |@ + | |@ + | |@ + |_|@ + @@ + @ + _ __ ___ @ + | '_ ` _ \ @ + | | | | | |@ + |_| |_| |_|@ + @@ + @ + _ __ @ + | '_ \ @ + | | | |@ + |_| |_|@ + @@ + @ + ___ @ + / _ \ @ + | (_) |@ + \___/ @ + @@ + @ + _ __ @ + | '_ \ @ + | |_) |@ + | .__/ @ + |_| @@ + @ + __ _ @ + / _` |@ + | (_| |@ + \__, |@ + |_|@@ + @ + _ __ @ + | '__|@ + | | @ + |_| @ + @@ + @ + ___ @ + / __|@ + \__ \@ + |___/@ + @@ + _ @ + | |_ @ + | __|@ + | |_ @ + \__|@ + @@ + @ + _ _ @ + | | | |@ + | |_| |@ + \__,_|@ + @@ + @ + __ __@ + \ \ / /@ + \ V / @ + \_/ @ + @@ + @ + __ __@ + \ \ /\ / /@ + \ V V / @ + \_/\_/ @ + @@ + @ + __ __@ + \ \/ /@ + > < @ + /_/\_\@ + @@ + @ + _ _ @ + | | | |@ + | |_| |@ + \__, |@ + |___/ @@ + @ + ____@ + |_ /@ + / / @ + /___|@ + @@ + __@ + / /@ + | | @ + < < @ + | | @ + \_\@@ + _ @ + | |@ + | |@ + | |@ + | |@ + |_|@@ + __ @ + \ \ @ + | | @ + > >@ + | | @ + /_/ @@ + /\/|@ + |/\/ @ + $ @ + $ @ + $ @ + @@ + _ _ @ + (_)_(_)@ + /_\ @ + / _ \ @ + /_/ \_\@ + @@ + _ _ @ + (_)_(_)@ + / _ \ @ + | |_| |@ + \___/ @ + @@ + _ _ @ + (_) (_)@ + | | | |@ + | |_| |@ + \___/ @ + @@ + _ _ @ + (_)_(_)@ + / _` |@ + | (_| |@ + \__,_|@ + @@ + _ _ @ + (_)_(_)@ + / _ \ @ + | (_) |@ + \___/ @ + @@ + _ _ @ + (_) (_)@ + | | | |@ + | |_| |@ + \__,_|@ + @@ + ___ @ + / _ \@ + | |/ /@ + | |\ \@ + | ||_/@ + |_| @@ +160 NO-BREAK SPACE + $@ + $@ + $@ + $@ + $@ + $@@ +161 INVERTED EXCLAMATION MARK + _ @ + (_)@ + | |@ + | |@ + |_|@ + @@ +162 CENT SIGN + _ @ + | | @ + / __)@ + | (__ @ + \ )@ + |_| @@ +163 POUND SIGN + ___ @ + / ,_\ @ + _| |_ @ + | |___ @ + (_,____|@ + @@ +164 CURRENCY SIGN + /\___/\@ + \ _ /@ + | (_) |@ + / ___ \@ + \/ \/@ + @@ +165 YEN SIGN + __ __ @ + \ V / @ + |__ __|@ + |__ __|@ + |_| @ + @@ +166 BROKEN BAR + _ @ + | |@ + |_|@ + _ @ + | |@ + |_|@@ +167 SECTION SIGN + __ @ + _/ _)@ + / \ \ @ + \ \\ \@ + \ \_/@ + (__/ @@ +168 DIAERESIS + _ _ @ + (_) (_)@ + $ $ @ + $ $ @ + $ $ @ + @@ +169 COPYRIGHT SIGN + _____ @ + / ___ \ @ + / / __| \ @ + | | (__ |@ + \ \___| / @ + \_____/ @@ +170 FEMININE ORDINAL INDICATOR + __ _ @ + / _` |@ + \__,_|@ + |____|@ + $ @ + @@ +171 LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + ____@ + / / /@ + / / / @ + \ \ \ @ + \_\_\@ + @@ +172 NOT SIGN + @ + _____ @ + |___ |@ + |_|@ + $ @ + @@ +173 SOFT HYPHEN + @ + @ + ____ @ + |____|@ + $ @ + @@ +174 REGISTERED SIGN + _____ @ + / ___ \ @ + / | _ \ \ @ + | | / |@ + \ |_|_\ / @ + \_____/ @@ +175 MACRON + _____ @ + |_____|@ + $ @ + $ @ + $ @ + @@ +176 DEGREE SIGN + __ @ + / \ @ + | () |@ + \__/ @ + $ @ + @@ +177 PLUS-MINUS SIGN + _ @ + _| |_ @ + |_ _|@ + _|_|_ @ + |_____|@ + @@ +178 SUPERSCRIPT TWO + ___ @ + |_ )@ + / / @ + /___|@ + $ @ + @@ +179 SUPERSCRIPT THREE + ____@ + |__ /@ + |_ \@ + |___/@ + $ @ + @@ +180 ACUTE ACCENT + __@ + /_/@ + $ @ + $ @ + $ @ + @@ +181 MICRO SIGN + @ + _ _ @ + | | | |@ + | |_| |@ + | ._,_|@ + |_| @@ +182 PILCROW SIGN + _____ @ + / |@ + | (| | |@ + \__ | |@ + |_|_|@ + @@ +183 MIDDLE DOT + @ + _ @ + (_)@ + $ @ + $ @ + @@ +184 CEDILLA + @ + @ + @ + @ + _ @ + )_)@@ +185 SUPERSCRIPT ONE + _ @ + / |@ + | |@ + |_|@ + $ @ + @@ +186 MASCULINE ORDINAL INDICATOR + ___ @ + / _ \@ + \___/@ + |___|@ + $ @ + @@ +187 RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + ____ @ + \ \ \ @ + \ \ \@ + / / /@ + /_/_/ @ + @@ +188 VULGAR FRACTION ONE QUARTER + _ __ @ + / | / / _ @ + | |/ / | | @ + |_/ /|_ _|@ + /_/ |_| @ + @@ +189 VULGAR FRACTION ONE HALF + _ __ @ + / | / /__ @ + | |/ /_ )@ + |_/ / / / @ + /_/ /___|@ + @@ +190 VULGAR FRACTION THREE QUARTERS + ____ __ @ + |__ / / / _ @ + |_ \/ / | | @ + |___/ /|_ _|@ + /_/ |_| @ + @@ +191 INVERTED QUESTION MARK + _ @ + (_) @ + | | @ + / /_ @ + \___|@ + @@ +192 LATIN CAPITAL LETTER A WITH GRAVE + __ @ + \_\ @ + /_\ @ + / _ \ @ + /_/ \_\@ + @@ +193 LATIN CAPITAL LETTER A WITH ACUTE + __ @ + /_/ @ + /_\ @ + / _ \ @ + /_/ \_\@ + @@ +194 LATIN CAPITAL LETTER A WITH CIRCUMFLEX + //\ @ + |/_\| @ + /_\ @ + / _ \ @ + /_/ \_\@ + @@ +195 LATIN CAPITAL LETTER A WITH TILDE + /\/| @ + |/\/ @ + /_\ @ + / _ \ @ + /_/ \_\@ + @@ +196 LATIN CAPITAL LETTER A WITH DIAERESIS + _ _ @ + (_)_(_)@ + /_\ @ + / _ \ @ + /_/ \_\@ + @@ +197 LATIN CAPITAL LETTER A WITH RING ABOVE + _ @ + (o) @ + /_\ @ + / _ \ @ + /_/ \_\@ + @@ +198 LATIN CAPITAL LETTER AE + ______ @ + / ____|@ + / _ _| @ + / __ |___ @ + /_/ |_____|@ + @@ +199 LATIN CAPITAL LETTER C WITH CEDILLA + ____ @ + / ___|@ + | | @ + | |___ @ + \____|@ + )_) @@ +200 LATIN CAPITAL LETTER E WITH GRAVE + __ @ + _\_\_ @ + | ____|@ + | _|_ @ + |_____|@ + @@ +201 LATIN CAPITAL LETTER E WITH ACUTE + __ @ + _/_/_ @ + | ____|@ + | _|_ @ + |_____|@ + @@ +202 LATIN CAPITAL LETTER E WITH CIRCUMFLEX + //\ @ + |/_\| @ + | ____|@ + | _|_ @ + |_____|@ + @@ +203 LATIN CAPITAL LETTER E WITH DIAERESIS + _ _ @ + (_)_(_)@ + | ____|@ + | _|_ @ + |_____|@ + @@ +204 LATIN CAPITAL LETTER I WITH GRAVE + __ @ + \_\ @ + |_ _|@ + | | @ + |___|@ + @@ +205 LATIN CAPITAL LETTER I WITH ACUTE + __ @ + /_/ @ + |_ _|@ + | | @ + |___|@ + @@ +206 LATIN CAPITAL LETTER I WITH CIRCUMFLEX + //\ @ + |/_\|@ + |_ _|@ + | | @ + |___|@ + @@ +207 LATIN CAPITAL LETTER I WITH DIAERESIS + _ _ @ + (_)_(_)@ + |_ _| @ + | | @ + |___| @ + @@ +208 LATIN CAPITAL LETTER ETH + ____ @ + | _ \ @ + _| |_| |@ + |__ __| |@ + |____/ @ + @@ +209 LATIN CAPITAL LETTER N WITH TILDE + /\/|@ + |/\/ @ + | \| |@ + | .` |@ + |_|\_|@ + @@ +210 LATIN CAPITAL LETTER O WITH GRAVE + __ @ + \_\ @ + / _ \ @ + | |_| |@ + \___/ @ + @@ +211 LATIN CAPITAL LETTER O WITH ACUTE + __ @ + /_/ @ + / _ \ @ + | |_| |@ + \___/ @ + @@ +212 LATIN CAPITAL LETTER O WITH CIRCUMFLEX + //\ @ + |/_\| @ + / _ \ @ + | |_| |@ + \___/ @ + @@ +213 LATIN CAPITAL LETTER O WITH TILDE + /\/| @ + |/\/ @ + / _ \ @ + | |_| |@ + \___/ @ + @@ +214 LATIN CAPITAL LETTER O WITH DIAERESIS + _ _ @ + (_)_(_)@ + / _ \ @ + | |_| |@ + \___/ @ + @@ +215 MULTIPLICATION SIGN + @ + @ + /\/\@ + > <@ + \/\/@ + @@ +216 LATIN CAPITAL LETTER O WITH STROKE + ____ @ + / _// @ + | |// |@ + | //| |@ + //__/ @ + @@ +217 LATIN CAPITAL LETTER U WITH GRAVE + __ @ + _\_\_ @ + | | | |@ + | |_| |@ + \___/ @ + @@ +218 LATIN CAPITAL LETTER U WITH ACUTE + __ @ + _/_/_ @ + | | | |@ + | |_| |@ + \___/ @ + @@ +219 LATIN CAPITAL LETTER U WITH CIRCUMFLEX + //\ @ + |/ \| @ + | | | |@ + | |_| |@ + \___/ @ + @@ +220 LATIN CAPITAL LETTER U WITH DIAERESIS + _ _ @ + (_) (_)@ + | | | |@ + | |_| |@ + \___/ @ + @@ +221 LATIN CAPITAL LETTER Y WITH ACUTE + __ @ + __/_/__@ + \ \ / /@ + \ V / @ + |_| @ + @@ +222 LATIN CAPITAL LETTER THORN + _ @ + | |___ @ + | __ \@ + | ___/@ + |_| @ + @@ +223 LATIN SMALL LETTER SHARP S + ___ @ + / _ \@ + | |/ /@ + | |\ \@ + | ||_/@ + |_| @@ +224 LATIN SMALL LETTER A WITH GRAVE + __ @ + \_\_ @ + / _` |@ + | (_| |@ + \__,_|@ + @@ +225 LATIN SMALL LETTER A WITH ACUTE + __ @ + /_/_ @ + / _` |@ + | (_| |@ + \__,_|@ + @@ +226 LATIN SMALL LETTER A WITH CIRCUMFLEX + //\ @ + |/_\| @ + / _` |@ + | (_| |@ + \__,_|@ + @@ +227 LATIN SMALL LETTER A WITH TILDE + /\/| @ + |/\/_ @ + / _` |@ + | (_| |@ + \__,_|@ + @@ +228 LATIN SMALL LETTER A WITH DIAERESIS + _ _ @ + (_)_(_)@ + / _` |@ + | (_| |@ + \__,_|@ + @@ +229 LATIN SMALL LETTER A WITH RING ABOVE + __ @ + (()) @ + / _ '|@ + | (_| |@ + \__,_|@ + @@ +230 LATIN SMALL LETTER AE + @ + __ ____ @ + / _` _ \@ + | (_| __/@ + \__,____|@ + @@ +231 LATIN SMALL LETTER C WITH CEDILLA + @ + ___ @ + / __|@ + | (__ @ + \___|@ + )_) @@ +232 LATIN SMALL LETTER E WITH GRAVE + __ @ + \_\ @ + / _ \@ + | __/@ + \___|@ + @@ +233 LATIN SMALL LETTER E WITH ACUTE + __ @ + /_/ @ + / _ \@ + | __/@ + \___|@ + @@ +234 LATIN SMALL LETTER E WITH CIRCUMFLEX + //\ @ + |/_\|@ + / _ \@ + | __/@ + \___|@ + @@ +235 LATIN SMALL LETTER E WITH DIAERESIS + _ _ @ + (_)_(_)@ + / _ \ @ + | __/ @ + \___| @ + @@ +236 LATIN SMALL LETTER I WITH GRAVE + __ @ + \_\@ + | |@ + | |@ + |_|@ + @@ +237 LATIN SMALL LETTER I WITH ACUTE + __@ + /_/@ + | |@ + | |@ + |_|@ + @@ +238 LATIN SMALL LETTER I WITH CIRCUMFLEX + //\ @ + |/_\|@ + | | @ + | | @ + |_| @ + @@ +239 LATIN SMALL LETTER I WITH DIAERESIS + _ _ @ + (_)_(_)@ + | | @ + | | @ + |_| @ + @@ +240 LATIN SMALL LETTER ETH + /\/\ @ + > < @ + _\/\ |@ + / __` |@ + \____/ @ + @@ +241 LATIN SMALL LETTER N WITH TILDE + /\/| @ + |/\/ @ + | '_ \ @ + | | | |@ + |_| |_|@ + @@ +242 LATIN SMALL LETTER O WITH GRAVE + __ @ + \_\ @ + / _ \ @ + | (_) |@ + \___/ @ + @@ +243 LATIN SMALL LETTER O WITH ACUTE + __ @ + /_/ @ + / _ \ @ + | (_) |@ + \___/ @ + @@ +244 LATIN SMALL LETTER O WITH CIRCUMFLEX + //\ @ + |/_\| @ + / _ \ @ + | (_) |@ + \___/ @ + @@ +245 LATIN SMALL LETTER O WITH TILDE + /\/| @ + |/\/ @ + / _ \ @ + | (_) |@ + \___/ @ + @@ +246 LATIN SMALL LETTER O WITH DIAERESIS + _ _ @ + (_)_(_)@ + / _ \ @ + | (_) |@ + \___/ @ + @@ +247 DIVISION SIGN + @ + _ @ + _(_)_ @ + |_____|@ + (_) @ + @@ +248 LATIN SMALL LETTER O WITH STROKE + @ + ____ @ + / _//\ @ + | (//) |@ + \//__/ @ + @@ +249 LATIN SMALL LETTER U WITH GRAVE + __ @ + _\_\_ @ + | | | |@ + | |_| |@ + \__,_|@ + @@ +250 LATIN SMALL LETTER U WITH ACUTE + __ @ + _/_/_ @ + | | | |@ + | |_| |@ + \__,_|@ + @@ +251 LATIN SMALL LETTER U WITH CIRCUMFLEX + //\ @ + |/ \| @ + | | | |@ + | |_| |@ + \__,_|@ + @@ +252 LATIN SMALL LETTER U WITH DIAERESIS + _ _ @ + (_) (_)@ + | | | |@ + | |_| |@ + \__,_|@ + @@ +253 LATIN SMALL LETTER Y WITH ACUTE + __ @ + _/_/_ @ + | | | |@ + | |_| |@ + \__, |@ + |___/ @@ +254 LATIN SMALL LETTER THORN + _ @ + | |__ @ + | '_ \ @ + | |_) |@ + | .__/ @ + |_| @@ +255 LATIN SMALL LETTER Y WITH DIAERESIS + _ _ @ + (_) (_)@ + | | | |@ + | |_| |@ + \__, |@ + |___/ @@ +0x0100 LATIN CAPITAL LETTER A WITH MACRON + ____ @ + /___/ @ + /_\ @ + / _ \ @ + /_/ \_\@ + @@ +0x0101 LATIN SMALL LETTER A WITH MACRON + ___ @ + /_ _/@ + / _` |@ + | (_| |@ + \__,_|@ + @@ +0x0102 LATIN CAPITAL LETTER A WITH BREVE + _ _ @ + \\_// @ + /_\ @ + / _ \ @ + /_/ \_\@ + @@ +0x0103 LATIN SMALL LETTER A WITH BREVE + \_/ @ + ___ @ + / _` |@ + | (_| |@ + \__,_|@ + @@ +0x0104 LATIN CAPITAL LETTER A WITH OGONEK + @ + _ @ + /_\ @ + / _ \ @ + /_/ \_\@ + (_(@@ +0x0105 LATIN SMALL LETTER A WITH OGONEK + @ + __ _ @ + / _` |@ + | (_| |@ + \__,_|@ + (_(@@ +0x0106 LATIN CAPITAL LETTER C WITH ACUTE + __ @ + _/_/ @ + / ___|@ + | |___ @ + \____|@ + @@ +0x0107 LATIN SMALL LETTER C WITH ACUTE + __ @ + /__/@ + / __|@ + | (__ @ + \___|@ + @@ +0x0108 LATIN CAPITAL LETTER C WITH CIRCUMFLEX + /\ @ + _//\\@ + / ___|@ + | |___ @ + \____|@ + @@ +0x0109 LATIN SMALL LETTER C WITH CIRCUMFLEX + /\ @ + /_\ @ + / __|@ + | (__ @ + \___|@ + @@ +0x010A LATIN CAPITAL LETTER C WITH DOT ABOVE + [] @ + ____ @ + / ___|@ + | |___ @ + \____|@ + @@ +0x010B LATIN SMALL LETTER C WITH DOT ABOVE + [] @ + ___ @ + / __|@ + | (__ @ + \___|@ + @@ +0x010C LATIN CAPITAL LETTER C WITH CARON + \\// @ + _\/_ @ + / ___|@ + | |___ @ + \____|@ + @@ +0x010D LATIN SMALL LETTER C WITH CARON + \\//@ + _\/ @ + / __|@ + | (__ @ + \___|@ + @@ +0x010E LATIN CAPITAL LETTER D WITH CARON + \\// @ + __\/ @ + | _ \ @ + | |_| |@ + |____/ @ + @@ +0x010F LATIN SMALL LETTER D WITH CARON + \/ _ @ + __| |@ + / _` |@ + | (_| |@ + \__,_|@ + @@ +0x0110 LATIN CAPITAL LETTER D WITH STROKE + ____ @ + |_ __ \ @ + /| |/ | |@ + /|_|/_| |@ + |_____/ @ + @@ +0x0111 LATIN SMALL LETTER D WITH STROKE + ---|@ + __| |@ + / _` |@ + | (_| |@ + \__,_|@ + @@ +0x0112 LATIN CAPITAL LETTER E WITH MACRON + ____ @ + /___/ @ + | ____|@ + | _|_ @ + |_____|@ + @@ +0x0113 LATIN SMALL LETTER E WITH MACRON + ____@ + /_ _/@ + / _ \ @ + | __/ @ + \___| @ + @@ +0x0114 LATIN CAPITAL LETTER E WITH BREVE + _ _ @ + \\_// @ + | ____|@ + | _|_ @ + |_____|@ + @@ +0x0115 LATIN SMALL LETTER E WITH BREVE + \\ //@ + -- @ + / _ \ @ + | __/ @ + \___| @ + @@ +0x0116 LATIN CAPITAL LETTER E WITH DOT ABOVE + [] @ + _____ @ + | ____|@ + | _|_ @ + |_____|@ + @@ +0x0117 LATIN SMALL LETTER E WITH DOT ABOVE + [] @ + __ @ + / _ \@ + | __/@ + \___|@ + @@ +0x0118 LATIN CAPITAL LETTER E WITH OGONEK + @ + _____ @ + | ____|@ + | _|_ @ + |_____|@ + (__(@@ +0x0119 LATIN SMALL LETTER E WITH OGONEK + @ + ___ @ + / _ \@ + | __/@ + \___|@ + (_(@@ +0x011A LATIN CAPITAL LETTER E WITH CARON + \\// @ + __\/_ @ + | ____|@ + | _|_ @ + |_____|@ + @@ +0x011B LATIN SMALL LETTER E WITH CARON + \\//@ + \/ @ + / _ \@ + | __/@ + \___|@ + @@ +0x011C LATIN CAPITAL LETTER G WITH CIRCUMFLEX + _/\_ @ + / ___|@ + | | _ @ + | |_| |@ + \____|@ + @@ +0x011D LATIN SMALL LETTER G WITH CIRCUMFLEX + /\ @ + _/_ \@ + / _` |@ + | (_| |@ + \__, |@ + |___/ @@ +0x011E LATIN CAPITAL LETTER G WITH BREVE + _\/_ @ + / ___|@ + | | _ @ + | |_| |@ + \____|@ + @@ +0x011F LATIN SMALL LETTER G WITH BREVE + \___/ @ + __ _ @ + / _` |@ + | (_| |@ + \__, |@ + |___/ @@ +0x0120 LATIN CAPITAL LETTER G WITH DOT ABOVE + _[]_ @ + / ___|@ + | | _ @ + | |_| |@ + \____|@ + @@ +0x0121 LATIN SMALL LETTER G WITH DOT ABOVE + [] @ + __ _ @ + / _` |@ + | (_| |@ + \__, |@ + |___/ @@ +0x0122 LATIN CAPITAL LETTER G WITH CEDILLA + ____ @ + / ___|@ + | | _ @ + | |_| |@ + \____|@ + )__) @@ +0x0123 LATIN SMALL LETTER G WITH CEDILLA + @ + __ _ @ + / _` |@ + | (_| |@ + \__, |@ + |_))))@@ +0x0124 LATIN CAPITAL LETTER H WITH CIRCUMFLEX + _/ \_ @ + | / \ |@ + | |_| |@ + | _ |@ + |_| |_|@ + @@ +0x0125 LATIN SMALL LETTER H WITH CIRCUMFLEX + _ /\ @ + | |//\ @ + | '_ \ @ + | | | |@ + |_| |_|@ + @@ +0x0126 LATIN CAPITAL LETTER H WITH STROKE + _ _ @ + | |=| |@ + | |_| |@ + | _ |@ + |_| |_|@ + @@ +0x0127 LATIN SMALL LETTER H WITH STROKE + _ @ + |=|__ @ + | '_ \ @ + | | | |@ + |_| |_|@ + @@ +0x0128 LATIN CAPITAL LETTER I WITH TILDE + /\//@ + |_ _|@ + | | @ + | | @ + |___|@ + @@ +0x0129 LATIN SMALL LETTER I WITH TILDE + @ + /\/@ + | |@ + | |@ + |_|@ + @@ +0x012A LATIN CAPITAL LETTER I WITH MACRON + /___/@ + |_ _|@ + | | @ + | | @ + |___|@ + @@ +0x012B LATIN SMALL LETTER I WITH MACRON + ____@ + /___/@ + | | @ + | | @ + |_| @ + @@ +0x012C LATIN CAPITAL LETTER I WITH BREVE + \__/@ + |_ _|@ + | | @ + | | @ + |___|@ + @@ +0x012D LATIN SMALL LETTER I WITH BREVE + @ + \_/@ + | |@ + | |@ + |_|@ + @@ +0x012E LATIN CAPITAL LETTER I WITH OGONEK + ___ @ + |_ _|@ + | | @ + | | @ + |___|@ + (__(@@ +0x012F LATIN SMALL LETTER I WITH OGONEK + _ @ + (_) @ + | | @ + | | @ + |_|_@ + (_(@@ +0x0130 LATIN CAPITAL LETTER I WITH DOT ABOVE + _[] @ + |_ _|@ + | | @ + | | @ + |___|@ + @@ +0x0131 LATIN SMALL LETTER DOTLESS I + @ + _ @ + | |@ + | |@ + |_|@ + @@ +0x0132 LATIN CAPITAL LIGATURE IJ + ___ _ @ + |_ _|| |@ + | | | |@ + | |_| |@ + |__|__/ @ + @@ +0x0133 LATIN SMALL LIGATURE IJ + _ _ @ + (_) (_)@ + | | | |@ + | | | |@ + |_|_/ |@ + |__/ @@ +0x0134 LATIN CAPITAL LETTER J WITH CIRCUMFLEX + /\ @ + /_\|@ + _ | | @ + | |_| | @ + \___/ @ + @@ +0x0135 LATIN SMALL LETTER J WITH CIRCUMFLEX + /\@ + /_\@ + | |@ + | |@ + _/ |@ + |__/ @@ +0x0136 LATIN CAPITAL LETTER K WITH CEDILLA + _ _ @ + | |/ / @ + | ' / @ + | . \ @ + |_|\_\ @ + )__)@@ +0x0137 LATIN SMALL LETTER K WITH CEDILLA + _ @ + | | __@ + | |/ /@ + | < @ + |_|\_\@ + )_)@@ +0x0138 LATIN SMALL LETTER KRA + @ + _ __ @ + | |/ \@ + | < @ + |_|\_\@ + @@ +0x0139 LATIN CAPITAL LETTER L WITH ACUTE + _ //@ + | | // @ + | | @ + | |___ @ + |_____|@ + @@ +0x013A LATIN SMALL LETTER L WITH ACUTE + //@ + | |@ + | |@ + | |@ + |_|@ + @@ +0x013B LATIN CAPITAL LETTER L WITH CEDILLA + _ @ + | | @ + | | @ + | |___ @ + |_____|@ + )__)@@ +0x013C LATIN SMALL LETTER L WITH CEDILLA + _ @ + | | @ + | | @ + | | @ + |_| @ + )_)@@ +0x013D LATIN CAPITAL LETTER L WITH CARON + _ \\//@ + | | \/ @ + | | @ + | |___ @ + |_____|@ + @@ +0x013E LATIN SMALL LETTER L WITH CARON + _ \\//@ + | | \/ @ + | | @ + | | @ + |_| @ + @@ +0x013F LATIN CAPITAL LETTER L WITH MIDDLE DOT + _ @ + | | @ + | | [] @ + | |___ @ + |_____|@ + @@ +0x0140 LATIN SMALL LETTER L WITH MIDDLE DOT + _ @ + | | @ + | | []@ + | | @ + |_| @ + @@ +0x0141 LATIN CAPITAL LETTER L WITH STROKE + __ @ + | // @ + |//| @ + // |__ @ + |_____|@ + @@ +0x0142 LATIN SMALL LETTER L WITH STROKE + _ @ + | |@ + |//@ + //|@ + |_|@ + @@ +0x0143 LATIN CAPITAL LETTER N WITH ACUTE + _/ /_ @ + | \ | |@ + | \| |@ + | |\ |@ + |_| \_|@ + @@ +0x0144 LATIN SMALL LETTER N WITH ACUTE + _ @ + _ /_/ @ + | '_ \ @ + | | | |@ + |_| |_|@ + @@ +0x0145 LATIN CAPITAL LETTER N WITH CEDILLA + _ _ @ + | \ | |@ + | \| |@ + | |\ |@ + |_| \_|@ + )_) @@ +0x0146 LATIN SMALL LETTER N WITH CEDILLA + @ + _ __ @ + | '_ \ @ + | | | |@ + |_| |_|@ + )_) @@ +0x0147 LATIN CAPITAL LETTER N WITH CARON + _\/ _ @ + | \ | |@ + | \| |@ + | |\ |@ + |_| \_|@ + @@ +0x0148 LATIN SMALL LETTER N WITH CARON + \\// @ + _\/_ @ + | '_ \ @ + | | | |@ + |_| |_|@ + @@ +0x0149 LATIN SMALL LETTER N PRECEDED BY APOSTROPHE + @ + _ __ @ + ( )| '_\ @ + |/| | | |@ + |_| |_|@ + @@ +0x014A LATIN CAPITAL LETTER ENG + _ _ @ + | \ | |@ + | \| |@ + | |\ |@ + |_| \ |@ + )_)@@ +0x014B LATIN SMALL LETTER ENG + _ __ @ + | '_ \ @ + | | | |@ + |_| | |@ + | |@ + |__ @@ +0x014C LATIN CAPITAL LETTER O WITH MACRON + ____ @ + /_ _/ @ + / _ \ @ + | (_) |@ + \___/ @ + @@ +0x014D LATIN SMALL LETTER O WITH MACRON + ____ @ + /_ _/ @ + / _ \ @ + | (_) |@ + \___/ @ + @@ +0x014E LATIN CAPITAL LETTER O WITH BREVE + \ / @ + _-_ @ + / _ \ @ + | |_| |@ + \___/ @ + @@ +0x014F LATIN SMALL LETTER O WITH BREVE + \ / @ + _-_ @ + / _ \ @ + | |_| |@ + \___/ @ + @@ +0x0150 LATIN CAPITAL LETTER O WITH DOUBLE ACUTE + ___ @ + /_/_/@ + / _ \ @ + | |_| |@ + \___/ @ + @@ +0x0151 LATIN SMALL LETTER O WITH DOUBLE ACUTE + ___ @ + /_/_/@ + / _ \ @ + | |_| |@ + \___/ @ + @@ +0x0152 LATIN CAPITAL LIGATURE OE + ___ ___ @ + / _ \| __|@ + | | | | | @ + | |_| | |__@ + \___/|____@ + @@ +0x0153 LATIN SMALL LIGATURE OE + @ + ___ ___ @ + / _ \ / _ \@ + | (_) | __/@ + \___/ \___|@ + @@ +0x0154 LATIN CAPITAL LETTER R WITH ACUTE + _/_/ @ + | _ \ @ + | |_) |@ + | _ < @ + |_| \_\@ + @@ +0x0155 LATIN SMALL LETTER R WITH ACUTE + __@ + _ /_/@ + | '__|@ + | | @ + |_| @ + @@ +0x0156 LATIN CAPITAL LETTER R WITH CEDILLA + ____ @ + | _ \ @ + | |_) |@ + | _ < @ + |_| \_\@ + )_) @@ +0x0157 LATIN SMALL LETTER R WITH CEDILLA + @ + _ __ @ + | '__|@ + | | @ + |_| @ + )_) @@ +0x0158 LATIN CAPITAL LETTER R WITH CARON + _\_/ @ + | _ \ @ + | |_) |@ + | _ < @ + |_| \_\@ + @@ +0x0159 LATIN SMALL LETTER R WITH CARON + \\// @ + _\/_ @ + | '__|@ + | | @ + |_| @ + @@ +0x015A LATIN CAPITAL LETTER S WITH ACUTE + _/_/ @ + / ___| @ + \___ \ @ + ___) |@ + |____/ @ + @@ +0x015B LATIN SMALL LETTER S WITH ACUTE + __@ + _/_/@ + / __|@ + \__ \@ + |___/@ + @@ +0x015C LATIN CAPITAL LETTER S WITH CIRCUMFLEX + _/\_ @ + / ___| @ + \___ \ @ + ___) |@ + |____/ @ + @@ +0x015D LATIN SMALL LETTER S WITH CIRCUMFLEX + @ + /_\_@ + / __|@ + \__ \@ + |___/@ + @@ +0x015E LATIN CAPITAL LETTER S WITH CEDILLA + ____ @ + / ___| @ + \___ \ @ + ___) |@ + |____/ @ + )__)@@ +0x015F LATIN SMALL LETTER S WITH CEDILLA + @ + ___ @ + / __|@ + \__ \@ + |___/@ + )_)@@ +0x0160 LATIN CAPITAL LETTER S WITH CARON + _\_/ @ + / ___| @ + \___ \ @ + ___) |@ + |____/ @ + @@ +0x0161 LATIN SMALL LETTER S WITH CARON + \\//@ + _\/ @ + / __|@ + \__ \@ + |___/@ + @@ +0x0162 LATIN CAPITAL LETTER T WITH CEDILLA + _____ @ + |_ _|@ + | | @ + | | @ + |_| @ + )__)@@ +0x0163 LATIN SMALL LETTER T WITH CEDILLA + _ @ + | |_ @ + | __|@ + | |_ @ + \__|@ + )_)@@ +0x0164 LATIN CAPITAL LETTER T WITH CARON + _____ @ + |_ _|@ + | | @ + | | @ + |_| @ + @@ +0x0165 LATIN SMALL LETTER T WITH CARON + \/ @ + | |_ @ + | __|@ + | |_ @ + \__|@ + @@ +0x0166 LATIN CAPITAL LETTER T WITH STROKE + _____ @ + |_ _|@ + | | @ + -|-|- @ + |_| @ + @@ +0x0167 LATIN SMALL LETTER T WITH STROKE + _ @ + | |_ @ + | __|@ + |-|_ @ + \__|@ + @@ +0x0168 LATIN CAPITAL LETTER U WITH TILDE + @ + _/\/_ @ + | | | |@ + | |_| |@ + \___/ @ + @@ +0x0169 LATIN SMALL LETTER U WITH TILDE + @ + _/\/_ @ + | | | |@ + | |_| |@ + \__,_|@ + @@ +0x016A LATIN CAPITAL LETTER U WITH MACRON + ____ @ + /__ _/@ + | | | |@ + | |_| |@ + \___/ @ + @@ +0x016B LATIN SMALL LETTER U WITH MACRON + ____ @ + / _ /@ + | | | |@ + | |_| |@ + \__,_|@ + @@ +0x016C LATIN CAPITAL LETTER U WITH BREVE + @ + \_/_ @ + | | | |@ + | |_| |@ + \____|@ + @@ +0x016D LATIN SMALL LETTER U WITH BREVE + @ + \_/_ @ + | | | |@ + | |_| |@ + \__,_|@ + @@ +0x016E LATIN CAPITAL LETTER U WITH RING ABOVE + O @ + __ _ @ + | | | |@ + | |_| |@ + \___/ @ + @@ +0x016F LATIN SMALL LETTER U WITH RING ABOVE + O @ + __ __ @ + | | | |@ + | |_| |@ + \__,_|@ + @@ +0x0170 LATIN CAPITAL LETTER U WITH DOUBLE ACUTE + -- --@ + /_//_/@ + | | | |@ + | |_| |@ + \___/ @ + @@ +0x0171 LATIN SMALL LETTER U WITH DOUBLE ACUTE + ____@ + _/_/_/@ + | | | |@ + | |_| |@ + \__,_|@ + @@ +0x0172 LATIN CAPITAL LETTER U WITH OGONEK + _ _ @ + | | | |@ + | | | |@ + | |_| |@ + \___/ @ + (__(@@ +0x0173 LATIN SMALL LETTER U WITH OGONEK + @ + _ _ @ + | | | |@ + | |_| |@ + \__,_|@ + (_(@@ +0x0174 LATIN CAPITAL LETTER W WITH CIRCUMFLEX + __ /\ __@ + \ \ //\\/ /@ + \ \ /\ / / @ + \ V V / @ + \_/\_/ @ + @@ +0x0175 LATIN SMALL LETTER W WITH CIRCUMFLEX + /\ @ + __ //\\__@ + \ \ /\ / /@ + \ V V / @ + \_/\_/ @ + @@ +0x0176 LATIN CAPITAL LETTER Y WITH CIRCUMFLEX + /\ @ + __//\\ @ + \ \ / /@ + \ V / @ + |_| @ + @@ +0x0177 LATIN SMALL LETTER Y WITH CIRCUMFLEX + /\ @ + //\\ @ + | | | |@ + | |_| |@ + \__, |@ + |___/ @@ +0x0178 LATIN CAPITAL LETTER Y WITH DIAERESIS + [] []@ + __ _@ + \ \ / /@ + \ V / @ + |_| @ + @@ +0x0179 LATIN CAPITAL LETTER Z WITH ACUTE + __/_/@ + |__ /@ + / / @ + / /_ @ + /____|@ + @@ +0x017A LATIN SMALL LETTER Z WITH ACUTE + _ @ + _/_/@ + |_ /@ + / / @ + /___|@ + @@ +0x017B LATIN CAPITAL LETTER Z WITH DOT ABOVE + __[]_@ + |__ /@ + / / @ + / /_ @ + /____|@ + @@ +0x017C LATIN SMALL LETTER Z WITH DOT ABOVE + [] @ + ____@ + |_ /@ + / / @ + /___|@ + @@ +0x017D LATIN CAPITAL LETTER Z WITH CARON + _\_/_@ + |__ /@ + / / @ + / /_ @ + /____|@ + @@ +0x017E LATIN SMALL LETTER Z WITH CARON + \\//@ + _\/_@ + |_ /@ + / / @ + /___|@ + @@ +0x017F LATIN SMALL LETTER LONG S + __ @ + / _|@ + |-| | @ + |-| | @ + |_| @ + @@ +0x02C7 CARON + \\//@ + \/ @ + $@ + $@ + $@ + $@@ +0x02D8 BREVE + \\_//@ + \_/ @ + $@ + $@ + $@ + $@@ +0x02D9 DOT ABOVE + []@ + $@ + $@ + $@ + $@ + $@@ +0x02DB OGONEK + $@ + $@ + $@ + $@ + $@ + )_) @@ +0x02DD DOUBLE ACUTE ACCENT + _ _ @ + /_/_/@ + $@ + $@ + $@ + $@@ diff --git a/blade-core/src/test/java/config.txt b/blade-core/src/test/java/config.txt new file mode 100644 index 000000000..04ea2732e --- /dev/null +++ b/blade-core/src/test/java/config.txt @@ -0,0 +1,33 @@ +# this is config struct + +app + base-package + ioc +mvc + view + 404 + 500 + statics +http + encoding + cached + xss +server + port + timeout + + +=> to properties + +app.base-package +app.ioc + +mvc.view.404 +mvc.view.500 +mvc.statics + +http.encoding +http.cached + +server.port +server.timeout \ No newline at end of file diff --git a/blade-core/src/test/java/log4j.properties b/blade-core/src/test/java/log4j.properties new file mode 100644 index 000000000..aeb65bb06 --- /dev/null +++ b/blade-core/src/test/java/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootLogger = info, stdout + +log4j.appender.stdout = org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout = org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern = [blade-core] %d %-5p [%t] %c | %m%n +log4j.logger.com.blade = debug \ No newline at end of file diff --git a/blade-embed-jetty/pom.xml b/blade-embed-jetty/pom.xml new file mode 100644 index 000000000..ad5fabb0a --- /dev/null +++ b/blade-embed-jetty/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + com.bladejava + blade + 1.0 + + + blade-embed-jetty + ${blade-embed-jetty.version} + blade-embed-jetty + + + + + org.slf4j + slf4j-api + + + + com.bladejava + blade-core + ${blade-core.version} + + + + javax.servlet + javax.servlet-api + ${servlet.version} + provided + + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + org.eclipse.jetty + jetty-webapp + ${jetty.version} + + + + + diff --git a/blade-embed-jetty/src/main/java/com/blade/embedd/EmbedJettyServer.java b/blade-embed-jetty/src/main/java/com/blade/embedd/EmbedJettyServer.java new file mode 100644 index 000000000..b200669c5 --- /dev/null +++ b/blade-embed-jetty/src/main/java/com/blade/embedd/EmbedJettyServer.java @@ -0,0 +1,209 @@ +package com.blade.embedd; + +import com.blade.Blade; +import com.blade.Const; +import com.blade.context.DynamicContext; +import com.blade.context.WebContextListener; +import com.blade.exception.EmbedServerException; +import com.blade.kit.CollectionKit; +import com.blade.kit.StringKit; +import com.blade.kit.base.Config; +import com.blade.mvc.DispatcherServlet; +import org.eclipse.jetty.server.*; +import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.server.handler.HandlerList; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.webapp.WebAppContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.http.HttpServlet; +import java.net.URL; +import java.util.EnumSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import static com.blade.Blade.$; + +public class EmbedJettyServer implements EmbedServer { + + private static final Logger LOGGER = LoggerFactory.getLogger(EmbedJettyServer.class); + + private int port = Const.DEFAULT_PORT; + + private Server server; + + private String classPath; + + private WebAppContext webAppContext; + + private Set staticFolders; + + private Config config = null; + + public EmbedJettyServer() { + System.setProperty("org.apache.jasper.compiler.disablejsr199", "true"); + $().loadAppConf("jetty.properties"); + config = $().config(); + staticFolders = $().configuration().getResources(); + + if(DynamicContext.isJarContext()){ + URL url = EmbedJettyServer.class.getResource("/"); + this.classPath = url.getPath(); +// if(null == url){ +// String urlStr = EmbedJettyServer.class.getResource("").getPath(); +// int pos = urlStr.indexOf("jar!/"); +// if(pos != -1){ +// String jarPath = new File(urlStr.substring(5, pos + 2)).getParent(); +// this.classPath = new File(jarPath).getParent() + File.separator + $().configuration().getClassPath(); +// +// } +// } + LOGGER.info("add classpath: {}", classPath); + } + + $().enableServer(true); + } + + @Override + public void startup(int port) throws EmbedServerException { + this.startup(port, "/", null); + } + + @Override + public void startup(int port, String contextPath) throws EmbedServerException { + this.startup(port, contextPath, null); + } + + @Override + public void setWebRoot(String webRoot) { + webAppContext.setResourceBase(webRoot); + } + + @Override + public void startup(int port, String contextPath, String webRoot) throws EmbedServerException { + this.port = port; + + // Setup Threadpool + QueuedThreadPool threadPool = new QueuedThreadPool(); + + int minThreads = config.getInt("server.jetty.min-threads", 10); + int maxThreads = config.getInt("server.jetty.max-threads", 100); + + threadPool.setMinThreads(minThreads); + threadPool.setMaxThreads(maxThreads); + threadPool.setName("blade-pool"); + + server = new org.eclipse.jetty.server.Server(threadPool); + + // 设置在JVM退出时关闭Jetty的钩子。 + server.setStopAtShutdown(true); + + webAppContext = new WebAppContext(); + webAppContext.setContextPath(contextPath); + webAppContext.setResourceBase(""); + + int securePort = config.getInt("server.jetty.http.secure-port", 8443); + int outputBufferSize = config.getInt("server.jetty.http.output-buffersize", 32768); + int requestHeaderSize = config.getInt("server.jetty.http.request-headersize", 8192); + int responseHeaderSize = config.getInt("server.jetty.http.response-headersize", 8192); + + // HTTP Configuration + HttpConfiguration http_config = new HttpConfiguration(); + http_config.setSecurePort(securePort); + http_config.setOutputBufferSize(outputBufferSize); + http_config.setRequestHeaderSize(requestHeaderSize); + http_config.setResponseHeaderSize(responseHeaderSize); + http_config.setSendServerVersion(true); + http_config.setSendDateHeader(false); + + long idleTimeout = config.getLong("server.jetty.http.idle-timeout", 30000L); + + ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(http_config)); + http.setPort(this.port); + http.setIdleTimeout(idleTimeout); + server.addConnector(http); + + ServletHolder servletHolder = new ServletHolder(DispatcherServlet.class); + servletHolder.setAsyncSupported(false); + servletHolder.setInitOrder(1); + + webAppContext.addEventListener(new WebContextListener()); + webAppContext.addServlet(servletHolder, "/"); + + ServletHolder defaultHolder = new ServletHolder(DefaultServlet.class); + if(StringKit.isNotBlank(classPath)){ + LOGGER.info("add classpath : {}", classPath); + defaultHolder.setInitParameter("resourceBase", classPath); + } + + for(String s : staticFolders){ + webAppContext.addServlet(defaultHolder, s); + } + + try { + + loadServlets(webAppContext); + loadFilters(webAppContext); + + HandlerList handlers = new HandlerList(); + handlers.setHandlers(new Handler[] { webAppContext, new DefaultHandler() }); + server.setHandler(handlers); + server.start(); + LOGGER.info("Blade Server Listen on 0.0.0.0:{}", this.port); + } catch (Exception e) { + throw new EmbedServerException(e); + } + } + + public void loadFilters(WebAppContext webAppContext) throws Exception{ + Map, String[]> filters = Blade.$().filters(); + if(CollectionKit.isNotEmpty(filters)){ + Set, String[]>> entrySet = filters.entrySet(); + for(Entry, String[]> entry : entrySet){ + Class filterClazz = entry.getKey(); + String[] pathSpecs = entry.getValue(); + for(String pathSpec : pathSpecs){ + webAppContext.addFilter(filterClazz, pathSpec, EnumSet.of(DispatcherType.REQUEST)); + } + } + } + } + + public void loadServlets(WebAppContext webAppContext) throws Exception{ + Map, String[]> servlets = Blade.$().servlets(); + if(CollectionKit.isNotEmpty(servlets)){ + Set, String[]>> entrySet = servlets.entrySet(); + for(Entry, String[]> entry : entrySet){ + Class servletClazz = entry.getKey(); + String[] pathSpecs = entry.getValue(); + for(String pathSpec : pathSpecs){ + webAppContext.addServlet(servletClazz, pathSpec); + } + } + } + } + + public void shutdown() throws EmbedServerException { + try { + server.stop(); + } catch (Exception e) { + throw new EmbedServerException(e); + } + } + + @Override + public void join() throws EmbedServerException { + try { + server.join(); + } catch (InterruptedException e) { + throw new EmbedServerException(e); + } + } + +} \ No newline at end of file diff --git a/blade-jetbrick/src/main/java/blade/render/JetbrickRender.java b/blade-jetbrick/src/main/java/blade/render/JetbrickRender.java deleted file mode 100644 index 16f885f0d..000000000 --- a/blade-jetbrick/src/main/java/blade/render/JetbrickRender.java +++ /dev/null @@ -1,173 +0,0 @@ -package blade.render; - -import java.io.IOException; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import jetbrick.io.resource.ResourceNotFoundException; -import jetbrick.template.JetEngine; -import jetbrick.template.JetTemplate; -import blade.Blade; -import blade.BladeWebContext; -import blade.exception.BladeException; - -/** - * Velocity渲染引擎 - * @author biezhi - * - */ -public class JetbrickRender extends Render { - - private JetEngine jetEngine; - - private Properties config; - - /** - * 默认构造函数 - */ - public JetbrickRender() { - config = new Properties(); - config.put("jetx.input.encoding", Blade.encoding()); - config.put("jetx.output.encoding", Blade.encoding()); - config.put("jetx.template.suffix", ".html"); - config.put("jetx.template.loaders", "jetbrick.template.loader.FileSystemResourceLoader"); - - jetEngine = JetEngine.create(config); - } - - /** - * @return 返回JetEngine引擎 - */ - public JetEngine getJetEngine(){ - return jetEngine; - } - - /** - * 清空配置 - */ - public void clean(){ - if(null != config){ - config.clear(); - } - } - - /** - * 添加一个配置 - * @param key 配置键 - * @param value 配置值 - */ - public void put(String key, Object value){ - if(null == config){ - config = new Properties(); - } - config.put(key, value); - } - - /** - * 根据配置文件构造一个JetEngine引擎 - * @param configLocation - * @throws IOException - */ - public JetbrickRender(String configLocation) throws IOException { - jetEngine = JetEngine.create(configLocation); - } - - /** - * 根据构造一个JetEngine引擎 - * @param config Properties配置 - */ - public JetbrickRender(Properties config) { - this.config = config; - jetEngine = JetEngine.create(this.config); - } - - /** - * 手动构造JetEngine引擎 - * @param jetEngine jetEngine引擎 - */ - public JetbrickRender(JetEngine jetEngine) { - this.jetEngine = jetEngine; - } - - /** - * 渲染视图 - */ - @Override - public Object render(String view) { - - HttpServletRequest request = BladeWebContext.servletRequest(); - HttpServletResponse response = BladeWebContext.servletResponse(); - - try { - - view = Blade.webRoot() + disposeView(view); - - JetTemplate template = jetEngine.getTemplate(view); - - Map context = new HashMap(); - - Enumeration attrs = request.getAttributeNames(); - - if(null != attrs && attrs.hasMoreElements()){ - while(attrs.hasMoreElements()){ - String attr = attrs.nextElement(); - context.put(attr, request.getAttribute(attr)); - } - } - - template.render(context, response.getOutputStream()); - } catch (ResourceNotFoundException e) { - render404(response, view); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - - /** - * 渲染视图 - */ - @Override - public Object render(ModelAndView modelAndView) { - HttpServletRequest request = BladeWebContext.servletRequest(); - HttpServletResponse response = BladeWebContext.servletResponse(); - - try { - - if(null == modelAndView){ - throw new BladeException("modelAndView is null"); - } - - String view = Blade.webRoot() + disposeView(modelAndView.getView()); - - JetTemplate template = jetEngine.getTemplate(view); - - Map context = modelAndView.getModel(); - - Enumeration attrs = request.getAttributeNames(); - - if(null != attrs && attrs.hasMoreElements()){ - while(attrs.hasMoreElements()){ - String attr = attrs.nextElement(); - context.put(attr, request.getAttribute(attr)); - } - } - - - template.render(context, response.getOutputStream()); - - } catch (ResourceNotFoundException e) { - render404(response, modelAndView.getView()); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - -} diff --git a/blade-kit/pom.xml b/blade-kit/pom.xml index 5b52dab77..14e24bf78 100644 --- a/blade-kit/pom.xml +++ b/blade-kit/pom.xml @@ -1,33 +1,53 @@ - - - 4.0.0 - - com.bladejava - blade-root - 1.0 - - - blade-kit - jar - ${blade.version} - blade-kit - https://github.com/biezhi/blade/blade-kit - - - - javax.mail - mail - 1.5.0-b01 - provided - - - log4j - log4j - 1.2.17 - provided - - - - + + + 4.0.0 + + com.bladejava + blade + 1.0 + + + blade-kit + jar + ${blade-kit.version} + blade-kit + https://github.com/biezhi/blade/tree/master/blade-kit + + + + + org.slf4j + slf4j-api + + + + junit + junit + + + + dom4j + dom4j + 1.6.1 + provided + + + + jaxen + jaxen + 1.1.6 + provided + + + + javax.servlet + javax.servlet-api + ${servlet.version} + provided + + + + + diff --git a/blade-kit/src/main/java/blade/exception/ExceptionStack.java b/blade-kit/src/main/java/blade/exception/ExceptionStack.java deleted file mode 100644 index c72b8a1ac..000000000 --- a/blade-kit/src/main/java/blade/exception/ExceptionStack.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.exception; - -import java.util.HashMap; -import java.util.Map; - -/** - * 异常栈 - * - * @author biezhi - * @since 1.0 - */ -public class ExceptionStack { - - private static ExceptionStack defaultInstance; - - - public static ExceptionStack getInstance() { - if (defaultInstance == null) { - defaultInstance = new ExceptionStack(); - } - return defaultInstance; - } - - private Map, ExceptionHandlerImpl> exceptionMap; - - public ExceptionStack() { - this.exceptionMap = new HashMap, ExceptionHandlerImpl>(); - } - - public void map(Class exceptionClass, ExceptionHandlerImpl handler) { - this.exceptionMap.put(exceptionClass, handler); - } - - public ExceptionHandlerImpl getHandler(Class exceptionClass) { - if (!this.exceptionMap.containsKey(exceptionClass)) { - - Class superclass = exceptionClass.getSuperclass(); - do { - if (this.exceptionMap.containsKey(superclass)) { - ExceptionHandlerImpl handler = this.exceptionMap.get(superclass); - this.exceptionMap.put(exceptionClass, handler); - return handler; - } - - superclass = superclass.getSuperclass(); - } while (superclass != null); - - this.exceptionMap.put(exceptionClass, null); - return null; - } - - return this.exceptionMap.get(exceptionClass); - } - - public ExceptionHandlerImpl getHandler(Exception exception) { - return this.getHandler(exception.getClass()); - } - - public Throwable fillInStackTrace() { - return null; - } -} diff --git a/blade-kit/src/main/java/blade/exception/package-info.java b/blade-kit/src/main/java/blade/exception/package-info.java deleted file mode 100644 index ccd1136b2..000000000 --- a/blade-kit/src/main/java/blade/exception/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * blade异常包 - */ -package blade.exception; \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/MailKit.java b/blade-kit/src/main/java/blade/kit/MailKit.java deleted file mode 100644 index 1a7d984e7..000000000 --- a/blade-kit/src/main/java/blade/kit/MailKit.java +++ /dev/null @@ -1,339 +0,0 @@ -package blade.kit; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import blade.kit.mail.EmailHandle; -import blade.kit.mail.MailTemplate; - -/** - * 邮件发送类 - * - * @author biezhi - * @since 1.0 - */ -public class MailKit { - -// private static final Logger LOGGER = Logger.getLogger(MailKit.class); - - /**常见的邮件发送协议地址**/ - public static final String SMTP_QQ = "smtp.qq.com"; - public static final String SMTP_163 = "smtp.163.com"; - public static final String SMTP_126 = "smtp.126.com"; - public static final String SMTP_SINA = "smtp.sina.com"; - public static final String SMTP_GMAIL = "smtp.gmail.com"; - - private static String CFG_SMTP = SMTP_QQ; - private static String SEND_USER = ""; - private static String SEND_PASSWORD = ""; - - /** - * 配置邮件协议和账户 - * @param smtp smtp协议 - * @param user 发送人邮件 - * @param password 发送人密码 - */ - public static void config(String smtp, String user, String password){ - if(StringKit.isNotBlank(smtp)){ - CFG_SMTP = smtp; - } - if(StringKit.isNotBlank(user)){ - SEND_USER = user; - } - if(StringKit.isNotBlank(password)){ - SEND_PASSWORD = password; - } - } - - /** - * 根据模板发送 - * @param mailTemplate - * @param subject - * @return - */ - public static boolean send(MailTemplate mailTemplate, String subject){ - if(null != mailTemplate && StringKit.isNotBlank(subject)){ - return sendProcess(CFG_SMTP, SEND_USER, SEND_PASSWORD, - mailTemplate.getToMail(), mailTemplate.getCcMail(), - subject, mailTemplate.toString(), mailTemplate.getFileList()); - } - return false; - } - - /** - * 发送邮件 - * @param toMail 收件人地址 - * @param subject 发送主题 - * @param content 发送内容 - * @throws Exception - * @return 成功返回true,失败返回false - */ - public static boolean send(String toMail, String subject, String content){ - return sendProcess(CFG_SMTP, SEND_USER, SEND_PASSWORD, toMail, null, subject, content, null); - } - - - /** - * 发送邮件并发送附件 - * @param toMail 收件人地址 - * @param subject 发送主题 - * @param content 发送内容 - * @param files 附件列表 - * @throws Exception - * @return 成功返回true,失败返回false - */ - public static boolean send(String toMail, String subject, String content, String ...files){ - return sendProcess(CFG_SMTP, SEND_USER, SEND_PASSWORD, toMail, null, subject, content, Arrays.asList(files)); - } - - /** - * 发送邮件并发送附件 - * @param toMail 收件人地址 - * @param subject 发送主题 - * @param content 发送内容 - * @param files 附件列表 - * @throws Exception - * @return 成功返回true,失败返回false - */ - public static boolean send(String toMail, String subject, String content, List files){ - return sendProcess(CFG_SMTP, SEND_USER, SEND_PASSWORD, toMail, null, subject, content, files); - } - - /** - * 发送并抄送 - * @param toMail 收件人地址 - * @param ccMail 抄送地址 - * @param subject 发送主题 - * @param content 发送内容 - * @return 成功返回true,失败返回false - */ - public static boolean sendAndCc(String toMail, String ccMail, String subject, String content){ - return sendProcess(CFG_SMTP, SEND_USER, SEND_PASSWORD, toMail, ccMail, subject, content, null); - } - - /** - * 发送邮件并抄送,带附件 - * @param toMail - * @param ccMail - * @param subject - * @param content - * @param files - * @return - */ - public static boolean sendAndCc(String toMail, String ccMail, String subject, String content, String ...files){ - return sendProcess(CFG_SMTP, SEND_USER, SEND_PASSWORD, toMail, ccMail, subject, content, Arrays.asList(files)); - } - - /** - * 发送邮件并抄送,带附件 - * @param toMail - * @param ccMail - * @param subject - * @param content - * @param files - * @return - */ - public static boolean sendAndCc(String toMail, String ccMail, String subject, String content, List files){ - return sendProcess(CFG_SMTP, SEND_USER, SEND_PASSWORD, toMail, ccMail, subject, content, files); - } - - /** - * 发送邮件 - * @param smtp 邮件协议 - * @param fromAddress 发送人地址 - * @param fromPass 发送人密码 - * @param toAddress 收件人地址 - * @param ccAdress 抄送人地址 - * @param subject 发送主题 - * @param content 发送内容 - * @throws Exception - */ - public static boolean sendProcess(String smtp,String fromAddress,String fromPass,String toMailList, - String ccAdress,String subject, String content,List fileList){ - try{ - EmailHandle emailHandle = new EmailHandle(smtp); - emailHandle.setFrom(fromAddress); - emailHandle.setNeedAuth(true); - emailHandle.setSubject(subject); - emailHandle.setBody(content); - emailHandle.setToList(toMailList); - - /**添加抄送**/ - if(StringKit.isNotEmpty(ccAdress)){ - emailHandle.setCopyToList(ccAdress); - } - - emailHandle.setFrom(fromAddress); - emailHandle.setNamePass(fromAddress, fromPass); - - if(null != fileList && fileList.size() > 0){ - /** 附件文件路径 **/ - for(String file : fileList){ - emailHandle.addFileAffix(file); - } - } - return emailHandle.sendEmail(); - }catch(Exception e){ - e.printStackTrace(); - } - return false; - } - - - - /*********************************************异步发送:S*******************************************/ - - /** - * 根据模板发送 - * @param mailTemplate - * @param subject - * @return - */ - public static void asynSend(MailTemplate mailTemplate, String subject){ - if(null != mailTemplate && StringKit.isNotBlank(subject)){ - asynSend(CFG_SMTP, SEND_USER, SEND_PASSWORD, mailTemplate.getToMail(), mailTemplate.getCcMail(), - subject, mailTemplate.toString(), mailTemplate.getFileList()); - } - } - - /** - * 异步发送邮件 - * @param toMail - * @param subject - * @param content - * @return - */ - public static void asynSend(final String toMail, final String subject, final String content){ - asynSend(CFG_SMTP, SEND_USER, SEND_PASSWORD, toMail, null, subject, content, null); - } - - /** - * 异步发送并抄送 - * @param toMail - * @param ccMail - * @param subject - * @param content - */ - public static void asynSendAndCc(final String toMail, final String ccMail, final String subject, final String content){ - asynSend(CFG_SMTP, SEND_USER, SEND_PASSWORD, toMail, ccMail, subject, content, null); - } - - /** - * 异步发送邮件并发送附件 - * @param toMail - * @param subject - * @param content - * @return - */ - public static void asynSend(final String toMail, final String subject, final String content, final String ...files){ - asynSend(CFG_SMTP, SEND_USER, SEND_PASSWORD, toMail, null, subject, content, Arrays.asList(files)); - } - - /** - * 异步发送邮件并发送附件 - * @param toMail - * @param subject - * @param content - * @return - */ - public static void asynSend(final String toMail, final String subject, final String content, final List files){ - asynSend(CFG_SMTP, SEND_USER, SEND_PASSWORD, toMail, null, subject, content, files); - } - - - /** - * 异步发送邮件并抄送,带附件 - * @param toMail - * @param ccMail - * @param subject - * @param content - * @param files - * @return - */ - public static void asynSendAndCc(final String toMail, final String ccMail, final String subject, final String content, final String ...files){ - asynSend(CFG_SMTP, SEND_USER, SEND_PASSWORD, toMail, ccMail, subject, content, Arrays.asList(files)); - } - - /** - * 异步发送邮件并抄送,带附件 - * @param toMail - * @param ccMail - * @param subject - * @param content - * @param files - * @return - */ - public static void asynSendAndCc(final String toMail, final String ccMail, final String subject, final String content, final List files){ - asynSend(CFG_SMTP, SEND_USER, SEND_PASSWORD, toMail, ccMail, subject, content, files); - } - - /** - * 发送邮件 - * @param smtp 邮件协议 - * @param fromAddress 发送人地址 - * @param fromPass 发送人密码 - * @param toAddress 收件人地址 - * @param ccAdress 抄送人地址 - * @param subject 发送主题 - * @param content 发送内容 - * @throws Exception - */ - public static boolean asynSend(final String smtp,final String fromAddress,final String fromPass,final String toAddress, - final String ccAdress,final String subject, final String content,final List fileList){ - Boolean flag = Boolean.FALSE; - FutureTask futureTask = null; - ExecutorService excutorService = Executors.newCachedThreadPool(); - // 执行任务 - futureTask = new FutureTask(new Callable() { - @Override - public Boolean call() throws Exception { - EmailHandle emailHandle = new EmailHandle(smtp); - emailHandle.setFrom(fromAddress); - emailHandle.setNeedAuth(true); - emailHandle.setSubject(subject); - emailHandle.setBody(content); - emailHandle.setToList(toAddress); - /**添加抄送**/ - if(StringKit.isNotEmpty(ccAdress)){ - emailHandle.setCopyToList(ccAdress); - } - emailHandle.setFrom(fromAddress); - emailHandle.setNamePass(fromAddress, fromPass); - if(null != fileList && fileList.size() > 0){ - /** 附件文件路径 **/ - for(String file : fileList){ - emailHandle.addFileAffix(file); - } - } - return emailHandle.sendEmail(); - } - }); - excutorService.submit(futureTask); - - try { - // 任务没超时说明发送成功 - flag = futureTask.get(5L, TimeUnit.SECONDS); - } catch (InterruptedException e) { - futureTask.cancel(true); - e.printStackTrace(); - } catch (ExecutionException e) { - futureTask.cancel(true); - e.printStackTrace(); - } catch (TimeoutException e) { - futureTask.cancel(true); - e.printStackTrace(); - } finally { - excutorService.shutdown(); - } - return flag; - } - /*********************************************异步发送:E*******************************************/ - -} diff --git a/blade-kit/src/main/java/blade/kit/PropertyKit.java b/blade-kit/src/main/java/blade/kit/PropertyKit.java deleted file mode 100644 index df197b4a3..000000000 --- a/blade-kit/src/main/java/blade/kit/PropertyKit.java +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.kit; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Properties; -import java.util.Set; - -import blade.kit.log.Logger; - -/** - * properties文件工具类 - * - * @author biezhi - * @since 1.0 - */ -public class PropertyKit { - - private static final Logger logger = Logger.getLogger(PropertyKit.class); - - /** - * 根据文件名读取properties文件 - */ - public static Properties getProperty(String resourceName) { - InputStream in = null; - Properties props = new Properties(); - try { - in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName); - if (in != null) { - props.load(in); - } - } catch (IOException e) { - logger.error("加载属性文件出错!", e); - } finally { - IOKit.closeQuietly(in); - } - return props; - } - - /** - * //TODO 根据文件名读取map数据 - */ - public static Map getPropertyMap(String resourceName) { - InputStream in = null; - Map map = CollectionKit.newHashMap(); - try { - in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName); - if (null == in) { - throw new RuntimeException("没有找到资源文件 [" + resourceName + "]"); - } - Properties prop = new Properties(); - prop.load(in); - Set> set = prop.entrySet(); - Iterator> it = set.iterator(); - while (it.hasNext()) { - Entry entry = it.next(); - String key = entry.getKey().toString(); - String value = entry.getValue().toString(); - String fuKey = getWildcard(value); - if(null != fuKey && null != prop.get(fuKey)){ - String fuValue = prop.get(fuKey).toString(); - value = value.replaceAll("\\$\\{" + fuKey + "\\}", fuValue); - } - map.put(key, value); - } - logger.info("加载properties文件[" + resourceName + "]"); - } catch (IOException e) { - e.printStackTrace(); - } finally { - IOKit.closeQuietly(in); - } - return map; - } - - private static String getWildcard(String str){ - if(null != str && str.indexOf("${") != -1){ - int start = str.indexOf("${"); - int end = str.indexOf("}"); - if(start != -1 && end != -1){ - return str.substring(start + 2, end); - } - } - return null; - } - - /** - * //TODO 根据property对象获取map格式数据 - */ - public static Map getPropertyMap(Properties prop) { - Map map = CollectionKit.newHashMap(); - Set> set = prop.entrySet(); - Iterator> it = set.iterator(); - while (it.hasNext()) { - Entry entry = it.next(); - map.put(entry.getKey().toString(), entry.getValue().toString()); - } - return map; - } -} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/ReflectKit.java b/blade-kit/src/main/java/blade/kit/ReflectKit.java deleted file mode 100644 index 02dc03e47..000000000 --- a/blade-kit/src/main/java/blade/kit/ReflectKit.java +++ /dev/null @@ -1,401 +0,0 @@ -package blade.kit; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.JarURLConnection; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import blade.kit.log.Logger; - -/** - * 有关 Reflection 处理的工具类。 - * - *

- * 这个类中的每个方法都可以“安全”地处理 null ,而不会抛出 NullPointerException。 - *

- * - * @author biezhi - * @since 1.0 - */ -public abstract class ReflectKit { - - private static final Logger LOGGER = Logger.getLogger(ReflectKit.class); - - // ------------------------------------------------------ - /** 新建对象 */ - public static Object newInstance(String className) { - Object obj = null; - try { - Class clazz = Class.forName(className); - obj = clazz.newInstance(); - LOGGER.debug("new %s", className); - } catch (Exception e) { - // quiet - } - return obj; - } - - /** - * 创建一个实例对象 - * @param clazz class对象 - * @return - */ - public static Object newInstance(Class clazz){ - try { - return clazz.newInstance(); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - return null; - } - - /** 用setter设置bean属性 */ - public static void setProperty(Object bean, String name, Object value) { - String methodName = "set" + StringKit.firstUpperCase(name); - invokeMehodByName(bean, methodName, value); - } - - /** 用getter获取bean属性 */ - public static Object getProperty(Object bean, String name) { - String methodName = "get" + StringKit.firstUpperCase(name); - return invokeMehodByName(bean, methodName); - } - - /** 类型转换 */ - @SuppressWarnings("unchecked") - public static T cast(Object value, Class type) { - if (value != null && !type.isAssignableFrom(value.getClass())) { - if (is(type, int.class, Integer.class)) { - value = Integer.parseInt(String.valueOf(value)); - } else if (is(type, long.class, Long.class)) { - value = Long.parseLong(String.valueOf(value)); - } else if (is(type, float.class, Float.class)) { - value = Float.parseFloat(String.valueOf(value)); - } else if (is(type, double.class, Double.class)) { - value = Double.parseDouble(String.valueOf(value)); - } else if (is(type, boolean.class, Boolean.class)) { - value = Boolean.parseBoolean(String.valueOf(value)); - } else if (is(type, String.class)) { - value = String.valueOf(value); - } - } - return (T) value; - } - - /** 查找方法 */ - public static Method getMethodByName(Object classOrBean, String methodName) { - Method ret = null; - if (classOrBean != null) { - Class clazz = null; - if (classOrBean instanceof Class) { - clazz = (Class) classOrBean; - } else { - clazz = classOrBean.getClass(); - } - for (Method method : clazz.getMethods()) { - if (method.getName().equals(methodName)) { - ret = method; - break; - } - } - } - return ret; - } - - /** - * - * @param bean 类实例 - * @param methodName 方法名称 - * @param args 方法参数 - * @return - */ - public static Object invokeMehodByName(Object bean, String methodName, - Object... args) { - try { - Method method = getMethodByName(bean, methodName); - Class[] types = method.getParameterTypes(); - - int argCount = args == null ? 0 : args.length; - - // 参数个数对不上 - ExceptionKit.makeRunTimeWhen(argCount != types.length, - "%s in %s", methodName, bean); - - // 转参数类型 - for (int i = 0; i < argCount; i++) { - args[i] = cast(args[i], types[i]); - } - - return method.invoke(bean, args); - } catch (Exception e) { - ExceptionKit.makeRuntime(e); - } - return null; - } - - /** - * - * @param bean 类实例 - * @param methodName 方法名称 - * @param args 方法参数 - * @return - */ - public static Object invokeMehod(Object bean, Method method, - Object... args) { - try { - Class[] types = method.getParameterTypes(); - - int argCount = args == null ? 0 : args.length; - - // 参数个数对不上 - ExceptionKit.makeRunTimeWhen(argCount != types.length, "%s in %s", method.getName(), bean); - - // 转参数类型 - for (int i = 0; i < argCount; i++) { - args[i] = cast(args[i], types[i]); - } - - return method.invoke(bean, args); - } catch (Exception e) { - ExceptionKit.makeRuntime(e); - } - return null; - } - - // ------------------------------------------------------ - - /** 对象是否其中一个 */ - public static boolean is(Object obj, Object... mybe) { - if (obj != null && mybe != null) { - for (Object mb : mybe) - if (obj.equals(mb)) - return true; - } - return false; - } - - public static boolean isNot(Object obj, Object... mybe) { - return !is(obj, mybe); - } - - // ------------------------------------------------------ - - /** 扫描包下面所有的类 */ - public static List scanPackageClass(String rootPackageName) { - List classNames = new ArrayList(); - try { - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - URL url = loader.getResource(rootPackageName.replace('.', '/')); - - ExceptionKit.makeRunTimeWhen(url == null, - "package[%s] not found!", rootPackageName); - - String protocol = url.getProtocol(); - if ("file".equals(protocol)) { - LOGGER.debug("scan in file"); - File[] files = new File(url.toURI()).listFiles(); - for (File f : files) { - scanPackageClassInFile(rootPackageName, f, classNames); - } - } else if ("jar".equals(protocol)) { - LOGGER.debug("scan in jar"); - JarFile jar = ((JarURLConnection) url.openConnection()) - .getJarFile(); - scanPackageClassInJar(jar, rootPackageName, classNames); - } - - } catch (URISyntaxException e) { - } catch (IOException e) { - } - return classNames; - } - - /** 扫描文件夹下所有class文件 */ - private static void scanPackageClassInFile(String rootPackageName, - File rootFile, List classNames) { - String absFileName = rootPackageName + "." + rootFile.getName(); - if (rootFile.isFile() && absFileName.endsWith(".class")) { - classNames.add(absFileName.substring(0, absFileName.length() - 6)); - } else if (rootFile.isDirectory()) { - String tmPackageName = rootPackageName + "." + rootFile.getName(); - for (File f : rootFile.listFiles()) { - scanPackageClassInFile(tmPackageName, f, classNames); - } - } - } - - /** - * 扫描jar里面的类 - * @param jar jar包文件 - * @param packageDirName 包目录 - * @param classNames class名称列表 - */ - private static void scanPackageClassInJar(JarFile jar, String packageDirName, List classNames) { - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String name = entry.getName().replace('/', '.'); - if (name.startsWith(packageDirName) && name.endsWith(".class")) { - classNames.add(name.substring(0, name.length() - 6)); - } - } - } - - - /** - * 方法调用,如果clazznull,返回null; - *

- * 如果methodnull,返回null - *

- * 如果targetnull,则为静态方法 - * - * @param method 调用的方法 - * @param target 目标对象 - * @param args 方法的参数值 - * @return 调用结果 - */ - public static Object invokeMethod(Method method, Object target, Object...args) { - if (method == null) { - return null; - } - - method.setAccessible(true); - try { - return method.invoke(target, args); - } catch (Exception ex) { - throw ExceptionKit.toRuntimeException(ex); - } - - } - - /** - *

- * 调用一个命名的方法,其参数类型相匹配的对象类型。 - *

- * - * - * @param object 调用方法作用的对象 - * @param methodName 方法名 - * @param args 参数值 - * @param parameterTypes 参数类型 - * @return 调用的方法的返回值 - * - */ - public static Object invokeMethod(Object object, String methodName, Object[] args, Class...parameterTypes) { - if (object == null || StringKit.isEmpty(methodName)) { - return null; - } - - if (parameterTypes == null) { - parameterTypes = Emptys.EMPTY_CLASS_ARRAY; - } - if (args == null) { - args = Emptys.EMPTY_OBJECT_ARRAY; - } - Method method; - try { - method = object.getClass().getDeclaredMethod(methodName, parameterTypes); - } catch (Exception ex) { - throw ExceptionKit.toRuntimeException(ex); - } - if (method == null) { - return null; - } - - return invokeMethod(method, object, args); - - } - - /** - *

- * 调用一个命名的静态方法,其参数类型相匹配的对象类型。 - *

- * - * - * @param clazz 调用方法作用的类 - * @param methodName 方法名 - * @param args 参数值 - * @param parameterTypes 参数类型 - * @return 调用的方法的返回值 - * - */ - public static Object invokeStaticMethod(Class clazz, String methodName, Object[] args, Class...parameterTypes) { - if (parameterTypes == null) { - parameterTypes = Emptys.EMPTY_CLASS_ARRAY; - } - if (args == null) { - args = Emptys.EMPTY_OBJECT_ARRAY; - } - Method method; - try { - method = clazz.getDeclaredMethod(methodName, parameterTypes); - } catch (Exception ex) { - throw ExceptionKit.toRuntimeException(ex); - } - if (method == null) { - return null; - } - - return invokeMethod(method, null, args); - } - - // ========================================================================== - // 辅助方法。 - // ========================================================================== - - private static final Method IS_SYNTHETIC; - static { - Method isSynthetic = null; - if (SystemKit.getJavaInfo().isJavaVersionAtLeast(1.5f)) { - // cannot call synthetic methods: - try { - isSynthetic = Member.class.getMethod("isSynthetic", Emptys.EMPTY_CLASS_ARRAY); - } catch (Exception e) { - // ignore - } - } - IS_SYNTHETIC = isSynthetic; - } - - public static boolean isAccessible(Member m) { - return m != null && Modifier.isPublic(m.getModifiers()) && !isSynthetic(m); - } - - static boolean isSynthetic(Member m) { - if (IS_SYNTHETIC != null) { - try { - return ((Boolean) IS_SYNTHETIC.invoke(m, (Object[]) null)).booleanValue(); - } catch (Exception e) { - } - } - return false; - } - - public static boolean isPublic(Member m) { - return m != null && Modifier.isPublic(m.getModifiers()); - } - - public static void forceAccess(AccessibleObject object) { - if (object == null || object.isAccessible()) { - return; - } - try { - object.setAccessible(true); - } catch (SecurityException e) { - throw ExceptionKit.toRuntimeException(e); - } - } - -} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/http/HttpBase.java b/blade-kit/src/main/java/blade/kit/http/HttpBase.java deleted file mode 100644 index 29a371712..000000000 --- a/blade-kit/src/main/java/blade/kit/http/HttpBase.java +++ /dev/null @@ -1,684 +0,0 @@ - -package blade.kit.http; - -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import blade.kit.io.StringBuilderWriter; - -/** - * http基类 - *

- *

- * - * @author biezhi - * @since 1.0 - * @param - */ -@SuppressWarnings("unchecked") -public abstract class HttpBase { - - //////////////////////////const/////////////////////////////////// - - /**Accept*/ - public static final String HEADER_ACCEPT = "Accept"; - - /**Accept-Encoding*/ - public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; - - /**User-Agent*/ - public static final String HEADER_USER_AGENT = "User-Agent"; - - /**Content-Type*/ - public static final String HEADER_CONTENT_TYPE = "Content-Type"; - - /**Content-Length*/ - public static final String HEADER_CONTENT_LENGTH = "Content-Length"; - - /**Content-Encoding*/ - public static final String HEADER_CONTENT_ENCODING = "Content-Encoding"; - - /**Host*/ - public static final String HEADER_HOST = "Host"; - - /**ETag*/ - public static final String HEADER_ETAG = "ETag"; - - /**Connection*/ - public static final String HEADER_CONNECTION = "Connection"; - - /**Keep-Alive*/ - public static final String HEADER_KEEP_ALIVE = "Keep-Alive"; - - /**Close*/ - public static final String HEADER_CLOSE = "Close"; - - /**HTTP/1.0*/ - public static final String HTTP_1_0 = "HTTP/1.0"; - - /**HTTP/1.1*/ - public static final String HTTP_1_1 = "HTTP/1.1"; - - /**UTF-8*/ - protected String charset = "UTF-8"; - - /**默认缓冲*/ - protected static final int DEFAULT_BUFFER_SIZE = 1024 * 4; - - /**http版本*/ - protected String httpVersion = HTTP_1_1; - - /**存储头信息*/ - protected Map> headers = new HashMap>(); - - /**存储表单数据*/ - protected Map form = new HashMap(); - - /**存储响应主体*/ - protected String body; - - /** - * 返回http版本 - * @return String - */ - public String httpVersion() { - return httpVersion; - } - - /** - * 设置http版本 - * @param httpVersion - * @return T - */ - public T httpVersion(String httpVersion) { - this.httpVersion = httpVersion; - return (T) this; - } - - // ---------------------------------------------------------------- headers - - /** - * 根据name获取头信息 - * @param name - * @return String - */ - public String header(String name) { - String key = name.trim(); - if(null == headers.get(key)){ - return null; - } - String value = headers.get(key).get(0); - if (value == null) { - return null; - } - return value; - } - - /** - * 移除一个头信息 - * @param name - */ - public void removeHeader(String name) { - String key = name.trim(); - headers.remove(key); - } - - /** - * 设置一个header - * @param name - * @param value - * @return T - */ - public T header(String name, String value) { - if(null != name && null != value){ - String key = name.trim(); - value = value.trim(); - headers.put(key, Arrays.asList(value)); - } - return (T) this; - } - - /** - * 覆盖一个header - * @param name - * @param value - * @param flag - * @return T - */ - public T header(String name, String value, boolean flag) { - if(null != name && null != value){ - String key = name.trim(); - value = value.trim(); - if(headers.containsKey(key)){ - headers.remove(key); - } - headers.put(key, Arrays.asList(value)); - } - return (T) this; - } - - /** - * header - * @param name - * @param value - * @return T - */ - public T header(String name, int value) { - return header(name, String.valueOf(value)); - } - - /** - * header - * @param name - * @param millis - * @return T - */ - public T header(String name, long millis) { - return header(name, String.valueOf(millis)); - } - - /** - * 获取headers - * @return Map> - */ - public Map> headers() { - return Collections.unmodifiableMap(headers); - } - - /** - * 返回字符集 - * @return String - */ - public String charset() { - return charset; - } - - /** - * 设置字符集 - * @param charset - * @return T - */ - public T charset(String charset) { - this.charset = null; - contentType(null, charset); - return (T) this; - } - - /**mediaType*/ - protected String mediaType; - - /** - * 获取mediaType - * @return String - */ - public String mediaType() { - return mediaType; - } - - /** - * 设置mediaType - * @param mediaType - * @return T - */ - public T mediaType(String mediaType) { - contentType(mediaType, null); - return (T) this; - } - - /** - * 获取contentType - * @return String - */ - public String contentType() { - return header(HEADER_CONTENT_TYPE); - } - - /** - * 设置contentType - * @param contentType - * @return T - */ - public T contentType(String contentType) { - header(HEADER_CONTENT_TYPE, contentType); - return (T) this; - } - - /** - * 设置mediaType包含字符集 - * @param mediaType - * @param charset - * @return T - */ - public T contentType(String mediaType, String charset) { - if (mediaType == null) { - mediaType = this.mediaType; - } else { - this.mediaType = mediaType; - } - - if (charset == null) { - charset = this.charset; - } else { - this.charset = charset; - } - - String contentType = mediaType; - if (charset != null) { - contentType += ";charset=" + charset; - } - - header(HEADER_CONTENT_TYPE, contentType); - return (T) this; - } - - /** - * 设置是否为活动连接 - * @param keepAlive - * @return T - */ - public T connectionKeepAlive(boolean keepAlive) { - if (keepAlive) { - header(HEADER_CONNECTION, HEADER_KEEP_ALIVE); - } else { - header(HEADER_CONNECTION, HEADER_CLOSE); - } - return (T) this; - } - - /** - * 获取是否为活动连接 - * @return boolean - */ - public boolean isConnectionPersistent() { - String connection = header(HEADER_CONNECTION); - if (connection == null) { - return !httpVersion.equalsIgnoreCase(HTTP_1_0); - } - - return !connection.equalsIgnoreCase(HEADER_CLOSE); - } - - /** - * 获取内容长度 - * @return String - */ - public String contentLength() { - return header(HEADER_CONTENT_LENGTH); - } - - /** - * 设置内容长度 - * @param value - * @return T - */ - public T contentLength(int value) { - header(HEADER_CONTENT_LENGTH, String.valueOf(value)); - return (T) this; - } - - /** - * 获取内容编码 - * @return String - */ - public String contentEncoding() { - return header(HEADER_CONTENT_ENCODING); - } - - /** - * 获取请求头 - * @return String - */ - public String accept() { - return header(HEADER_ACCEPT); - } - - /** - * 设置请求头 - * @param encodings - * @return T - */ - public T accept(String encodings) { - header(HEADER_ACCEPT, encodings); - return (T) this; - } - - /** - * 获取请求编码 - * @return String - */ - public String acceptEncoding() { - return header(HEADER_ACCEPT_ENCODING); - } - - /** - * 设置请求编码 - * @param encodings - * @return T - */ - public T acceptEncoding(String encodings) { - header(HEADER_ACCEPT_ENCODING, encodings); - return (T) this; - } - - /** - * 初始化表单 - */ - protected void initForm() { - if (form == null) { - form = new HashMap(); - } - } - - /** - * 转换数据类型 - * @param value - * @return Object - */ - protected Object wrapFormValue(Object value) { - if (value == null) { - return null; - } - if (value instanceof CharSequence) { - return value.toString(); - } - if (value instanceof Number) { - return value.toString(); - } - if (value instanceof Boolean) { - return value.toString(); - } - throw new HttpException("Unsupported value type: " + value.getClass().getName()); - } - - /** - * 设置表单数据 - * @param name - * @param value - * @return T - */ - public T query(String name, Object value) { - initForm(); - value = wrapFormValue(value); - form.put(name, value); - - return (T) this; - } - - /** - * 设置表单数据,是否覆盖 - * @param name - * @param value - * @param overwrite - * @return T - */ - public T query(String name, Object value, boolean overwrite) { - initForm(); - - value = wrapFormValue(value); - - if (overwrite) { - form.put(name, value); - } else { - form.put(name, value); - } - - return (T) this; - } - - /** - * 设置表单数据 - * @param name - * @param value - * @param parameters - * @return T - */ - public T query(String name, Object value, Object... parameters) { - initForm(); - - query(name, value); - - for (int i = 0; i < parameters.length; i += 2) { - name = parameters[i].toString(); - - query(name, parameters[i + 1]); - } - return (T) this; - } - - /** - * 设置map类型表单数据 - * @param formMap - * @return T - */ - public T query(Map formMap) { - initForm(); - - for (Map.Entry entry : formMap.entrySet()) { - query(entry.getKey(), entry.getValue()); - } - return (T) this; - } - - /** - * 获取表单数据 - * @return Map - */ - public Map form() { - return form; - } - - // ---------------------------------------------------------------- form encoding - - /**formEncoding*/ - protected String formEncoding = "UTF-8"; - - /** - * 设置表单编码 - * @param encoding - * @return T - */ - public T formEncoding(String encoding) { - this.formEncoding = encoding; - return (T) this; - } - - /** - * 获取响应主体 - * @return String - */ - public String body() { - return body; - } - - /** - * 获取响应流字节码 - * @return byte[] - */ - public byte[] bodyBytes() { - if (body == null) { - return null; - } - try { - return body.getBytes("ISO8859-1"); - } catch (UnsupportedEncodingException ignore) { - return null; - } - } - - /** - * 获取响应文本 - * @return String - */ - public String bodyText() { - if (body == null) { - return null; - } - if (charset != null) { - try { - return new String(body.getBytes("ISO8859-1"), charset); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - } - return body(); - } - - /** - * 设置内容主体 - * @param body - * @return T - */ - public T body(String body) { - this.body = body; - this.form = null; - contentLength(body.length()); - return (T) this; - } - - /** - * 设置内容主体编码 - * @param charset - * @return String - */ - public String bodyText(String charset) { - try { - return new String(body.getBytes(charset), "ISO8859-1"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - return null; - } - - /** - * 设置内容mediaType - * @param body - * @param mediaType - * @return T - */ - public T bodyText(String body, String mediaType) { - return bodyText(body, mediaType, "UTF-8"); - } - - /** - * 设置内容html - * @param body - * @return T - */ - public T bodyHtml(String body) { - return bodyText(body, "text/html", "UTF-8"); - } - - /** - * 设置主体文本 - * @param body - * @param mediaType - * @param charset - * @return T - */ - public T bodyText(String body, String mediaType, String charset) { - try { - body = new String(body.getBytes(charset), "ISO8859-1"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - contentType(mediaType, charset); - body(body); - return (T) this; - } - - /** - * 设置主体字节码 - * @param content - * @param contentType - * @return T - */ - public T body(byte[] content, String contentType) { - String body = null; - try { - body = new String(content, "ISO8859-1"); - } catch (UnsupportedEncodingException ignore) { - } - contentType(contentType); - return body(body); - } - - /** - * 读取主体 - * @param input - * @return String - * @throws IOException - */ - public String readBody(InputStream input) throws IOException{ - StringBuilderWriter sw = new StringBuilderWriter(); - InputStreamReader in = new InputStreamReader(input, Charset.defaultCharset()); - copy(in, sw); - close(input); - return sw.toString(); - } - - /** - * 复制数据流 - * @param input - * @param output - * @return int - * @throws IOException - */ - public int copy(Reader input, Writer output) throws IOException { - long count = copyLarge(input, output); - if (count > Integer.MAX_VALUE) { - return -1; - } - return (int) count; - } - - /** - * 复制长数据流 - * @param input - * @param output - * @return long - * @throws IOException - */ - public long copyLarge(Reader input, Writer output) throws IOException { - return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]); - } - - /** - * 复制长数据流 - * @param input - * @param output - * @param buffer - * @return long - * @throws IOException - */ - public long copyLarge(Reader input, Writer output, char [] buffer) throws IOException { - long count = 0; - int n = 0; - while (-1 != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - } - return count; - } - - /** - * 关闭数据流 - * @param closeable - * @throws IOException - */ - public void close(Closeable closeable) throws IOException{ - if(null != closeable){ - closeable.close(); - } - } -} diff --git a/blade-kit/src/main/java/blade/kit/http/HttpConnectionWrapper.java b/blade-kit/src/main/java/blade/kit/http/HttpConnectionWrapper.java deleted file mode 100644 index deb823da0..000000000 --- a/blade-kit/src/main/java/blade/kit/http/HttpConnectionWrapper.java +++ /dev/null @@ -1,372 +0,0 @@ -package blade.kit.http; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -/** - * http连接对象包装 - *

- *

- * - * @author biezhi - * @since 1.0 - */ -public class HttpConnectionWrapper { - - /** - * httpsURLConnection - */ - private HttpsURLConnection httpsURLConnection; - - /** - * httpURLConnection - */ - private HttpURLConnection httpURLConnection; - - /** - * timeout超时,以毫秒为单位 - */ - private int timeout; - - /** - * url - */ - private String url; - - /** - * method请求方法 - */ - private String method = "GET"; - - /** - * 初始化HttpConnection - * @param url - * @param method - * @throws IOException - * @throws KeyManagementException - * @throws NoSuchAlgorithmException - * @throws NoSuchProviderException - */ - public HttpConnectionWrapper(String url, String method) throws IOException, KeyManagementException, NoSuchAlgorithmException, NoSuchProviderException { - this.url = url; - this.method = method; - - if(isHttps(this.url)){ - initHttps(); - } else { - initHttp(); - } - } - - /** - * 初始化http请求参数 - */ - private void initHttp() throws IOException { - URL _url = new URL(this.url); - this.httpURLConnection = (HttpURLConnection) _url.openConnection(); - - // method - if (this.method.equalsIgnoreCase("post")) { - this.httpURLConnection.setDoOutput(true); - this.httpURLConnection.setUseCaches(false); - } - this.httpURLConnection.setDoInput(true); - this.httpURLConnection.setRequestMethod(this.method); - - // header - this.httpURLConnection.setRequestProperty(HttpBase.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); - this.httpURLConnection.setRequestProperty(HttpBase.HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded"); - this.httpURLConnection.setRequestProperty(HttpBase.HEADER_USER_AGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:36.0) Gecko/20100101 Firefox/36.0"); - - this.httpsURLConnection = null; - - } - - /** - * 初始化http请求参数 - */ - private void initHttps() throws IOException, NoSuchAlgorithmException, NoSuchProviderException, KeyManagementException { - TrustManager[] tm = { new MyX509TrustManager() }; - SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); - sslContext.init(null, tm, new java.security.SecureRandom()); - - // 从上述SSLContext对象中得到SSLSocketFactory对象 - SSLSocketFactory ssf = sslContext.getSocketFactory(); - - URL _url = new URL(url); - this.httpsURLConnection = (HttpsURLConnection) _url.openConnection(); - - // domain validate - this.httpsURLConnection.setHostnameVerifier(new TrustAnyHostnameVerifier()); - - this.httpsURLConnection.setSSLSocketFactory(ssf); - - // method - if (this.method.equalsIgnoreCase("post")) { - this.httpsURLConnection.setDoOutput(true); - this.httpsURLConnection.setUseCaches(false); - } - this.httpsURLConnection.setDoInput(true); - this.httpsURLConnection.setRequestMethod(this.method); - - // header - this.httpsURLConnection.setRequestProperty(HttpBase.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); - this.httpsURLConnection.setRequestProperty(HttpBase.HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded"); - this.httpsURLConnection.setRequestProperty(HttpBase.HEADER_USER_AGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:36.0) Gecko/20100101 Firefox/36.0"); - - this.httpURLConnection = null; - } - - /** - * 获取请求方法,GET/POST - * @return String - */ - public String getMethod() { - return method; - } - - /** - * 设置请求方法 - * @param method - */ - public void setMethod(String method) { - this.method = method; - } - - /** - * 获取请求URL - * @return String - */ - public String getUrl() { - return url; - } - - /** - * 设置请求URL - * @param url - */ - public void setUrl(String url) { - this.url = url; - } - - /** - * 获取超时 - * @return int - */ - public int getTimeout() { - return timeout; - } - - /** - * 设置超时 - * @param timeout - */ - public void setTimeout(int timeout) { - this.timeout = timeout; - } - - /** - * 获取HttpsURLConnection对象 - * @return HttpsURLConnection - */ - public HttpsURLConnection getHttpsURLConnection() { - return httpsURLConnection; - } - - /** - * 获取HttpURLConnection对象 - * @return HttpURLConnection - */ - public HttpURLConnection getHttpURLConnection() { - return httpURLConnection; - } - - /** - * 检测是否https - * @param url - * @return boolean - */ - private boolean isHttps(String url) { - return url.startsWith("https"); - } - - /** - * https 域名校验 - * - * @author biezhi - * @since 1.0 - */ - public class TrustAnyHostnameVerifier implements HostnameVerifier { - public boolean verify(String hostname, SSLSession session) { - return true;// 直接返回true - } - } - - /** - * 设置连接超时 - * @param timeout - */ - public void setConnectTimeout(int timeout){ - if(null != this.httpURLConnection){ - this.httpURLConnection.setConnectTimeout(timeout); - } - if(null != this.httpsURLConnection){ - this.httpsURLConnection.setConnectTimeout(timeout); - } - } - - /** - * 设置读取超时 - * @param timeout - */ - public void setReadTimeout(int timeout){ - if(null != this.httpURLConnection){ - this.httpURLConnection.setReadTimeout(timeout); - } - if(null != this.httpsURLConnection){ - this.httpsURLConnection.setReadTimeout(timeout); - } - } - - /** - * 设置请求头 - * @param key - * @param value - */ - public void setRequestProperty(String key, String value){ - if(null != this.httpURLConnection){ - this.httpURLConnection.setRequestProperty(key, value); - } - if(null != this.httpsURLConnection){ - this.httpsURLConnection.setRequestProperty(key, value); - } - } - - /** - * 设置请求头 - * @param headers - */ - public void setRequestProperty(Map> headers){ - Set keySet = headers.keySet(); - for(String key : keySet){ - List valueList = headers.get(key); - this.setRequestProperty(key, valueList.get(0)); - } - } - - /** - * 连接 - * @throws IOException - */ - public void connect() throws IOException{ - if(null != this.httpURLConnection){ - this.httpURLConnection.connect(); - } - if(null != this.httpsURLConnection){ - this.httpsURLConnection.connect(); - } - } - - /** - * 断开连接 - */ - public void disconnect(){ - if(null != this.httpURLConnection){ - this.httpURLConnection.disconnect(); - } - if(null != this.httpsURLConnection){ - this.httpsURLConnection.disconnect(); - } - } - - public InputStream getInputStream() throws IOException{ - if(null != this.httpURLConnection){ - return this.httpURLConnection.getInputStream(); - } - if(null != this.httpsURLConnection){ - return this.httpsURLConnection.getInputStream(); - } - return null; - } - - /** - * 获取输出流对象 - * @return OutputStream - * @throws IOException - */ - public OutputStream getOutputStream() throws IOException{ - if(null != this.httpURLConnection){ - return this.httpURLConnection.getOutputStream(); - } - if(null != this.httpsURLConnection){ - return this.httpsURLConnection.getOutputStream(); - } - return null; - } - - /** - * 获取请求头信息 - * @return Map> - */ - public Map> getHeaderFields(){ - if(null != this.httpURLConnection){ - return this.httpURLConnection.getHeaderFields(); - } - if(null != this.httpsURLConnection){ - return this.httpsURLConnection.getHeaderFields(); - } - return null; - } - - /** - * 获取响应码 - * @return int - * @throws IOException - */ - public int getResponseCode() throws IOException{ - if(null != this.httpURLConnection){ - return this.httpURLConnection.getResponseCode(); - } - if(null != this.httpsURLConnection){ - return this.httpsURLConnection.getResponseCode(); - } - return 0; - } -} - -// 证书管理 -class MyX509TrustManager implements X509TrustManager { - - public X509Certificate[] getAcceptedIssuers() { - return null; - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) - throws CertificateException { - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) - throws CertificateException { - } -} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/http/HttpException.java b/blade-kit/src/main/java/blade/kit/http/HttpException.java deleted file mode 100644 index 128e0ae77..000000000 --- a/blade-kit/src/main/java/blade/kit/http/HttpException.java +++ /dev/null @@ -1,37 +0,0 @@ -package blade.kit.http; - -/** - * http异常封装 - *

- *

- * - * @author biezhi - * @since 1.0 - */ -public class HttpException extends RuntimeException { - - private static final long serialVersionUID = -6657642586184082657L; - - /** - * HttpException - */ - public HttpException() { - // TODO Auto-generated constructor stub - } - - /** - * HttpException - * @param msg - */ - public HttpException(String msg) { - super(msg); - } - - /** - * HttpException - * @param t - */ - public HttpException(Throwable t) { - super(t); - } -} diff --git a/blade-kit/src/main/java/blade/kit/http/HttpKit.java b/blade-kit/src/main/java/blade/kit/http/HttpKit.java deleted file mode 100644 index d91d83750..000000000 --- a/blade-kit/src/main/java/blade/kit/http/HttpKit.java +++ /dev/null @@ -1,61 +0,0 @@ -package blade.kit.http; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * http util - * @author biezhi - * - */ -public class HttpKit { - - /** - * 参数转换为map,a=1&b=2 - * @param query - * @param decode - * @return Map - */ - public static Map parseQuery(String query, boolean decode) { - Map queryMap = new HashMap(); - String[] kvArr = query.split("&"); - if(null != kvArr && kvArr.length > 0){ - for(String k : kvArr){ - if(k.indexOf("=") != -1){ - String[] kv = k.split("="); - queryMap.put(kv[0], kv[1]); - } - } - } - return queryMap; - } - - /** - * map转为url参数 - * @param map - * @return String - */ - public static String getQuery(Map map){ - try { - if(null != map && map.size() > 0){ - StringBuffer sBuffer = new StringBuffer(); - Set keySet = map.keySet(); - for(String key : keySet){ - sBuffer.append(key + "=" + URLEncoder.encode(map.get(key).toString(), "UTF-8") + "&"); - } - return sBuffer.substring(0, sBuffer.length() - 1).toString(); - } - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - return ""; - } - - public static HttpRequest createRequest(String url){ - return new HttpRequest(url); - } -} - diff --git a/blade-kit/src/main/java/blade/kit/http/HttpRequest.java b/blade-kit/src/main/java/blade/kit/http/HttpRequest.java deleted file mode 100644 index c3a4ec767..000000000 --- a/blade-kit/src/main/java/blade/kit/http/HttpRequest.java +++ /dev/null @@ -1,181 +0,0 @@ -package blade.kit.http; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.util.List; -import java.util.Map; - -/** - * http请求类 - *

- *

- * - * @author biezhi - * @since 1.0 - */ -public class HttpRequest extends HttpBase{ - - /** - * method - */ - protected String method = "GET"; - - /** - * URL - */ - private String url = ""; - - /** - * 默认超时 - */ - private int timeout = -1; - - /** - * 连接对象 - */ - private HttpConnectionWrapper httpConnection; - - /** - * 设置请求方法 - * @param method - * @return HttpRequest - */ - private HttpRequest method(String method) { - this.method = method.toUpperCase(); - return this; - } - - /** - * 设置请求URL - * @param url - */ - public HttpRequest(String url) { - this.url = url; - } - - /** - * POST请求 - * @param url - * @return HttpRequest - */ - public static HttpRequest post(String url) { - return new HttpRequest(url).method("POST"); - } - - /** - * GET请求 - * @param url - * @return HttpRequest - */ - public static HttpRequest get(String url) { - return new HttpRequest(url).method("GET"); - } - - /** - * 设置超时 - * @param milliseconds - * @return HttpRequest - */ - public HttpRequest timeout(int milliseconds) { - this.timeout = milliseconds; - return this; - } - - /** - * 执行Reuqest请求 - * @return HttpResponse - * @throws IOException - * @throws NoSuchProviderException - * @throws NoSuchAlgorithmException - * @throws KeyManagementException - */ - public HttpResponse execute() throws Exception{ - - if(this.method.equalsIgnoreCase("get")){ - withUrl(); - } - // init connection - this.httpConnection = new HttpConnectionWrapper(this.url, this.method); - - // response - HttpResponse httpResponse = null; - if(this.timeout != -1){ - // connect timeout - this.httpConnection.setConnectTimeout(this.timeout); - // read timeout - this.httpConnection.setReadTimeout(this.timeout); - } - - // set header - if(!this.headers.isEmpty()){ - this.httpConnection.setRequestProperty(this.headers); - } - - try { - - if(this.method.equalsIgnoreCase("POST")){ - OutputStream outputStream = httpConnection.getOutputStream(); - sendTo(outputStream); - } else { - this.httpConnection.connect(); - } - - InputStream inputStream = this.httpConnection.getInputStream(); - Map> headerFields = this.httpConnection.getHeaderFields(); - - httpResponse = HttpResponse.readResponse(inputStream); - - httpResponse.setHeader(headerFields); - httpResponse.setStatusCode(this.httpConnection.getResponseCode()); - httpResponse.setHttpRequest(this); - - } catch (IOException ioex) { - throw new HttpException(ioex); - } - boolean keepAlive = httpResponse.isConnectionPersistent(); - if (keepAlive == false) { - // closes connection if keep alive is false, or if counter reached 0 - this.httpConnection.disconnect(); - this.httpConnection = null; - } - return httpResponse; - } - - /** - * 转换URL - */ - private void withUrl(){ - String queryString = HttpKit.getQuery(this.form); - if(null != queryString && !"".equals(queryString)){ - if(this.url.endsWith("?")){ - this.url += queryString; - } else { - this.url += "?" + queryString; - } - } - } - - /** - * 发送数据流 - * @param outputStream - * @throws IOException - */ - private void sendTo(OutputStream outputStream) throws IOException { - if(null != outputStream){ - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, this.formEncoding)); - String queryString = HttpKit.getQuery(this.form); - writer.write(queryString); - writer.flush(); - writer.close(); - - outputStream.close(); - } - } - -} diff --git a/blade-kit/src/main/java/blade/kit/http/HttpResponse.java b/blade-kit/src/main/java/blade/kit/http/HttpResponse.java deleted file mode 100644 index f62df60f8..000000000 --- a/blade-kit/src/main/java/blade/kit/http/HttpResponse.java +++ /dev/null @@ -1,86 +0,0 @@ -package blade.kit.http; - -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.Map; - -/** - * Http响应类 - *

- *

- * - * @author biezhi - * @since 1.0 - */ -public class HttpResponse extends HttpBase{ - - /** - * 响应状态码 - */ - private int statusCode; - - /** - * 请求对象 - */ - private HttpRequest httpRequest; - - public HttpResponse() { - } - - /** - * 获取状态码 - * @return - */ - public int getStatusCode() { - return statusCode; - } - - /** - * 设置状态码 - * @param statusCode - */ - public void setStatusCode(int statusCode) { - this.statusCode = statusCode; - } - - /** - * 读取响应流 - * @param in - * @return HttpResponse - * @throws IOException - */ - public static HttpResponse readResponse(InputStream in) throws IOException { - HttpResponse httpResponse = new HttpResponse(); - String body = httpResponse.readBody(in); - httpResponse.body = body; - return httpResponse; - } - - /** - * 获取请求对象 - * @return - */ - public HttpRequest getHttpRequest() { - return httpRequest; - } - - /** - * 设置请求对象 - * @param httpRequest - */ - public void setHttpRequest(HttpRequest httpRequest) { - this.httpRequest = httpRequest; - } - - /** - * 设置请求头 - * @param headerFields - */ - public void setHeader(Map> headerFields) { - for (Map.Entry> entry : headerFields.entrySet()) { - this.header(entry.getKey(), entry.getValue().get(0)); - } - } - -} diff --git a/blade-kit/src/main/java/blade/kit/io/package-info.java b/blade-kit/src/main/java/blade/kit/io/package-info.java deleted file mode 100644 index d227577ed..000000000 --- a/blade-kit/src/main/java/blade/kit/io/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * IO操作包 - */ -package blade.kit.io; \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/log/JDKLoggerAdaptor.java b/blade-kit/src/main/java/blade/kit/log/JDKLoggerAdaptor.java deleted file mode 100644 index e7de7adb2..000000000 --- a/blade-kit/src/main/java/blade/kit/log/JDKLoggerAdaptor.java +++ /dev/null @@ -1,75 +0,0 @@ -package blade.kit.log; - -import java.util.logging.Level; - -/** - * JDK的日志适配 - * - * @author biezhi - * @since 1.0 - */ -public class JDKLoggerAdaptor extends Logger { - - private java.util.logging.Logger logger; - - public JDKLoggerAdaptor() { - } - - public JDKLoggerAdaptor(String name) { - this.name = name; - logger = java.util.logging.Logger.getLogger(name); - } - - private Level getLevel(int level) { - switch (level) { - case TRACE: - return Level.FINEST; - case DEBUG: - return Level.FINE; - case INFO: - return Level.INFO; - case WARN: - return Level.WARNING; - case ERROR: - case FATAL: - return Level.SEVERE; - } - return Level.INFO; - } - - @Override - public void log(int level, Object message, Object... args) { - log(level, message, null, args); - } - - @Override - public void log(int level, Object message, Throwable t, Object... args) { - - log(getLevel(level), format(message, args), t); - - } - - private void log(Level level, String msg, Throwable ex) { - if (logger.isLoggable(level)) { - // Hack (?) to get the stack trace. - Throwable dummyException = new Throwable(); - StackTraceElement locations[] = dummyException.getStackTrace(); - // Caller will be the third element - String cname = "unknown"; - String method = "unknown"; - if (locations != null && locations.length > 3) { - StackTraceElement caller = locations[3]; - cname = caller.getClassName(); - method = caller.getMethodName(); - } - - if (ex == null) { - logger.logp(level, cname, method, msg); - } else { - logger.logp(level, cname, method, msg, ex); - } - } - - } - -} diff --git a/blade-kit/src/main/java/blade/kit/log/Log4jLogAdaptor.java b/blade-kit/src/main/java/blade/kit/log/Log4jLogAdaptor.java deleted file mode 100644 index ed113114e..000000000 --- a/blade-kit/src/main/java/blade/kit/log/Log4jLogAdaptor.java +++ /dev/null @@ -1,186 +0,0 @@ -package blade.kit.log; - -import org.apache.log4j.Level; -import org.apache.log4j.Priority; - -/** - * Log4j日志适配 - * - * @author biezhi - * @since 1.0 - */ -@SuppressWarnings("deprecation") -public class Log4jLogAdaptor extends Logger{ - - private static final String FQCN = Log4jLogAdaptor.class.getName(); - private static Priority traceLevel; - private org.apache.log4j.Logger logger; - - static { - try { - traceLevel = (Priority) Level.class.getDeclaredField("TRACE").get(null); - } catch (Exception ex) { - traceLevel = Priority.DEBUG; - } - } - - public Log4jLogAdaptor(String name) { - this.name = name; - logger = org.apache.log4j.Logger.getLogger(name); - } - - @Override - public String getName() { - return this.name; - } - - private Priority getLevel(int level) { - switch (level) { - case TRACE: - return traceLevel; - case DEBUG: - return Priority.DEBUG; - case INFO: - return Priority.INFO; - case WARN: - return Priority.WARN; - case ERROR: - return Priority.ERROR; - case FATAL: - return Priority.FATAL; - } - return Priority.DEBUG; - } - - @Override - public void log(int level, Object message, Object... args) { - message = format(message, args); - logger.log(FQCN, getLevel(level), message, null); - } - - @Override - public void log(int level, Object message, Throwable t, Object... args) { - message = format(message, args); - logger.log(FQCN, getLevel(level), message, t); - } - - @Override - public void trace(Object message) { - - } - - @Override - public void trace(Object message, Object... args) { - // TODO Auto-generated method stub - - } - - @Override - public void trace(Object message, Throwable t) { - // TODO Auto-generated method stub - - } - - @Override - public void trace(Object message, Throwable t, Object... args) { - // TODO Auto-generated method stub - - } - - @Override - public void debug(Object message) { - // TODO Auto-generated method stub - - } - - @Override - public void debug(Object message, Object... args) { - // TODO Auto-generated method stub - - } - - @Override - public void debug(Object message, Throwable t) { - // TODO Auto-generated method stub - - } - - @Override - public void debug(Object message, Throwable t, Object... args) { - // TODO Auto-generated method stub - - } - - @Override - public void info(Object message) { - // TODO Auto-generated method stub - - } - - @Override - public void info(Object message, Object... args) { - // TODO Auto-generated method stub - - } - - @Override - public void info(Object message, Throwable t) { - // TODO Auto-generated method stub - - } - - @Override - public void info(Object message, Throwable t, Object... args) { - // TODO Auto-generated method stub - - } - - @Override - public void warn(Object message) { - // TODO Auto-generated method stub - - } - - @Override - public void warn(Object message, Object... args) { - // TODO Auto-generated method stub - - } - - @Override - public void warn(Object message, Throwable t) { - // TODO Auto-generated method stub - - } - - @Override - public void warn(Object message, Throwable t, Object... args) { - // TODO Auto-generated method stub - - } - - @Override - public void error(Object message) { - // TODO Auto-generated method stub - - } - - @Override - public void error(Object message, Object... args) { - // TODO Auto-generated method stub - - } - - @Override - public void error(Object message, Throwable t) { - // TODO Auto-generated method stub - - } - - @Override - public void error(Object message, Throwable t, Object... args) { - // TODO Auto-generated method stub - - } - -} diff --git a/blade-kit/src/main/java/blade/kit/log/Logger.java b/blade-kit/src/main/java/blade/kit/log/Logger.java deleted file mode 100644 index eed9b2594..000000000 --- a/blade-kit/src/main/java/blade/kit/log/Logger.java +++ /dev/null @@ -1,223 +0,0 @@ -package blade.kit.log; - -import java.lang.reflect.Constructor; - -/** - * 日志输出 - * - * @author biezhi - * @since 1.0 - */ -public abstract class Logger { - - private static Class logClass = SysLoggerAdaptor.class; - private static Constructor logConstructor; - - public static final int TRACE = 10; - public static final int DEBUG = 20; - public static final int INFO = 30; - public static final int WARN = 40; - public static final int ERROR = 50; - public static final int FATAL = 60; - - private int level = Logger.DEBUG; - - /** - * 日志名称 - */ - protected String name; - - public abstract void log(int level, Object message, Throwable t, Object... args); - - public abstract void log(int level, Object message, Object... args); - - static{ - try { - Class.forName("org.apache.log4j.Logger"); - logClass = Log4jLogAdaptor.class; - } catch (Exception e) { - logClass = SysLoggerAdaptor.class; - } - } - - public static void setLoggerImpl(Class loggerClass){ - logClass = loggerClass; - } - - public static Logger getLogger(String name) { - try { - - if (logConstructor == null) { - synchronized (Logger.class) { - if (logConstructor == null) - logConstructor = logClass.getConstructor(String.class); - } - } - Logger log = (Logger) logConstructor.newInstance(name); - return log; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static Logger getLogger(Class clazz) { - return getLogger(clazz.getName()); - } - - public static Logger getLogger() { - String currentClassName = Thread.currentThread().getStackTrace()[2].getClassName(); - return getLogger(currentClassName); - } - - public String getName() { - return this.name; - } - - public void setLevel(int level) { - this.level = level; - } - - public void trace(Object message) { - if (level <= TRACE) - log(TRACE, message); - } - - public void trace(Object message, Object... args) { - if (level <= TRACE) - log(TRACE, message, args); - } - - public void trace(Object message, Throwable t) { - if (level <= TRACE) - log(TRACE, message, t); - } - - - public void trace(Object message, Throwable t, Object... args) { - if (level <= TRACE) - log(TRACE, message, t, args); - } - - - public void debug(Object message) { - if (level <= DEBUG) - log(DEBUG, message); - } - - - public void debug(Object message, Object... args) { - if (level <= DEBUG) - log(DEBUG, message, args); - } - - - public void debug(Object message, Throwable t) { - if (level <= DEBUG) - log(DEBUG, message, t); - } - - - public void debug(Object message, Throwable t, Object... args) { - if (level <= DEBUG) - log(DEBUG, message, t, args); - } - - - public void info(Object message) { - if (level <= INFO) - log(INFO, message); - } - - - public void info(Object message, Object... args) { - if (level <= INFO) - log(INFO, message, args); - } - - - public void info(Object message, Throwable t) { - if (level <= INFO) - log(INFO, message, t); - } - - - public void info(Object message, Throwable t, Object... args) { - if (level <= INFO) - log(INFO, message, t, args); - } - - - public void warn(Object message) { - if (level <= WARN) - log(WARN, message); - } - - - public void warn(Object message, Object... args) { - if (level <= WARN) - log(WARN, message, args); - } - - - public void warn(Object message, Throwable t) { - if (level <= WARN) - log(WARN, message, t); - } - - - public void warn(Object message, Throwable t, Object... args) { - if (level <= WARN) - log(WARN, message, t, args); - } - - - public void error(Object message) { - if (level <= ERROR) - log(ERROR, message); - } - - - public void error(Object message, Object... args) { - if (level <= ERROR) - log(ERROR, message, args); - } - - - public void error(Object message, Throwable t) { - if (level <= ERROR) - log(ERROR, message, t); - } - - - public void error(Object message, Throwable t, Object... args) { - if (level <= ERROR) - log(ERROR, message, t, args); - } - - - public void fatal(Object message, Object... args) { - if (level <= FATAL) - log(FATAL, message, args); - } - - - public void fatal(Object message, Throwable t, Object... args) { - if (level <= FATAL) - log(FATAL, message, t, args); - } - - protected String format(Object message, Object... args) { - if (message == null) { - return null; - } - - if (args == null || args.length == 0) - return message.toString(); - else - return String.format(message.toString(), args); - } - - public boolean isDebugEnabled() { - return level <= DEBUG; - } -} diff --git a/blade-kit/src/main/java/blade/kit/log/SysLoggerAdaptor.java b/blade-kit/src/main/java/blade/kit/log/SysLoggerAdaptor.java deleted file mode 100644 index 6a0ce0ea9..000000000 --- a/blade-kit/src/main/java/blade/kit/log/SysLoggerAdaptor.java +++ /dev/null @@ -1,70 +0,0 @@ -package blade.kit.log; - -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * Blade自带的日志实现 - * - * @author biezhi - * @since 1.0 - */ -public class SysLoggerAdaptor extends Logger { - - private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS"); - - public SysLoggerAdaptor() { - } - - public SysLoggerAdaptor(String name) { - this.name = name; - } - - private String getLevel(int level){ - if(level <= TRACE){ - return "TRACE"; - } - if(level <= DEBUG){ - return "DEBUG"; - } - if(level <= INFO){ - return "INFO"; - } - if(level <= WARN){ - return "WARN"; - } - if(level <= ERROR){ - return "ERROR"; - } - if(level <= FATAL){ - return "FATAL"; - } - return "DEBUG"; - } - - private String now() { - return sdf.format(new Date()); - } - - @Override - public void log(int level, Object message, Object... args) { - log(level, message, null, args); - - } - - @Override - public void log(int level, Object message, Throwable t, Object... args) { - - StringBuilder sb = new StringBuilder(now()); - sb.append(" ").append(getLevel(level)).append(" "); - sb.append("[").append(Thread.currentThread().getName()).append("]").append(" "); - sb.append(getName()).append(" | "); - sb.append(format(message, args)); - System.out.println(sb.toString()); - if (t != null) { - t.printStackTrace(System.err); - } - - } - -} diff --git a/blade-kit/src/main/java/blade/kit/log/package-info.java b/blade-kit/src/main/java/blade/kit/log/package-info.java deleted file mode 100644 index 798e9707a..000000000 --- a/blade-kit/src/main/java/blade/kit/log/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 内置日志系统包 - */ -package blade.kit.log; \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/mail/EmailHandle.java b/blade-kit/src/main/java/blade/kit/mail/EmailHandle.java deleted file mode 100644 index 1480aebd3..000000000 --- a/blade-kit/src/main/java/blade/kit/mail/EmailHandle.java +++ /dev/null @@ -1,286 +0,0 @@ -package blade.kit.mail; - -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Properties; - -import javax.activation.DataHandler; -import javax.activation.FileDataSource; -import javax.mail.BodyPart; -import javax.mail.Message; -import javax.mail.Multipart; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; -import javax.mail.internet.MimeUtility; - -import blade.kit.log.Logger; - -/** - * 邮件发送处理工具类 - * @author jiang.li - * @date 2013-12-19 14:08 - */ -public class EmailHandle { - - private static final Logger LOGGER = Logger.getLogger(EmailHandle.class); - - /** 邮件对象 **/ - private MimeMessage mimeMsg; - - /** 发送邮件的Session会话 **/ - private Session session; - - /** 邮件发送时的一些配置信息的一个属性对象 **/ - private Properties props; - - /** 发件人的用户名 **/ - private String sendUserName; - - /** 发件人密码 **/ - private String sendUserPass; - - /** 附件添加的组件 **/ - private Multipart mp; - - /** 存放附件文件 **/ - private List files = new LinkedList(); - - - public EmailHandle(String smtp) { - sendUserName = ""; - sendUserPass = ""; - setSmtpHost(smtp); - createMimeMessage(); - } - - private void setSmtpHost(String hostName) { - if (props == null){ - props = System.getProperties(); - } - props.put("mail.smtp.host", hostName); - } - - public boolean createMimeMessage() { - try { - /**用props对象来创建并初始化session对象**/ - session = Session.getDefaultInstance(props, null); - /**用session对象来创建并初始化邮件对象**/ - mimeMsg = new MimeMessage(session); - /**生成附件组件的实例**/ - mp = new MimeMultipart(); - return true; - } catch (Exception e) { - System.err.println("获取邮件会话对象时发生错误!" + e); - return false; - } - - } - - /** - * 设置SMTP的身份认证 - */ - public void setNeedAuth(boolean need) { - if (props == null){ props = System.getProperties();} - if (need){ - props.put("mail.smtp.auth", "true"); - }else{ - props.put("mail.smtp.auth", "false"); - } - - } - - /** - * 进行用户身份验证时,设置用户名和密码 - */ - public void setNamePass(String name, String pass) { - sendUserName = name; - sendUserPass = pass; - } - - /** - * 设置邮件主题 - * - * @param mailSubject - * @return - */ - public boolean setSubject(String mailSubject) { - try { - mimeMsg.setSubject(mailSubject); - return true; - } catch (Exception e) { - return false; - } - - } - - /** - * 设置邮件内容,并设置其为文本格式或HTML文件格式,编码方式为UTF-8 - * @param mailBody - * @return - */ - public boolean setBody(String mailBody) { - try { - BodyPart bp = new MimeBodyPart(); - bp.setContent("" + mailBody, "text/html;charset=UTF-8"); - /**在组件上添加邮件文本**/ - mp.addBodyPart(bp); - } catch (Exception e) { - System.err.println("设置邮件正文时发生错误!" + e); - return false; - } - return true; - } - - /** - * 增加发送附件 - * @param filename 邮件附件的地址,只能是本机地址而不能是网络地址,否则抛出异常 - * @return - */ - public boolean addFileAffix(String filename) { - try { - BodyPart bp = new MimeBodyPart(); - FileDataSource fileds = new FileDataSource(filename); - bp.setDataHandler(new DataHandler(fileds)); - /**解决附件名称乱码**/ - bp.setFileName(MimeUtility.encodeText(fileds.getName(), "UTF-8",null)); - /**添加附件**/ - mp.addBodyPart(bp); - files.add(fileds); - return true; - } catch (Exception e) { - System.err.println("增加邮件附件<" + filename + ">时发生错误:" + e); - return false; - } - - } - - /** - * 删除添加的附件 - * @return - */ - public boolean delFileAffix() { - try { - FileDataSource fileds = null; - for (Iterator it = files.iterator(); it.hasNext();) { - fileds = it.next(); - if (fileds != null && fileds.getFile() != null) { - fileds.getFile().delete(); - } - } - return true; - } catch (Exception e) { - System.err.println("删除邮件附件发生错误:" + e); - return false; - } - - } - - /** - * 设置发件人地址 - * @param from 发件人地址 - * @return - */ - public boolean setFrom(String from) { - try { - mimeMsg.setFrom(new InternetAddress(from)); - return true; - } catch (Exception e) { - return false; - } - } - - /** - * 设置收件人地址 - * @param to收件人的地址 - * @return - */ - public boolean setTo(String to) { - try { - if (to == null) - return false; - - mimeMsg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); - return true; - } catch (Exception e) { - return false; - } - } - - /** - * 设置收件人地址 - * @param toList 收件人的地址列表 - * @return - */ - public boolean setToList(String toList) { - try { - if (toList == null) - return false; - InternetAddress[] iaToList = InternetAddress.parse(toList); - mimeMsg.setRecipients(Message.RecipientType.TO, iaToList); - return true; - } catch (Exception e) { - return false; - } - } - - /** - * 发送抄送 - * @param copyto - * @return - */ - public boolean setCopyTo(String copyto) { - try { - if (copyto == null) - return false; - mimeMsg.setRecipients(javax.mail.Message.RecipientType.CC,InternetAddress.parse(copyto)); - return true; - } catch (Exception e) { - return false; - } - } - - /** - * 发送抄送 - * @param copyto - * @return - */ - public boolean setCopyToList(String copytoList) { - try { - if (copytoList == null) - return false; - InternetAddress[] iacopytoList = InternetAddress.parse(copytoList); - mimeMsg.setRecipients(javax.mail.Message.RecipientType.CC, iacopytoList); - return true; - } catch (Exception e) { - return false; - } - } - - /** - * 发送邮件 - * @return - */ - public boolean sendEmail() throws Exception { - LOGGER.debug("正在发送邮件...."); - - mimeMsg.setContent(mp); - mimeMsg.saveChanges(); - Session mailSession = Session.getInstance(props, null); - Transport transport = mailSession.getTransport("smtp"); - - /** 连接邮件服务器并进行身份验证 **/ - transport.connect((String) props.get("mail.smtp.host"), sendUserName, sendUserPass); - - /** 发送邮件 **/ - transport.sendMessage(mimeMsg, mimeMsg.getRecipients(Message.RecipientType.TO)); - transport.close(); - LOGGER.debug("发送邮件成功!"); - return true; - } - -} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/mail/MailTemplate.java b/blade-kit/src/main/java/blade/kit/mail/MailTemplate.java deleted file mode 100644 index e83ded523..000000000 --- a/blade-kit/src/main/java/blade/kit/mail/MailTemplate.java +++ /dev/null @@ -1,155 +0,0 @@ -package blade.kit.mail; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; - -import blade.kit.FileKit; -import blade.kit.PatternKit; -import blade.kit.StringKit; - -/** - * 邮件发送模板 - * - * @author biezhi - * @since 1.0 - */ -public class MailTemplate { - - private String templateBody; - private List ccMails = new ArrayList(); - private List toMails = new ArrayList(); - private List fileList = new ArrayList(); - - public MailTemplate() { - } - - public MailTemplate(String templetPath, String toMail, String ccMail) throws IOException { - - loadTemplet(templetPath); - - if(PatternKit.isEmail(toMail)){ - this.toMails.add(toMail); - } - - if(PatternKit.isEmail(ccMail)){ - this.ccMails.add(ccMail); - } - } - - /** - * 添加附件 - * @param filePath - * @return - */ - public MailTemplate addFile(String filePath){ - if(FileKit.isFile(filePath)){ - this.fileList.add(filePath); - } - return this; - } - - /** - * 添加附件列表 - * @param filePath - * @return - */ - public MailTemplate addFiles(List files){ - if(null != files && files.size() > 0){ - this.fileList.addAll(files); - } - return this; - } - - /** - * 发送给谁 - * @param toMail - * @return - */ - public MailTemplate toMail(String ... toMails){ - if(null != toMails && toMails.length > 0){ - for(String toMail : toMails){ - if(PatternKit.isEmail(toMail)){ - this.toMails.add(toMail); - } - } - } - return this; - } - - public MailTemplate ccMail(String... ccMails){ - if(null != ccMails && ccMails.length > 0){ - for(String ccMail : ccMails){ - if(PatternKit.isEmail(ccMail)){ - this.ccMails.add(ccMail); - } - } - } - return this; - } - - /** - * 加载模板 - * @param templetPath - * @return - * @throws IOException - */ - public MailTemplate loadTemplet(String templetPath) throws IOException { - InputStream input = null; - InputStreamReader read = null; - BufferedReader reader = null; - - if (!new File(templetPath).exists()) { - templateBody = ""; - } - try { - input = new FileInputStream(templetPath); - read = new InputStreamReader(input, "UTF-8"); - reader = new BufferedReader(read); - String line; - String result = ""; - while ((line = reader.readLine()) != null) { - result += line + "\n"; - } - templateBody = result.substring(result.indexOf("")); - } catch (Exception e) { - e.printStackTrace(); - templateBody = ""; - } finally { - reader.close(); - read.close(); - input.close(); - } - - return this; - } - - @Override - public String toString() { - return this.templateBody; - } - - public String getToMail() { - if(null != toMails && toMails.size() > 0){ - StringKit.join(toMails, ",").substring(1); - } - return null; - } - - public String getCcMail() { - if(null != ccMails && ccMails.size() > 0){ - StringKit.join(ccMails, ",").substring(1); - } - return null; - } - - public List getFileList() { - return fileList; - } - -} diff --git a/blade-kit/src/main/java/blade/kit/mail/package-info.java b/blade-kit/src/main/java/blade/kit/mail/package-info.java deleted file mode 100644 index a487957c4..000000000 --- a/blade-kit/src/main/java/blade/kit/mail/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 邮件发送包 - */ -package blade.kit.mail; \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/package-info.java b/blade-kit/src/main/java/blade/kit/package-info.java deleted file mode 100644 index 08a579341..000000000 --- a/blade-kit/src/main/java/blade/kit/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 一些通用工具包 - */ -package blade.kit; \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/resource/package-info.java b/blade-kit/src/main/java/blade/kit/resource/package-info.java deleted file mode 100644 index 7e091b6f7..000000000 --- a/blade-kit/src/main/java/blade/kit/resource/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 资源搜索包 - */ -package blade.kit.resource; \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/timw/TimwCounter.java b/blade-kit/src/main/java/blade/kit/timw/TimwCounter.java deleted file mode 100644 index 68cd13cea..000000000 --- a/blade-kit/src/main/java/blade/kit/timw/TimwCounter.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.kit.timw; - -/** - * 计数器 - *

- *

- * - * @author biezhi - * @since 1.0 - */ -class TimwCounter { - - private long time; - - public TimwCounter() { - start(); - } - - /** - * @return 开始计时并返回当前时间 - */ - public long start() { - time = System.currentTimeMillis(); - return time; - } - - /** - * @return 重新计时并返回当前时间 - */ - public long durationRestart() { - long now = System.currentTimeMillis(); - long d = now - time; - time = now; - return d; - } - - /** - * @return 返回计时花费的时间 - */ - public long duration() { - return System.currentTimeMillis() - time; - } - -} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/timw/TimwMonitor.java b/blade-kit/src/main/java/blade/kit/timw/TimwMonitor.java deleted file mode 100644 index a721e172c..000000000 --- a/blade-kit/src/main/java/blade/kit/timw/TimwMonitor.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.kit.timw; - -import blade.kit.TimwKit; - -/** - * 计时器 - *

- * 用于统计计时的类 - *

- * - * @author biezhi - * @since 1.0 - */ -public class TimwMonitor { - - /** - * 计时器 - */ - private TimwCounter timeKit; - - /** - * 均值器 - */ - private TimwKit averager; - - TimwMonitor() { - this.timeKit = new TimwCounter(); - this.averager = new TimwKit(); - } - - - public TimwCounter getTimeKit() { - return timeKit; - } - - public TimwKit getAverager() { - return averager; - } - - /** - * 一个计时开始 - */ - public void start() { - timeKit.start(); - } - - /** - * 一个计时结束 - */ - public void end() { - long time = timeKit.duration(); - averager.add(time); - } - - /** - * 一个计时结束,并且启动下次计时。 - */ - public long endAndRestart() { - long time = timeKit.durationRestart(); - averager.add(time); - return time; - } - - /** - * 求全部计时均值 - */ - public Number average() { - return averager.getAverage(); - } - - /** - * 打印全部时间值 - */ - public String render() { - return averager.print(); - } - - /** - * 打印全部时间值 - */ - public String renderAvg() { - return averager.printAvg(); - } - - /** - * 清楚数据 - */ - public void clear() { - averager.clear(); - } - -} diff --git a/blade-kit/src/main/java/blade/kit/timw/package-info.java b/blade-kit/src/main/java/blade/kit/timw/package-info.java deleted file mode 100644 index c7efc5c84..000000000 --- a/blade-kit/src/main/java/blade/kit/timw/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 计时器包 - */ -package blade.kit.timw; \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/AES.java b/blade-kit/src/main/java/com/blade/kit/AES.java new file mode 100644 index 000000000..906d4adcd --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/AES.java @@ -0,0 +1,58 @@ +package com.blade.kit; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +/** + * Aes encryption + */ +public class AES { + + private static SecretKeySpec secretKey; + private static byte[] key; + + public static void setKey(String myKey) { + MessageDigest sha = null; + try { + key = myKey.getBytes("UTF-8"); + sha = MessageDigest.getInstance("SHA-1"); + key = sha.digest(key); + key = Arrays.copyOf(key, 16); // use only first 128 bit + secretKey = new SecretKeySpec(key, "AES"); + + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + public static String encrypt(String strToEncrypt) { + try { + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + return Base64.encodeBytes(cipher.doFinal(strToEncrypt.getBytes("UTF-8"))); + } catch (Exception e) { + System.out.println("Error while encrypting: " + e.toString()); + } + return null; + } + + public static String decrypt(String strToDecrypt) { + try { + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING"); + + cipher.init(Cipher.DECRYPT_MODE, secretKey); + return new String(cipher.doFinal(Base64.decode(strToDecrypt))); + + } catch (Exception e) { + System.out.println("Error while decrypting: " + e.toString()); + } + return null; + } + +} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/Assert.java b/blade-kit/src/main/java/com/blade/kit/Assert.java similarity index 78% rename from blade-kit/src/main/java/blade/kit/Assert.java rename to blade-kit/src/main/java/com/blade/kit/Assert.java index 271422882..8556169cb 100644 --- a/blade-kit/src/main/java/blade/kit/Assert.java +++ b/blade-kit/src/main/java/com/blade/kit/Assert.java @@ -1,286 +1,365 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.kit; - -import java.util.Collection; -import java.util.Map; - -/** - * 断言工具类 - * - * @author biezhi - * @since 1.0 - * - */ -public abstract class Assert { - - private static final String DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE = "The value %s is not in the specified exclusive range of %s to %s"; - private static final String DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE = "The value %s is not in the specified inclusive range of %s to %s"; - private static final String DEFAULT_IS_TRUE_EX_MESSAGE = "The validated expression is false"; - private static final String DEFAULT_IS_FALSE_EX_MESSAGE = "The validated expression is true"; - private static final String DEFAULT_NOT_BLANK_EX_MESSAGE = "The validated character sequence is blank"; - private static final String DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE = "The validated array is empty"; - private static final String DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE = "The validated character sequence is empty"; - private static final String DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE = "The validated collection is empty"; - private static final String DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE = "The validated map is empty"; - private static final String DEFAULT_IS_ASSIGNABLE_EX_MESSAGE = "Cannot assign a %s to a %s"; - private static final String DEFAULT_IS_INSTANCE_OF_EX_MESSAGE = "Expected type: %s, actual: %s"; - - public static void isTrue(boolean expression, String message) { - if (!expression) { - throw new IllegalArgumentException(message); - } - } - - public static void notNull(Object object, String message) { - if (object == null) { - throw new IllegalArgumentException(message); - } - } - - public static void notNull(Object object) { - notNull(object, "[Assertion failed] - this argument is required; it must not be null"); - } - - public static void hasLength(String text, String message) { - if (StringKit.isEmpty(text)) { - throw new IllegalArgumentException(message); - } - } - - public static void state(boolean expression, String message) { - if (!expression) { - throw new IllegalStateException(message); - } - } - - public static void isTrue(boolean expression) { - if (expression == false) { - throw new IllegalArgumentException(DEFAULT_IS_TRUE_EX_MESSAGE); - } - } - - public static void isTrue(boolean expression, String message, Object... values) { - if (expression == false) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void isFalse(boolean expression) { - if (expression) { - throw new IllegalArgumentException(DEFAULT_IS_FALSE_EX_MESSAGE); - } - } - - public static void isFalse(boolean expression, String message, Object... values) { - if (expression) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void notNull(Object object, String message, Object... values) { - if (object == null) { - throw new NullPointerException(String.format(message, values)); - } - } - - public static void notEmpty(Object[] array) { - if (array == null) { - throw new NullPointerException(DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE); - } - if (array.length == 0) { - throw new IllegalArgumentException(DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE); - } - } - - public static void notEmpty(Object[] array, String message, Object... values) { - if (array == null) { - throw new NullPointerException(String.format(message, values)); - } - if (array.length == 0) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void notEmpty(Collection collection) { - if (collection == null) { - throw new NullPointerException(DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE); - } - if (collection.size() == 0) { - throw new IllegalArgumentException(DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE); - } - } - - public static void notEmpty(Collection collection, String message, Object... values) { - if (collection == null) { - throw new NullPointerException(String.format(message, values)); - } - if (collection.size() == 0) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void notEmpty(Map map) { - if (map == null) { - throw new NullPointerException(DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE); - } - if (map.size() == 0) { - throw new IllegalArgumentException(DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE); - } - } - - public static void notEmpty(Map map, String message, Object... values) { - if (map == null) { - throw new NullPointerException(String.format(message, values)); - } - if (map.size() == 0) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void notEmpty(CharSequence str) { - if (str == null) { - throw new NullPointerException(DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE); - } - if (str.length() == 0) { - throw new IllegalArgumentException(DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE); - } - } - - public static void notEmpty(CharSequence str, String message, Object... values) { - if (str == null) { - throw new NullPointerException(String.format(message, values)); - } - if (str.length() == 0) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void notBlank(CharSequence str) { - if (str == null) { - throw new NullPointerException(DEFAULT_NOT_BLANK_EX_MESSAGE); - } - if (str.toString().trim().length() == 0) { - throw new IllegalArgumentException(DEFAULT_NOT_BLANK_EX_MESSAGE); - } - } - - public static void notBlank(CharSequence str, String message, Object... values) { - if (str == null) { - throw new NullPointerException(String.format(message, values)); - } - if (str.toString().trim().length() == 0) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void inclusiveBetween(final T start, final T end, final Comparable value) { - if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { - throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); - } - } - - public static void inclusiveBetween(final T start, final T end, final Comparable value, final String message, final Object... values) { - if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void inclusiveBetween(long start, long end, long value) { - if (value < start || value > end) { - throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); - } - } - - public static void inclusiveBetween(long start, long end, long value, final String message, final Object... values) { - if (value < start || value > end) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void inclusiveBetween(double start, double end, double value) { - if (value < start || value > end) { - throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); - } - } - - public static void inclusiveBetween(double start, double end, double value, final String message, final Object... values) { - if (value < start || value > end) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void exclusiveBetween(final T start, final T end, final Comparable value) { - if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) { - throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); - } - } - - public static void exclusiveBetween(final T start, final T end, final Comparable value, final String message, final Object... values) { - if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void exclusiveBetween(long start, long end, long value) { - if (value <= start || value >= end) { - throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); - } - } - - public static void exclusiveBetween(long start, long end, long value, final String message, final Object... values) { - if (value <= start || value >= end) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void exclusiveBetween(double start, double end, double value) { - if (value <= start || value >= end) { - throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); - } - } - - public static void exclusiveBetween(double start, double end, double value, final String message, final Object... values) { - if (value <= start || value >= end) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void isInstanceOf(Class type, Object obj) { - if (type.isInstance(obj) == false) { - throw new IllegalArgumentException(String.format(DEFAULT_IS_INSTANCE_OF_EX_MESSAGE, type.getName(), obj == null ? "null" : obj.getClass().getName())); - } - } - - public static void isInstanceOf(Class type, Object obj, String message, Object... values) { - if (type.isInstance(obj) == false) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - - public static void isAssignableFrom(Class superType, Class type) { - if (superType.isAssignableFrom(type) == false) { - throw new IllegalArgumentException(String.format(DEFAULT_IS_ASSIGNABLE_EX_MESSAGE, type == null ? "null" : type.getName(), superType.getName())); - } - } - - public static void isAssignableFrom(Class superType, Class type, String message, Object... values) { - if (superType.isAssignableFrom(type) == false) { - throw new IllegalArgumentException(String.format(message, values)); - } - } - -} +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; + +import java.util.Collection; +import java.util.Map; + +/** + * 断言工具类 + * + * @author biezhi + * @since 1.0 + * + */ +public abstract class Assert { + + private static final String DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE = "The value %s is not in the specified exclusive range of %s to %s"; + private static final String DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE = "The value %s is not in the specified inclusive range of %s to %s"; + private static final String DEFAULT_IS_TRUE_EX_MESSAGE = "The validated expression is false"; + private static final String DEFAULT_IS_FALSE_EX_MESSAGE = "The validated expression is true"; + private static final String DEFAULT_NOT_BLANK_EX_MESSAGE = "The validated character sequence is blank"; + private static final String DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE = "The validated array is empty"; + private static final String DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE = "The validated character sequence is empty"; + private static final String DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE = "The validated collection is empty"; + private static final String DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE = "The validated map is empty"; + private static final String DEFAULT_IS_ASSIGNABLE_EX_MESSAGE = "Cannot assign a %s to a %s"; + private static final String DEFAULT_IS_INSTANCE_OF_EX_MESSAGE = "Expected type: %s, actual: %s"; + + public static void isTrue(boolean expression, String message) { + if (!expression) { + throw new IllegalArgumentException(message); + } + } + + public static void notNull(Object object, String message) { + if (object == null) { + throw new NullPointerException(message); + } + } + + public static void notNull(Object object) { + notNull(object, "[Assertion failed] - this argument is required; it must not be null"); + } + + public static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } + + public static void checkPositionIndexes(int start, int end, int size) { + // Carefully optimized for execution by hotspot (explanatory comment + // above) + if (start < 0 || end < start || end > size) { + throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size)); + } + } + + private static String badPositionIndexes(int start, int end, int size) { + if (start < 0 || start > size) { + return badPositionIndex(start, size, "start index"); + } + if (end < 0 || end > size) { + return badPositionIndex(end, size, "end index"); + } + // end < start + return format("end index (%s) must not be less than start index (%s)", end, start); + } + + private static String badPositionIndex(int index, int size, String desc) { + if (index < 0) { + return format("%s (%s) must not be negative", desc, index); + } else if (size < 0) { + throw new IllegalArgumentException("negative size: " + size); + } else { // index > size + return format("%s (%s) must not be greater than size (%s)", desc, index, size); + } + } + + /** + * Substitutes each {@code %s} in {@code template} with an argument. These are matched by + * position: the first {@code %s} gets {@code args[0]}, etc. If there are more arguments than + * placeholders, the unmatched arguments will be appended to the end of the formatted message in + * square braces. + * + * @param template a non-null string containing 0 or more {@code %s} placeholders. + * @param args the arguments to be substituted into the message template. Arguments are converted + * to strings using {@link String#valueOf(Object)}. Arguments can be null. + */ + // Note that this is somewhat-improperly used from Verify.java as well. + static String format(String template, Object... args) { + template = String.valueOf(template); // null -> "null" + + // start substituting the arguments into the '%s' placeholders + StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + builder.append(template.substring(templateStart, placeholderStart)); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template.substring(templateStart)); + + // if we run out of placeholders, append the extra args in square braces + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + builder.append(']'); + } + + return builder.toString(); + } + + public static void hasLength(String text, String message) { + if (StringKit.isEmpty(text)) { + throw new IllegalArgumentException(message); + } + } + + public static void state(boolean expression, String message) { + if (!expression) { + throw new IllegalStateException(message); + } + } + + public static void isTrue(boolean expression) { + if (expression == false) { + throw new IllegalArgumentException(DEFAULT_IS_TRUE_EX_MESSAGE); + } + } + + public static void isTrue(boolean expression, String message, Object... values) { + if (expression == false) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void isFalse(boolean expression) { + if (expression) { + throw new IllegalArgumentException(DEFAULT_IS_FALSE_EX_MESSAGE); + } + } + + public static void isFalse(boolean expression, String message, Object... values) { + if (expression) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void notNull(Object object, String message, Object... values) { + if (object == null) { + throw new NullPointerException(String.format(message, values)); + } + } + + public static void notEmpty(Object[] array) { + if (array == null) { + throw new NullPointerException(DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE); + } + if (array.length == 0) { + throw new IllegalArgumentException(DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE); + } + } + + public static void notEmpty(Object[] array, String message, Object... values) { + if (array == null) { + throw new NullPointerException(String.format(message, values)); + } + if (array.length == 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void notEmpty(Collection collection) { + if (collection == null) { + throw new NullPointerException(DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE); + } + if (collection.size() == 0) { + throw new IllegalArgumentException(DEFAULT_NOT_EMPTY_COLLECTION_EX_MESSAGE); + } + } + + public static void notEmpty(Collection collection, String message, Object... values) { + if (collection == null) { + throw new NullPointerException(String.format(message, values)); + } + if (collection.size() == 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void notEmpty(Map map) { + if (map == null) { + throw new NullPointerException(DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE); + } + if (map.size() == 0) { + throw new IllegalArgumentException(DEFAULT_NOT_EMPTY_MAP_EX_MESSAGE); + } + } + + public static void notEmpty(Map map, String message, Object... values) { + if (map == null) { + throw new NullPointerException(String.format(message, values)); + } + if (map.size() == 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void notEmpty(CharSequence str) { + if (str == null) { + throw new NullPointerException(DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE); + } + if (str.length() == 0) { + throw new IllegalArgumentException(DEFAULT_NOT_EMPTY_CHAR_SEQUENCE_EX_MESSAGE); + } + } + + public static void notEmpty(CharSequence str, String message, Object... values) { + if (str == null) { + throw new NullPointerException(String.format(message, values)); + } + if (str.length() == 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void notBlank(CharSequence str) { + if (str == null) { + throw new NullPointerException(DEFAULT_NOT_BLANK_EX_MESSAGE); + } + if (str.toString().trim().length() == 0) { + throw new IllegalArgumentException(DEFAULT_NOT_BLANK_EX_MESSAGE); + } + } + + public static void notBlank(CharSequence str, String message, Object... values) { + if (str == null) { + throw new NullPointerException(String.format(message, values)); + } + if (str.toString().trim().length() == 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void inclusiveBetween(final T start, final T end, final Comparable value) { + if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { + throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + public static void inclusiveBetween(final T start, final T end, final Comparable value, final String message, final Object... values) { + if (value.compareTo(start) < 0 || value.compareTo(end) > 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void inclusiveBetween(long start, long end, long value) { + if (value < start || value > end) { + throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + public static void inclusiveBetween(long start, long end, long value, final String message, final Object... values) { + if (value < start || value > end) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void inclusiveBetween(double start, double end, double value) { + if (value < start || value > end) { + throw new IllegalArgumentException(String.format(DEFAULT_INCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + public static void inclusiveBetween(double start, double end, double value, final String message, final Object... values) { + if (value < start || value > end) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void exclusiveBetween(final T start, final T end, final Comparable value) { + if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) { + throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + public static void exclusiveBetween(final T start, final T end, final Comparable value, final String message, final Object... values) { + if (value.compareTo(start) <= 0 || value.compareTo(end) >= 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void exclusiveBetween(long start, long end, long value) { + if (value <= start || value >= end) { + throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + public static void exclusiveBetween(long start, long end, long value, final String message, final Object... values) { + if (value <= start || value >= end) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void exclusiveBetween(double start, double end, double value) { + if (value <= start || value >= end) { + throw new IllegalArgumentException(String.format(DEFAULT_EXCLUSIVE_BETWEEN_EX_MESSAGE, value, start, end)); + } + } + + public static void exclusiveBetween(double start, double end, double value, final String message, final Object... values) { + if (value <= start || value >= end) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void isInstanceOf(Class type, Object obj) { + if (type.isInstance(obj) == false) { + throw new IllegalArgumentException(String.format(DEFAULT_IS_INSTANCE_OF_EX_MESSAGE, type.getName(), obj == null ? "null" : obj.getClass().getName())); + } + } + + public static void isInstanceOf(Class type, Object obj, String message, Object... values) { + if (type.isInstance(obj) == false) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + public static void isAssignableFrom(Class superType, Class type) { + if (superType.isAssignableFrom(type) == false) { + throw new IllegalArgumentException(String.format(DEFAULT_IS_ASSIGNABLE_EX_MESSAGE, type == null ? "null" : type.getName(), superType.getName())); + } + } + + public static void isAssignableFrom(Class superType, Class type, String message, Object... values) { + if (superType.isAssignableFrom(type) == false) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/Base64.java b/blade-kit/src/main/java/com/blade/kit/Base64.java new file mode 100644 index 000000000..69cd26e7d --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/Base64.java @@ -0,0 +1,292 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; + +import java.io.UnsupportedEncodingException; + +/** + * BASE64工具类 + * + * @author biezhi + * @since 1.0 + */ +public class Base64 { + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte) '='; + + /** Preferred encoding. */ + private final static String PREFERRED_ENCODING = "US-ASCII"; + + /** The 64 valid Base64 values. */ + private final static byte[] _STANDARD_ALPHABET = { (byte) 'A', (byte) 'B', + (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', + (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', + (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', + (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', + (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', + (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', + (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', + (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', + (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', + (byte) '+', (byte) '/' }; + + private static byte[] base64DecodeChars = new byte[] { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, + -1, -1, -1 }; + + /** Defeats instantiation. */ + private Base64() { + } + + /** + *

+ * Encodes up to three bytes of the array source and writes the + * resulting four Base64 bytes to destination. The source and + * destination arrays can be manipulated anywhere along their length by + * specifying srcOffset and destOffset. This method + * does not check to make sure your arrays are large enough to accomodate + * srcOffset + 3 for the source array or + * destOffset + 4 for the destination array. The + * actual number of significant bytes in your array is given by + * numSigBytes. + *

+ *

+ * This is the lowest level of the encoding methods with all possible + * parameters. + *

+ * + * @param source + * the array to convert + * @param srcOffset + * the index where conversion begins + * @param numSigBytes + * the number of significant bytes in your array + * @param destination + * the array to hold the conversion + * @param destOffset + * the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4(byte[] source, int srcOffset, + int numSigBytes, byte[] destination, int destOffset) { + + byte[] ALPHABET = _STANDARD_ALPHABET; + + int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) + | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) + | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); + + switch (numSigBytes) { + case 3: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; + return destination; + + case 2: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + case 1: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = EQUALS_SIGN; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + default: + return destination; + } + } + + /** + * Encode string as a byte array in Base64 annotation. + * + * @param string + * @return The Base64-encoded data as a string + */ + public static String encode(String string) { + byte[] bytes; + try { + bytes = string.getBytes(PREFERRED_ENCODING); + } catch (UnsupportedEncodingException e) { + bytes = string.getBytes(); + } + return encodeBytes(bytes); + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source + * The data to convert + * @return The Base64-encoded data as a String + * @throws NullPointerException + * if source array is null + * @throws IllegalArgumentException + * if source array, offset, or length are invalid + * @since 2.0 + */ + public static String encodeBytes(byte[] source) { + return encodeBytes(source, 0, source.length); + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source + * The data to convert + * @param off + * Offset in array where conversion should begin + * @param len + * Length of data to convert + * @return The Base64-encoded data as a String + * @throws NullPointerException + * if source array is null + * @throws IllegalArgumentException + * if source array, offset, or length are invalid + * @since 2.0 + */ + public static String encodeBytes(byte[] source, int off, int len) { + byte[] encoded = encodeBytesToBytes(source, off, len); + try { + return new String(encoded, PREFERRED_ENCODING); + } catch (UnsupportedEncodingException uue) { + return new String(encoded); + } + } + + /** + * Similar to {@link #encodeBytes(byte[], int, int)} but returns a byte + * array instead of instantiating a String. This is more efficient if you're + * working with I/O streams and have large data sets to encode. + * + * + * @param source + * The data to convert + * @param off + * Offset in array where conversion should begin + * @param len + * Length of data to convert + * @return The Base64-encoded data as a String if there is an error + * @throws NullPointerException + * if source array is null + * @throws IllegalArgumentException + * if source array, offset, or length are invalid + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes(byte[] source, int off, int len) { + + if (source == null) + throw new NullPointerException("Cannot serialize a null array."); + + if (off < 0) + throw new IllegalArgumentException("Cannot have negative offset: " + + off); + + if (len < 0) + throw new IllegalArgumentException("Cannot have length offset: " + len); + + if (off + len > source.length) + throw new IllegalArgumentException( + String + .format( + "Cannot have offset of %d and length of %d with array of length %d", + off, len, source.length)); + + // Bytes needed for actual encoding + int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); + + byte[] outBuff = new byte[encLen]; + + int d = 0; + int e = 0; + int len2 = len - 2; + for (; d < len2; d += 3, e += 4) + encode3to4(source, d + off, 3, outBuff, e); + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e); + e += 4; + } + + if (e <= outBuff.length - 1) { + byte[] finalOut = new byte[e]; + System.arraycopy(outBuff, 0, finalOut, 0, e); + return finalOut; + } else + return outBuff; + } + + //解码 + public static byte[] decode(String str) throws UnsupportedEncodingException { + StringBuffer sb = new StringBuffer(); + byte[] data = str.getBytes("US-ASCII"); + int len = data.length; + int i = 0; + int b1, b2, b3, b4; + while (i < len) { + /* b1 */ + do { + b1 = base64DecodeChars[data[i++]]; + } while (i < len && b1 == -1); + if (b1 == -1) + break; + /* b2 */ + do { + b2 = base64DecodeChars[data[i++]]; + } while (i < len && b2 == -1); + if (b2 == -1) + break; + sb.append((char) ((b1 << 2) | ((b2 & 0X30) >>> 4))); + /* b3 */ + do { + b3 = data[i++]; + if (b3 == 61) + return sb.toString().getBytes("iso8859-1"); + b3 = base64DecodeChars[b3]; + } while (i < len && b3 == -1); + if (b3 == -1) + break; + sb.append((char) (((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2))); + /* b4 */ + do { + b4 = data[i++]; + if (b4 == 61) + return sb.toString().getBytes("iso8859-1"); + b4 = base64DecodeChars[b4]; + } while (i < len && b4 == -1); + if (b4 == -1) + break; + sb.append((char) (((b3 & 0X03) << 6) | b4)); + } + return sb.toString().getBytes("iso8859-1"); + } + + public static String decoder(String str) throws UnsupportedEncodingException{ + return new String(decode(str)); + } + +} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/BeanKit.java b/blade-kit/src/main/java/com/blade/kit/BeanKit.java similarity index 75% rename from blade-kit/src/main/java/blade/kit/BeanKit.java rename to blade-kit/src/main/java/com/blade/kit/BeanKit.java index 6433c0cd3..a283ee3f0 100644 --- a/blade-kit/src/main/java/blade/kit/BeanKit.java +++ b/blade-kit/src/main/java/com/blade/kit/BeanKit.java @@ -1,4 +1,19 @@ -package blade.kit; +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; import java.beans.BeanInfo; import java.beans.Introspector; @@ -91,5 +106,4 @@ public static List> toListMap(List list) { return result; } - } \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/CharKit.java b/blade-kit/src/main/java/com/blade/kit/CharKit.java similarity index 91% rename from blade-kit/src/main/java/blade/kit/CharKit.java rename to blade-kit/src/main/java/com/blade/kit/CharKit.java index aab0e086d..d911bf872 100644 --- a/blade-kit/src/main/java/blade/kit/CharKit.java +++ b/blade-kit/src/main/java/com/blade/kit/CharKit.java @@ -1,4 +1,19 @@ -package blade.kit; +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; import java.io.UnsupportedEncodingException; @@ -221,9 +236,6 @@ public static byte[] toAsciiByteArray(CharSequence charSequence) { return barr; } - // ---------------------------------------------------------------- raw - // arrays - public static byte[] toRawByteArray(char[] carr) { if (carr == null) { return null; @@ -260,8 +272,6 @@ public static char[] toRawCharArray(byte[] barr) { return carr; } - // ---------------------------------------------------------------- encoding - public static byte[] toByteArray(char[] carr) throws UnsupportedEncodingException { if (carr == null) { return null; @@ -290,8 +300,6 @@ public static char[] toCharArray(byte[] barr, String charset) throws Unsupported return new String(barr, charset).toCharArray(); } - // ---------------------------------------------------------------- find - public static boolean equalsOne(char c, char[] match) { if (match == null) { return false; @@ -352,9 +360,7 @@ public static int findFirstDiff(char[] source, int index, char match) { } return -1; } - - // ---------------------------------------------------------------- is - + public static boolean isLowercaseLetter(char c) { return (c >= 'a') && (c <= 'z'); } @@ -399,9 +405,6 @@ public static boolean isChinese(char c) { return (c >= 0x4e00 && c <= 0x9fa5); } - // ---------------------------------------------------------------- - // conversions - public static char toUpperAscii(char c) { if (isLowercaseLetter(c)) { c -= (char) 0x20; diff --git a/blade-kit/src/main/java/com/blade/kit/ClassLoaderKit.java b/blade-kit/src/main/java/com/blade/kit/ClassLoaderKit.java new file mode 100644 index 000000000..60bf386e2 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/ClassLoaderKit.java @@ -0,0 +1,226 @@ +package com.blade.kit; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +public class ClassLoaderKit { + private static final Map abbreviationMap; + + /** + * Returns current thread's context class loader + */ + public static ClassLoader getDefault() { + ClassLoader loader = null; + try { + loader = ClassLoader.class.getClassLoader(); + } catch (Exception e) { + } + if (loader == null) { + loader = ClassLoaderKit.class.getClassLoader(); + if (loader == null) { + loader = ClassLoader.getSystemClassLoader(); + } + } + return loader; + } + + /** + * 使用默认的 ClassLoader 去载入类. + * @return null if class not found + */ + public static Class loadClass(final String qualifiedClassName) { + return loadClass(qualifiedClassName, null); + } + + /** + * 使用默认的 ClassLoader 去载入类. + * @return null if class not found + */ + public static Class loadClass(final String qualifiedClassName, ClassLoader loader) { + try { + return loadClassEx(qualifiedClassName, loader); + } catch (ClassNotFoundException e) { + return null; + } + } + + /** + * 使用默认的 ClassLoader 去载入类. + * @throws ClassNotFoundException + */ + public static Class loadClassEx(final String qualifiedClassName) throws ClassNotFoundException { + return loadClassEx(qualifiedClassName, null); + } + + /** + * 使用指定的 ClassLoader 去载入类. + * @throws ClassNotFoundException + */ + public static Class loadClassEx(final String qualifiedClassName, final ClassLoader classLoader) throws ClassNotFoundException { + Assert.notNull(qualifiedClassName, "qualifiedClassName must be not null"); + + ClassLoader loader = (classLoader == null) ? getDefault() : classLoader; + + // 尝试基本类型 + if (abbreviationMap.containsKey(qualifiedClassName)) { + String className = '[' + abbreviationMap.get(qualifiedClassName); + return Class.forName(className, false, loader).getComponentType(); + } + + // 尝试用 Class.forName() + try { + String className = getCanonicalClassName(qualifiedClassName); + return Class.forName(className, false, loader); + } catch (ClassNotFoundException e) { + } + + // 尝试当做一个内部类去识别 + if (qualifiedClassName.indexOf('$') == -1) { + int ipos = qualifiedClassName.lastIndexOf('.'); + if (ipos > 0) { + try { + String className = qualifiedClassName.substring(0, ipos) + '$' + qualifiedClassName.substring(ipos + 1); + className = getCanonicalClassName(className); + return Class.forName(className, false, loader); + } catch (ClassNotFoundException e) { + } + } + } + + throw new ClassNotFoundException(qualifiedClassName); + } + + /** + * 将 Java 类名转为 {@code Class.forName()} 可以载入的类名格式. + *
+     * getCanonicalClassName("int") == "int";
+     * getCanonicalClassName("int[]") == "[I";
+     * getCanonicalClassName("java.lang.String") == "java.lang.String";
+     * getCanonicalClassName("java.lang.String[]") == "[Ljava.lang.String;";
+     * 
+ */ + public static String getCanonicalClassName(String qualifiedClassName) { + Assert.notNull(qualifiedClassName, "qualifiedClassName must be not null"); + + String name = StringKit.trimToEmpty(qualifiedClassName); + if (name.endsWith("[]")) { + StringBuilder sb = new StringBuilder(); + + while (name.endsWith("[]")) { + name = name.substring(0, name.length() - 2); + sb.append('['); + } + + String abbreviation = abbreviationMap.get(name); + if (abbreviation != null) { + sb.append(abbreviation); + } else { + sb.append('L').append(name).append(';'); + } + + name = sb.toString(); + } + return name; + } + + /** + * Finds the resource with the given name. + * @param name - The resource name + * @return A URL object for reading the resource, or null if the resource could not be found + */ + public static URL getResource(String name) { + return getResource(name, null); + } + + /** + * Finds the resource with the given name. + * @param name - The resource name + * @return A URL object for reading the resource, or null if the resource could not be found + */ + public static URL getResource(String name, ClassLoader classLoader) { + Assert.notNull(name, "resourceName must be not null"); + + if (name.startsWith("/")) { + name = name.substring(1); + } + if (classLoader != null) { + URL url = classLoader.getResource(name); + if (url != null) { + return url; + } + } + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + if (loader != null && loader != classLoader) { + URL url = loader.getResource(name); + if (url != null) { + return url; + } + } + + return ClassLoader.getSystemResource(name); + } + + /** + * Returns an input stream for reading the specified resource. + */ + public static InputStream getResourceAsStream(String name) throws IOException { + return getResourceAsStream(name, null); + } + + /** + * Returns an input stream for reading the specified resource. + */ + public static InputStream getResourceAsStream(String name, ClassLoader classLoader) throws IOException { + URL url = getResource(name, classLoader); + if (url != null) { + return url.openStream(); + } + return null; + } + + /** + * Returns an input stream for reading the specified class. + */ + public static InputStream getClassAsStream(Class clazz) throws IOException { + return getResourceAsStream(getClassFileName(clazz), clazz.getClassLoader()); + } + + /** + * Returns an input stream for reading the specified class. + */ + public static InputStream getClassAsStream(String qualifiedClassName) throws IOException { + return getResourceAsStream(getClassFileName(qualifiedClassName)); + } + + /** + * 获取一个 class 所代表的文件名 + */ + public static String getClassFileName(Class clazz) { + if (clazz.isArray()) { + clazz = clazz.getComponentType(); + } + return getClassFileName(clazz.getName()); + } + + /** + * 获取一个 class 所代表的文件名 + */ + public static String getClassFileName(String qualifiedClassName) { + return qualifiedClassName.replace('.', '/') + ".class"; + } + + static { + abbreviationMap = new HashMap(); + abbreviationMap.put("boolean", "Z"); + abbreviationMap.put("byte", "B"); + abbreviationMap.put("short", "S"); + abbreviationMap.put("char", "C"); + abbreviationMap.put("int", "I"); + abbreviationMap.put("long", "J"); + abbreviationMap.put("float", "F"); + abbreviationMap.put("double", "D"); + } +} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/CloneKit.java b/blade-kit/src/main/java/com/blade/kit/CloneKit.java similarity index 96% rename from blade-kit/src/main/java/blade/kit/CloneKit.java rename to blade-kit/src/main/java/com/blade/kit/CloneKit.java index 4afb3a7db..00a0ee76d 100644 --- a/blade-kit/src/main/java/blade/kit/CloneKit.java +++ b/blade-kit/src/main/java/com/blade/kit/CloneKit.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.kit; +package com.blade.kit; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; diff --git a/blade-kit/src/main/java/blade/kit/CollectionKit.java b/blade-kit/src/main/java/com/blade/kit/CollectionKit.java similarity index 93% rename from blade-kit/src/main/java/blade/kit/CollectionKit.java rename to blade-kit/src/main/java/com/blade/kit/CollectionKit.java index 411e8c62e..9fadf39d3 100644 --- a/blade-kit/src/main/java/blade/kit/CollectionKit.java +++ b/blade-kit/src/main/java/com/blade/kit/CollectionKit.java @@ -1,4 +1,19 @@ -package blade.kit; +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; import java.util.ArrayDeque; import java.util.ArrayList; @@ -49,6 +64,9 @@ */ public abstract class CollectionKit { + private static final int DEFAULT_INITIAL_CAPACITY = 16; + private static final int DEFAULT_CONCURRENCY_LEVEL = 4; + /** * new HashMap */ @@ -81,7 +99,7 @@ public static LinkedHashMap newLinkedHashMap(int size) { * new concurrentHashMap */ public static ConcurrentHashMap newConcurrentHashMap() { - return new ConcurrentHashMap(); + return new ConcurrentHashMap(DEFAULT_INITIAL_CAPACITY, 0.75f, DEFAULT_CONCURRENCY_LEVEL); } /** @@ -90,7 +108,7 @@ public static ConcurrentHashMap newConcurrentHashMap() { public static ConcurrentHashMap newConcurrentHashMap(int size) { return new ConcurrentHashMap(size); } - + /** * new ArrayList */ @@ -1105,8 +1123,7 @@ public static ConcurrentSkipListMap createConcurrentSkipListMap(Sor /** * 创建ConcurrentSkipListSet实例 * - * @param - * @param + * @param 泛型 * @return ConcurrentSkipListSet实例 */ public static ConcurrentSkipListSet createConcurrentSkipListSet() { @@ -1116,8 +1133,7 @@ public static ConcurrentSkipListSet createConcurrentSkipListSet() { /** * 创建ConcurrentSkipListSet实例 * - * @param - * @param + * @param 泛型 * @param collection 集合 @see Collection * @return ConcurrentSkipListSet实例 */ @@ -1132,8 +1148,7 @@ public static ConcurrentSkipListSet createConcurrentSkipListSet(Collectio /** * 创建ConcurrentSkipListSet实例 * - * @param - * @param + * @param 泛型 * @param comparator 比较器 @see Comparator * @return ConcurrentSkipListSet实例 */ @@ -1148,8 +1163,7 @@ public static ConcurrentSkipListSet createConcurrentSkipListSet(Comparato /** * 创建ConcurrentSkipListSet实例 * - * @param - * @param + * @param 泛型 * @param set 可排序的散列 @see SortedSet * @return ConcurrentSkipListSet实例 */ @@ -1164,7 +1178,7 @@ public static ConcurrentSkipListSet createConcurrentSkipListSet(SortedSet /** * 创建ConcurrentLinkedQueue实例 * - * @param + * @param 泛型 * @return ConcurrentLinkedQueue实例 */ public static Queue createConcurrentLinkedQueue() { @@ -1174,7 +1188,7 @@ public static Queue createConcurrentLinkedQueue() { /** * 创建ConcurrentLinkedQueue实例 * - * @param + * @param 泛型 * @param collection 集合 @see Collection * @return ConcurrentLinkedQueue实例 */ @@ -1189,7 +1203,7 @@ public static Queue createConcurrentLinkedQueue(Collection c /** * 创建CopyOnWriteArrayList实例 * - * @param + * @param 泛型 * @return CopyOnWriteArrayList实例 */ public static CopyOnWriteArrayList createCopyOnWriteArrayList() { @@ -1199,7 +1213,7 @@ public static CopyOnWriteArrayList createCopyOnWriteArrayList() { /** * 创建CopyOnWriteArrayList实例 * - * @param + * @param 泛型 * @param collection 集合 @see Collection * * @return CopyOnWriteArrayList实例 @@ -1215,7 +1229,7 @@ public static CopyOnWriteArrayList createCopyOnWriteArrayList(Collection< /** * 创建CopyOnWriteArrayList实例 * - * @param + * @param 泛型 * @param toCopyIn 创建一个保存给定数组的副本的数组 * * @return CopyOnWriteArrayList实例 @@ -1231,7 +1245,7 @@ public static CopyOnWriteArrayList createCopyOnWriteArrayList(E[] toCopyI /** * 创建CopyOnWriteArraySet实例 * - * @param + * @param 泛型 * @return CopyOnWriteArraySet实例 */ public static CopyOnWriteArraySet createCopyOnWriteArraySet() { @@ -1241,7 +1255,7 @@ public static CopyOnWriteArraySet createCopyOnWriteArraySet() { /** * 创建CopyOnWriteArraySet实例 * - * @param + * @param 泛型 * @param collection 集合 @see Collection * * @return CopyOnWriteArraySet实例 @@ -1291,7 +1305,7 @@ public static String[] arrayToHeavy(String[] arr){ public static List listToHeavy(List list){ Set set = new HashSet(list); list.clear(); - list.removeAll(set); + list.addAll(set); return list; } diff --git a/blade-kit/src/main/java/blade/kit/DateKit.java b/blade-kit/src/main/java/com/blade/kit/DateKit.java similarity index 82% rename from blade-kit/src/main/java/blade/kit/DateKit.java rename to blade-kit/src/main/java/com/blade/kit/DateKit.java index ae3090d08..32b72c4ad 100644 --- a/blade-kit/src/main/java/blade/kit/DateKit.java +++ b/blade-kit/src/main/java/com/blade/kit/DateKit.java @@ -1,4 +1,19 @@ -package blade.kit; +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; import java.text.DateFormat; import java.text.ParseException; @@ -9,7 +24,10 @@ import java.util.List; /** + * + *

* 日期处理类 + *

* * @author biezhi * @since 1.0 @@ -20,31 +38,37 @@ public class DateKit { * 日 */ public final static int INTERVAL_DAY = 1; + /** * 周 */ public final static int INTERVAL_WEEK = 2; + /** * 月 */ public final static int INTERVAL_MONTH = 3; + /** * 年 */ public final static int INTERVAL_YEAR = 4; + /** * 小时 */ public final static int INTERVAL_HOUR = 5; + /** * 分钟 */ public final static int INTERVAL_MINUTE = 6; + /** * 秒 */ public final static int INTERVAL_SECOND = 7; - + /** * date = 1901-01-01 */ @@ -53,9 +77,8 @@ public class DateKit { /** * 测试是否是当天 * - * @param date - * - 某一日期 - * @return true-今天, false-不是 + * @param date 某一日期 + * @return true 今天, false-不是 */ @SuppressWarnings("deprecation") public static boolean isToday(Date date) { @@ -123,7 +146,6 @@ public static Date dateFormat(String date, String dateFormat) { /** * 使用默认格式 yyyy-MM-dd HH:mm:ss * - * @author Robin Chang * @param date * @return */ @@ -582,7 +604,6 @@ private static String getFormatTime(int time, int format) { * @param birthday * @return int * @exception - * @author 豆皮 * @Date Apr 24, 2008 */ public static int getUserAge(Date birthday) { @@ -604,29 +625,32 @@ public static int getUserAge(Date birthday) { * @param unixTime * 1970年至今的秒数 * @return - * @author 郑卿 */ public static Date getDateByUnixTime(int unixTime) { return new Date(unixTime * 1000L); } + public static long getUnixTimeLong() { + return getUnixTimeByDate(new Date()); + } + + public static int getCurrentUnixTime() { + return getUnixTimeByDate(new Date()); + } + /** * 将Date型时间转换成int型时间(1970年至今的秒数) * * @param unixTime * 1970年至今的秒数 * @return - * @author 郑卿 */ public static int getUnixTimeByDate(Date date) { return (int) (date.getTime() / 1000); } - - public static void main(String[] args) { - Date date1 = dateFormat("1981-01-01 00:00:00"); - Date date2 = dateFormat("1900-12-31 00:00:00"); - System.out.println(birthdayFormat(date1)); - System.out.println(birthdayFormat(date2)); + + public static long getUnixTimeLong(Date date) { + return (date.getTime() / 1000); } public static Date getNextDay(Date date) { @@ -697,7 +721,7 @@ public static Date getWeekAgo(Date date) { public static Date getDatebyTime(Date date, int n) { String str = DateKit.dateFormat(date, "yyyy-MM-dd"); - String[] strs = str.split("-"); + String[] strs = StringKit.split(str, "-"); int month = Integer.parseInt(strs[1]); int monthnow = (month + n) % 12; int year = Integer.parseInt(strs[0]) + (month + n) / 12; @@ -765,10 +789,70 @@ public static long getSpecifyTimeSec(long time, int range) { * @param dateFormat * 时间格式 * @return - * @author sky */ public static String formatDateByUnixTime(long unixTime, String dateFormat) { return dateFormat(new Date(unixTime * 1000), dateFormat); } + + private static List dateFormats = new ArrayList(12) { + private static final long serialVersionUID = 2249396579858199535L; + { + add(new SimpleDateFormat("yyyy-MM-dd")); + add(new SimpleDateFormat("yyyy/MM/dd")); + add(new SimpleDateFormat("yyyy.MM.dd")); + add(new SimpleDateFormat("yyyy-MM-dd HH:24:mm:ss")); + add(new SimpleDateFormat("yyyy/MM/dd HH:24:mm:ss")); + add(new SimpleDateFormat("yyyy.MM.dd HH:24:mm:ss")); + add(new SimpleDateFormat("M/dd/yyyy")); + add(new SimpleDateFormat("dd.M.yyyy")); + add(new SimpleDateFormat("M/dd/yyyy hh:mm:ss a")); + add(new SimpleDateFormat("dd.M.yyyy hh:mm:ss a")); + add(new SimpleDateFormat("dd.MMM.yyyy")); + add(new SimpleDateFormat("dd-MMM-yyyy")); + } + }; + + public static Date convertToDate(String input) { + Date date = null; + if(null == input) { + return null; + } + for (SimpleDateFormat format : dateFormats) { + try { + format.setLenient(false); + date = format.parse(input); + } catch (ParseException e) { + //Shhh.. try other formats + } + if (date != null) { + break; + } + } + return date; + } + + public static Long getTodayTime() { + Calendar today = Calendar.getInstance(); + today.set(Calendar.HOUR_OF_DAY, 0); + today.set(Calendar.MINUTE, 0); + today.set(Calendar.SECOND, 0); + return Long.valueOf(String.valueOf(today.getTimeInMillis()).substring(0, 10)); + } + + public static Long getYesterdayTime() { + Calendar today = Calendar.getInstance(); + today.set(Calendar.HOUR_OF_DAY, -24); + today.set(Calendar.MINUTE, 0); + today.set(Calendar.SECOND, 0); + return Long.valueOf(String.valueOf(today.getTimeInMillis()).substring(0, 10)); + } + public static Long getTomorrowTime() { + Calendar tomorrow = Calendar.getInstance(); + tomorrow.set(Calendar.HOUR_OF_DAY, 24); + tomorrow.set(Calendar.MINUTE, 0); + tomorrow.set(Calendar.SECOND, 0); + return Long.valueOf(String.valueOf(tomorrow.getTimeInMillis()).substring(0, 10)); + } + } \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/Emptys.java b/blade-kit/src/main/java/com/blade/kit/Emptys.java similarity index 81% rename from blade-kit/src/main/java/blade/kit/Emptys.java rename to blade-kit/src/main/java/com/blade/kit/Emptys.java index 7d33e5114..85a207f52 100644 --- a/blade-kit/src/main/java/blade/kit/Emptys.java +++ b/blade-kit/src/main/java/com/blade/kit/Emptys.java @@ -1,4 +1,19 @@ -package blade.kit; +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; import java.io.Serializable; diff --git a/blade-kit/src/main/java/com/blade/kit/EncrypKit.java b/blade-kit/src/main/java/com/blade/kit/EncrypKit.java new file mode 100644 index 000000000..7b27a3cec --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/EncrypKit.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; + +import java.security.MessageDigest; + +/** + * md5 sha加密类 + * + * @author biezhi + * @since 1.0 + */ +public class EncrypKit { + + private static final String ALGORITHM = "MD5"; + + private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', + 'e', 'f' }; + + /** + * encode string + * + * @param algorithm + * @param str + * @return String + */ + public static String encode(String algorithm, String str) { + if (str == null) { + return null; + } + try { + MessageDigest messageDigest = MessageDigest.getInstance(algorithm); + messageDigest.update(str.getBytes()); + return getFormattedText(messageDigest.digest()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + /** + * encode By MD5 + * + * @param str + * @return String + */ + public static String md5(String str) { + if (str == null) { + return null; + } + try { + MessageDigest messageDigest = MessageDigest.getInstance(ALGORITHM); + messageDigest.update(str.getBytes()); + return getFormattedText(messageDigest.digest()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + /** + * Takes the raw bytes from the digest and formats them correct. + * + * @param bytes + * the raw bytes from the digest. + * @return the formatted bytes. + */ + private static String getFormattedText(byte[] bytes) { + int len = bytes.length; + StringBuilder buf = new StringBuilder(len * 2); + // 把密文转换成十六进制的字符串形式 + for (int j = 0; j < len; j++) { + buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]); + buf.append(HEX_DIGITS[bytes[j] & 0x0f]); + } + return buf.toString(); + } + + public static String sha1(final String str){ + return EncrypKit.encode("SHA1", str); + } + +} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/ExceptionKit.java b/blade-kit/src/main/java/com/blade/kit/ExceptionKit.java similarity index 87% rename from blade-kit/src/main/java/blade/kit/ExceptionKit.java rename to blade-kit/src/main/java/com/blade/kit/ExceptionKit.java index ddabf2e0d..08f727cf9 100644 --- a/blade-kit/src/main/java/blade/kit/ExceptionKit.java +++ b/blade-kit/src/main/java/com/blade/kit/ExceptionKit.java @@ -1,4 +1,19 @@ -package blade.kit; +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/blade-kit/src/main/java/blade/kit/FileKit.java b/blade-kit/src/main/java/com/blade/kit/FileKit.java similarity index 91% rename from blade-kit/src/main/java/blade/kit/FileKit.java rename to blade-kit/src/main/java/com/blade/kit/FileKit.java index 4acd4a08d..c6e68c241 100644 --- a/blade-kit/src/main/java/blade/kit/FileKit.java +++ b/blade-kit/src/main/java/com/blade/kit/FileKit.java @@ -1,21 +1,38 @@ -package blade.kit; +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; +import java.nio.channels.FileChannel; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; -import blade.exception.IllegalPathException; +import com.blade.kit.exception.IllegalPathException; /** - * 有关File处理的工具类。 + * 有关文件处理的工具类。 *

* 这个类中的每个方法都可以“安全”地处理 null ,而不会抛出 NullPointerException。 *

@@ -25,14 +42,6 @@ */ public abstract class FileKit { - /* - * ========================================================================== == - */ - /* 常量和singleton。 */ - /* - * ========================================================================== == - */ - private static final char COLON_CHAR = ':'; private static final String UNC_PREFIX = "//"; private static final String SLASH = "/"; @@ -94,7 +103,7 @@ public static File toFile(URL url) { public static boolean exist(String path) { return (path == null) ? false : new File(path).exists(); } - + /** * 判断文件是否存在,如果file为null,则返回false * @@ -1683,5 +1692,98 @@ public static String getClassFilePath(Class clazz) throws IOException { File file = new File(filePath); return file.getAbsolutePath(); } - + + /** + * 复制单个文件 + * @param sourceFile 准备复制的文件源 + * @param destFile 拷贝到新绝对路径带文件名 + * @return + * @throws IOException + */ + @SuppressWarnings("resource") + public static void copy(String sourceFile, String destFile) throws IOException { + + File source = new File(sourceFile); + if (source.exists()) { + FileChannel inputChannel = null; + FileChannel outputChannel = null; + try { + File dest = new File(destFile); + inputChannel = new FileInputStream(source).getChannel(); + outputChannel = new FileOutputStream(dest).getChannel(); + outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); + } finally { + inputChannel.close(); + outputChannel.close(); + } + } + } + + /** + * 复制整个文件夹的内容 + * @param oldPath 准备拷贝的目录 + * @param newPath 指定绝对路径的新目录 + * @return + */ + @SuppressWarnings("resource") + public static void copyDir(String oldPath, String newPath) { + try { + /**如果文件夹不存在 则建立新文件**/ + new File(newPath).mkdirs(); + File a = new File(oldPath); + String[] file = a.list(); + File temp = null; + for (int i = 0; i < file.length; i++) { + if (oldPath.endsWith(File.separator)) { + temp = new File(oldPath + file[i]); + } else { + temp = new File(oldPath + File.separator + file[i]); + } + if (temp.isFile()) { + + FileChannel inputChannel = null; + FileChannel outputChannel = null; + try { + File dest = new File(newPath + "/" + (temp.getName()).toString()); + inputChannel = new FileInputStream(temp).getChannel(); + outputChannel = new FileOutputStream(dest).getChannel(); + outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); + } finally { + inputChannel.close(); + outputChannel.close(); + } + } + /**如果是子文件**/ + if (temp.isDirectory()) { + copyDir(oldPath + "/" + file[i], newPath + "/" + file[i]); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 移动文件 + * @param oldPath + * @param newPath + * @return + * @throws IOException + */ + public static void moveFile(String oldPath, String newPath) throws IOException { + copy(oldPath, newPath); + delete(oldPath); + } + + /** + * 移动目录 + * @param oldPath + * @param newPath + * @return + */ + public static void moveFolder(String oldPath, String newPath) { + copyDir(oldPath, newPath); + deleteDir(oldPath); + } + } \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/HashidKit.java b/blade-kit/src/main/java/com/blade/kit/HashidKit.java new file mode 100644 index 000000000..7846d636a --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/HashidKit.java @@ -0,0 +1,370 @@ +package com.blade.kit; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Hashids designed for Generating short hashes from numbers (like YouTube and + * Bitly), obfuscate database IDs, use them as forgotten password hashes, + * invitation codes, store shard numbers This is implementation of + * http://hashids.org v0.3.3 version. + * + * @author fanweixiao + * @since 0.3.3 + */ +public class HashidKit { + + private static final String DEFAULT_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + + private String salt = ""; + private String alphabet = ""; + private String seps = "cfhistuCFHISTU"; + private int minHashLength = 0; + private String guards; + + public HashidKit() { + this(""); + } + + public HashidKit(String salt) { + this(salt, 0); + } + + public HashidKit(String salt, int minHashLength) { + this(salt, minHashLength, DEFAULT_ALPHABET); + } + + public HashidKit(String salt, int minHashLength, String alphabet) { + this.salt = salt; + if (minHashLength < 0) + this.minHashLength = 0; + else + this.minHashLength = minHashLength; + this.alphabet = alphabet; + + String uniqueAlphabet = ""; + for (int i = 0; i < this.alphabet.length(); i++) { + if (!uniqueAlphabet.contains("" + this.alphabet.charAt(i))) { + uniqueAlphabet += "" + this.alphabet.charAt(i); + } + } + + this.alphabet = uniqueAlphabet; + + int minAlphabetLength = 16; + if (this.alphabet.length() < minAlphabetLength) { + throw new IllegalArgumentException( + "alphabet must contain at least " + minAlphabetLength + + " unique characters"); + } + + if (this.alphabet.contains(" ")) { + throw new IllegalArgumentException( + "alphabet cannot contains spaces"); + } + + // seps should contain only characters present in alphabet; + // alphabet should not contains seps + for (int i = 0; i < this.seps.length(); i++) { + int j = this.alphabet.indexOf(this.seps.charAt(i)); + if (j == -1) { + this.seps = this.seps.substring(0, i) + " " + + this.seps.substring(i + 1); + } else { + this.alphabet = this.alphabet.substring(0, j) + " " + + this.alphabet.substring(j + 1); + } + } + + this.alphabet = this.alphabet.replaceAll("\\s+", ""); + this.seps = this.seps.replaceAll("\\s+", ""); + this.seps = this.consistentShuffle(this.seps, this.salt); + + double sepDiv = 3.5; + if ((this.seps.equals("")) + || ((this.alphabet.length() / this.seps.length()) > sepDiv)) { + int seps_len = (int) Math.ceil(this.alphabet.length() / sepDiv); + + if (seps_len == 1) { + seps_len++; + } + + if (seps_len > this.seps.length()) { + int diff = seps_len - this.seps.length(); + this.seps += this.alphabet.substring(0, diff); + this.alphabet = this.alphabet.substring(diff); + } else { + this.seps = this.seps.substring(0, seps_len); + } + } + + this.alphabet = this.consistentShuffle(this.alphabet, this.salt); + // use double to round up + int guardDiv = 12; + int guardCount = (int) Math.ceil((double) this.alphabet.length() + / guardDiv); + + if (this.alphabet.length() < 3) { + this.guards = this.seps.substring(0, guardCount); + this.seps = this.seps.substring(guardCount); + } else { + this.guards = this.alphabet.substring(0, guardCount); + this.alphabet = this.alphabet.substring(guardCount); + } + } + + /** + * Encrypt numbers to string + * + * @param numbers + * the numbers to encrypt + * @return the encrypt string + */ + public String encode(long... numbers) { + for (long number : numbers) { + if (number > 9007199254740992L) { + throw new IllegalArgumentException( + "number can not be greater than 9007199254740992L"); + } + } + + String retval = ""; + if (numbers.length == 0) { + return retval; + } + + return this._encode(numbers); + } + + /** + * Decrypt string to numbers + * + * @param hash + * the encrypt string + * @return decryped numbers + */ + public long[] decode(String hash) { + long[] ret = {}; + + if (hash.equals("")) + return ret; + + return this._decode(hash, this.alphabet); + } + + /** + * Encrypt hexa to string + * + * @param hexa + * the hexa to encrypt + * @return the encrypt string + */ + public String encodeHex(String hexa) { + if (!hexa.matches("^[0-9a-fA-F]+$")) + return ""; + + List matched = new ArrayList(); + Matcher matcher = Pattern.compile("[\\w\\W]{1,12}").matcher(hexa); + + while (matcher.find()) + matched.add(Long.parseLong("1" + matcher.group(), 16)); + + // conversion + long[] result = new long[matched.size()]; + for (int i = 0; i < matched.size(); i++) + result[i] = matched.get(i); + + return this._encode(result); + } + + /** + * Decrypt string to numbers + * + * @param hash + * the encrypt string + * @return decryped numbers + */ + public String decodeHex(String hash) { + String result = ""; + long[] numbers = this.decode(hash); + + for (long number : numbers) { + result += Long.toHexString(number).substring(1); + } + + return result; + } + + private String _encode(long... numbers) { + int numberHashInt = 0; + for (int i = 0; i < numbers.length; i++) { + numberHashInt += (numbers[i] % (i + 100)); + } + String alphabet = this.alphabet; + char ret = alphabet.toCharArray()[numberHashInt % alphabet.length()]; + // char lottery = ret; + long num; + int sepsIndex, guardIndex; + String buffer, ret_str = ret + ""; + char guard; + + for (int i = 0; i < numbers.length; i++) { + num = numbers[i]; + buffer = ret + this.salt + alphabet; + + alphabet = this.consistentShuffle(alphabet, + buffer.substring(0, alphabet.length())); + String last = this.hash(num, alphabet); + + ret_str += last; + + if (i + 1 < numbers.length) { + num %= ((int) last.toCharArray()[0] + i); + sepsIndex = (int) (num % this.seps.length()); + ret_str += this.seps.toCharArray()[sepsIndex]; + } + } + + if (ret_str.length() < this.minHashLength) { + guardIndex = (numberHashInt + (int) (ret_str.toCharArray()[0])) + % this.guards.length(); + guard = this.guards.toCharArray()[guardIndex]; + + ret_str = guard + ret_str; + + if (ret_str.length() < this.minHashLength) { + guardIndex = (numberHashInt + (int) (ret_str.toCharArray()[2])) + % this.guards.length(); + guard = this.guards.toCharArray()[guardIndex]; + + ret_str += guard; + } + } + + int halfLen = alphabet.length() / 2; + while (ret_str.length() < this.minHashLength) { + alphabet = this.consistentShuffle(alphabet, alphabet); + ret_str = alphabet.substring(halfLen) + ret_str + + alphabet.substring(0, halfLen); + int excess = ret_str.length() - this.minHashLength; + if (excess > 0) { + int start_pos = excess / 2; + ret_str = ret_str.substring(start_pos, start_pos + + this.minHashLength); + } + } + + return ret_str; + } + + private long[] _decode(String hash, String alphabet) { + ArrayList ret = new ArrayList(); + + int i = 0; + String regexp = "[" + this.guards + "]"; + String hashBreakdown = hash.replaceAll(regexp, " "); + String[] hashArray = hashBreakdown.split(" "); + + if (hashArray.length == 3 || hashArray.length == 2) { + i = 1; + } + + hashBreakdown = hashArray[i]; + + char lottery = hashBreakdown.toCharArray()[0]; + + hashBreakdown = hashBreakdown.substring(1); + hashBreakdown = hashBreakdown.replaceAll("[" + this.seps + "]", " "); + hashArray = hashBreakdown.split(" "); + + String subHash, buffer; + for (String aHashArray : hashArray) { + subHash = aHashArray; + buffer = lottery + this.salt + alphabet; + alphabet = this.consistentShuffle(alphabet, + buffer.substring(0, alphabet.length())); + ret.add(this.unhash(subHash, alphabet)); + } + + // transform from List to long[] + long[] arr = new long[ret.size()]; + for (int k = 0; k < arr.length; k++) { + arr[k] = ret.get(k); + } + + if (!this._encode(arr).equals(hash)) { + arr = new long[0]; + } + + return arr; + } + + /* Private methods */ + private String consistentShuffle(String alphabet, String salt) { + if (salt.length() <= 0) + return alphabet; + + char[] arr = salt.toCharArray(); + int asc_val, j; + char tmp; + for (int i = alphabet.length() - 1, v = 0, p = 0; i > 0; i--, v++) { + v %= salt.length(); + asc_val = (int) arr[v]; + p += asc_val; + j = (asc_val + v + p) % i; + + tmp = alphabet.charAt(j); + alphabet = alphabet.substring(0, j) + alphabet.charAt(i) + + alphabet.substring(j + 1); + alphabet = alphabet.substring(0, i) + tmp + + alphabet.substring(i + 1); + } + + return alphabet; + } + + private String hash(long input, String alphabet) { + String hash = ""; + int alphabetLen = alphabet.length(); + char[] arr = alphabet.toCharArray(); + + do { + hash = arr[(int) (input % alphabetLen)] + hash; + input /= alphabetLen; + } while (input > 0); + + return hash; + } + + private Long unhash(String input, String alphabet) { + long number = 0, pos; + char[] input_arr = input.toCharArray(); + + for (int i = 0; i < input.length(); i++) { + pos = alphabet.indexOf(input_arr[i]); + number += pos * Math.pow(alphabet.length(), input.length() - i - 1); + } + + return number; + } + + public static int checkedCast(long value) { + int result = (int) value; + if (result != value) { + // don't use checkArgument here, to avoid boxing + throw new IllegalArgumentException("Out of range: " + value); + } + return result; + } + + /** + * Get version + * + * @return version + */ + public String getVersion() { + return "1.0.0"; + } + +} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/IOKit.java b/blade-kit/src/main/java/com/blade/kit/IOKit.java similarity index 67% rename from blade-kit/src/main/java/blade/kit/IOKit.java rename to blade-kit/src/main/java/com/blade/kit/IOKit.java index aa4451c3d..8bf36d76c 100644 --- a/blade-kit/src/main/java/blade/kit/IOKit.java +++ b/blade-kit/src/main/java/com/blade/kit/IOKit.java @@ -1,243 +1,273 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.kit; - -import java.io.Closeable; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.Reader; -import java.io.StringWriter; -import java.io.Writer; -import java.net.HttpURLConnection; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URLConnection; -import java.nio.channels.Selector; -import java.nio.charset.Charset; -import java.util.zip.ZipFile; - -import blade.kit.io.FastByteArrayOutputStream; - -/** - * IO工具类 - * - * @author biezhi - * @since 1.0 - */ -public final class IOKit { - - public static final String LINE_SEPARATOR; - - static { - // avoid security issues - StringWriter buf = new StringWriter(4); // NOSONAR - PrintWriter out = new PrintWriter(buf); - out.println(); - LINE_SEPARATOR = buf.toString(); - } - - /** - * The default buffer size to use. - */ - public static final int DEFAULT_BUFFER_SIZE = 1024 * 4; - - private IOKit() { - } - - public static String toString(InputStream input) throws IOException { - StringWriter sw = new StringWriter(); - copy(input, sw); - return sw.toString(); - } - - public static byte[] toByteArray(InputStream input) throws IOException { - @SuppressWarnings("resource") - FastByteArrayOutputStream os = new FastByteArrayOutputStream(); - byte[] buf = new byte[1024]; - for (int n = input.read(buf); n != -1; n = input.read(buf)) { - os.write(buf, 0, n); - } - return os.toByteArray(); - } - - - - public static long copyLarge(final InputStream input, final OutputStream output) - throws IOException { - byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; - long count = 0; - int n = 0; - while (-1 != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - } - return count; - } - - public static void copy(InputStream input, Writer output) - throws IOException { - InputStreamReader in = new InputStreamReader(input); // NOSONAR - copy(in, output); - } - - public static long copyLarge(Reader input, Writer output) throws IOException { - char[] buffer = new char[DEFAULT_BUFFER_SIZE]; - long count = 0; - int n = 0; - while (-1 != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - } - return count; - } - - public static void write(byte[] data, File file) { - OutputStream os = null; - try { - os = new FileOutputStream(file); - os.write(data); - } catch (IOException e) { - throw new IllegalStateException(e); - } finally { - closeQuietly(os); - } - } - - public static void write(char[] data, File file, String charsetName) { - write(data, file, Charset.forName(charsetName)); - } - - public static void write(char[] data, File file, Charset charset) { - OutputStream os = null; - try { - os = new FileOutputStream(file); - os.write(new String(data).getBytes(charset)); - } catch (IOException e) { - throw new IllegalStateException(e); - } finally { - closeQuietly(os); - } - } - - public static void write(String data, File file, String charsetName) { - write(data, file, Charset.forName(charsetName)); - } - - public static void write(String data, File file, Charset charset) { - OutputStream os = null; - try { - os = new FileOutputStream(file); - os.write(data.getBytes(charset)); - } catch (IOException e) { - throw new IllegalStateException(e); - } finally { - closeQuietly(os); - } - } - - public static long copy(InputStream input, OutputStream output) - throws IOException { - byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; - long count = 0L; - int n = 0; - while (-1 != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - } - return count; - } - - public static long copy(InputStream input, Writer output, String charsetName) - throws IOException { - return copy(new InputStreamReader(input, Charset.forName(charsetName)), - output); - } - - public static long copy(InputStream input, Writer output, Charset charset) - throws IOException { - return copy(new InputStreamReader(input, charset), output); - } - - public static long copy(Reader input, Writer output) throws IOException { - char[] buffer = new char[DEFAULT_BUFFER_SIZE]; - long count = 0L; - int n = 0; - while (-1 != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - } - return count; - } - - public static void closeQuietly(ZipFile obj) { - try { - if (obj != null) { - obj.close(); - } - } catch (IOException e) { - } - } - - public static void closeQuietly(Socket socket) { - try { - if (socket != null) { - socket.close(); - } - } catch (IOException e) { - } - } - - public static void closeQuietly(ServerSocket socket) { - try { - if (socket != null) { - socket.close(); - } - } catch (IOException e) { - } - } - - public static void closeQuietly(Selector selector) { - try { - if (selector != null) { - selector.close(); - } - } catch (IOException e) { - } - } - - public static void closeQuietly(URLConnection conn) { - if (conn != null) { - if (conn instanceof HttpURLConnection) { - ((HttpURLConnection) conn).disconnect(); - } - } - } - - public static void closeQuietly(Closeable closeable) { - if (null != closeable) { - try { - closeable.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } -} +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.net.HttpURLConnection; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URLConnection; +import java.nio.channels.Selector; +import java.nio.charset.Charset; +import java.util.zip.ZipFile; + +import com.blade.kit.io.FastByteArrayOutputStream; + +/** + * IO工具类 + * + * @author biezhi + * @since 1.0 + */ +public final class IOKit { + + public static final String LINE_SEPARATOR; + + private static final int EOF = -1; + + static { + // avoid security issues + StringWriter buf = new StringWriter(4); // NOSONAR + PrintWriter out = new PrintWriter(buf); + out.println(); + LINE_SEPARATOR = buf.toString(); + } + + /** + * The default buffer size to use. + */ + public static final int DEFAULT_BUFFER_SIZE = 0x1000; + + private IOKit() { + } + + public static String toString(InputStream input) throws IOException { + StringWriter sw = new StringWriter(); + copy(input, sw); + return sw.toString(); + } + + public static String toString(File file) throws IOException { + try { + BufferedReader reader = new BufferedReader(new FileReader(file)); + StringBuilder data = readFromBufferedReader(reader); + reader.close(); + return new String(data.toString().getBytes(), "utf-8"); + } catch (IOException ex) { + throw new RuntimeException("File " + file + " not found."); + } + } + + private static StringBuilder readFromBufferedReader(BufferedReader reader) throws IOException { + StringBuilder builder = new StringBuilder(); + char[] buffer = new char[DEFAULT_BUFFER_SIZE]; + int numRead = 0; + while((numRead = reader.read(buffer)) != EOF) { + builder.append(String.valueOf(buffer, 0, numRead)); + buffer = new char[DEFAULT_BUFFER_SIZE]; + } + return builder; + } + + public static byte[] toByteArray(InputStream input) throws IOException { + @SuppressWarnings("resource") + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + byte[] buf = new byte[1024]; + for (int n = input.read(buf); n != EOF; n = input.read(buf)) { + os.write(buf, 0, n); + } + return os.toByteArray(); + } + + + + public static long copyLarge(final InputStream input, final OutputStream output) + throws IOException { + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + long count = 0L; + int n = 0; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + public static void copy(InputStream input, Writer output) + throws IOException { + InputStreamReader in = new InputStreamReader(input); // NOSONAR + copy(in, output); + } + + public static long copyLarge(Reader input, Writer output) throws IOException { + char[] buffer = new char[DEFAULT_BUFFER_SIZE]; + long count = 0L; + int n = 0; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + public static void write(byte[] data, File file) { + OutputStream os = null; + try { + os = new FileOutputStream(file); + os.write(data); + } catch (IOException e) { + throw new IllegalStateException(e); + } finally { + closeQuietly(os); + } + } + + public static void write(char[] data, File file, String charsetName) { + write(data, file, Charset.forName(charsetName)); + } + + public static void write(char[] data, File file, Charset charset) { + OutputStream os = null; + try { + os = new FileOutputStream(file); + os.write(new String(data).getBytes(charset)); + } catch (IOException e) { + throw new IllegalStateException(e); + } finally { + closeQuietly(os); + } + } + + public static void write(String data, File file, String charsetName) { + write(data, file, Charset.forName(charsetName)); + } + + public static void write(String data, File file, Charset charset) { + OutputStream os = null; + try { + os = new FileOutputStream(file); + os.write(data.getBytes(charset)); + } catch (IOException e) { + throw new IllegalStateException(e); + } finally { + closeQuietly(os); + } + } + + public static int copy(InputStream input, OutputStream output) + throws IOException { + long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + public static int copy(InputStream input, Writer output, String charsetName) + throws IOException { + return copy(new InputStreamReader(input, Charset.forName(charsetName)), + output); + } + + public static int copy(InputStream input, Writer output, Charset charset) + throws IOException { + return copy(new InputStreamReader(input, charset), output); + } + + public static int copy(Reader input, Writer output) throws IOException { + long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + public static void closeQuietly(ZipFile obj) { + try { + if (obj != null) { + obj.close(); + } + } catch (IOException e) { + } + } + + public static void closeQuietly(Socket socket) { + try { + if (socket != null) { + socket.close(); + } + } catch (IOException e) { + } + } + + public static void closeQuietly(ServerSocket socket) { + try { + if (socket != null) { + socket.close(); + } + } catch (IOException e) { + } + } + + public static void closeQuietly(Selector selector) { + try { + if (selector != null) { + selector.close(); + } + } catch (IOException e) { + } + } + + public static void closeQuietly(URLConnection conn) { + if (conn != null) { + if (conn instanceof HttpURLConnection) { + ((HttpURLConnection) conn).disconnect(); + } + } + } + + public static void closeQuietly(Closeable closeable) { + if (null != closeable) { + try { + closeable.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static String toString(Reader input) throws IOException { + StringBuilder output = new StringBuilder(); + char[] buffer = new char[DEFAULT_BUFFER_SIZE]; + int n; + while (EOF != (n = input.read(buffer))) { + output.append(buffer, 0, n); + } + return output.toString(); + } +} diff --git a/blade-core/src/main/java/blade/kit/IpKit.java b/blade-kit/src/main/java/com/blade/kit/IPKit.java similarity index 95% rename from blade-core/src/main/java/blade/kit/IpKit.java rename to blade-kit/src/main/java/com/blade/kit/IPKit.java index 7554c2f01..7d81ddad0 100644 --- a/blade-core/src/main/java/blade/kit/IpKit.java +++ b/blade-kit/src/main/java/com/blade/kit/IPKit.java @@ -1,85 +1,85 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.kit; - -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.Enumeration; - -import javax.servlet.http.HttpServletRequest; - -/** - * IP工具类 - * - * @author biezhi - * @since 1.0 - */ -public class IpKit { - - /** - * @param request - * IP - * @return IP Address - */ - public static String getIpAddrByRequest(HttpServletRequest request) { - String ip = request.getHeader("x-forwarded-for"); - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("WL-Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getRemoteAddr(); - } - return ip; - } - - /** - * @return 本机IP - * @throws SocketException - */ - public static String getRealIp() throws SocketException { - String localip = null;// 本地IP,如果没有配置外网IP则返回它 - String netip = null;// 外网IP - - Enumeration netInterfaces = NetworkInterface.getNetworkInterfaces(); - InetAddress ip = null; - boolean finded = false;// 是否找到外网IP - while (netInterfaces.hasMoreElements() && !finded) { - NetworkInterface ni = netInterfaces.nextElement(); - Enumeration address = ni.getInetAddresses(); - while (address.hasMoreElements()) { - ip = address.nextElement(); - if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 外网IP - netip = ip.getHostAddress(); - finded = true; - break; - } else if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 内网IP - localip = ip.getHostAddress(); - } - } - } - - if (netip != null && !"".equals(netip)) { - return netip; - } else { - return localip; - } - } - +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + +import javax.servlet.http.HttpServletRequest; + +/** + * IP工具类 + * + * @author biezhi + * @since 1.0 + */ +public class IPKit { + + /** + * @param request + * IP + * @return IP Address + */ + public static String getIpAddrByRequest(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + return ip; + } + + /** + * @return 本机IP + * @throws SocketException + */ + public static String getRealIp() throws SocketException { + String localip = null;// 本地IP,如果没有配置外网IP则返回它 + String netip = null;// 外网IP + + Enumeration netInterfaces = NetworkInterface.getNetworkInterfaces(); + InetAddress ip = null; + boolean finded = false;// 是否找到外网IP + while (netInterfaces.hasMoreElements() && !finded) { + NetworkInterface ni = netInterfaces.nextElement(); + Enumeration address = ni.getInetAddresses(); + while (address.hasMoreElements()) { + ip = address.nextElement(); + if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 外网IP + netip = ip.getHostAddress(); + finded = true; + break; + } else if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 内网IP + localip = ip.getHostAddress(); + } + } + } + + if (netip != null && !"".equals(netip)) { + return netip; + } else { + return localip; + } + } + } \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/MimeParse.java b/blade-kit/src/main/java/com/blade/kit/MimeParse.java similarity index 94% rename from blade-kit/src/main/java/blade/kit/MimeParse.java rename to blade-kit/src/main/java/com/blade/kit/MimeParse.java index 82c1b70c6..3686964e8 100644 --- a/blade-kit/src/main/java/blade/kit/MimeParse.java +++ b/blade-kit/src/main/java/com/blade/kit/MimeParse.java @@ -1,227 +1,228 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.kit; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -/** - * MIME-Type解析类 - * - * @author biezhi - * @since 1.0 - */ -public class MimeParse { - - /** - * Constant for no mime type - */ - public static final String NO_MIME_TYPE = ""; - - /** - * Parse results container - */ - private static class ParseResults { - String type; - - String subType; - - // !a dictionary of all the parameters for the media range - Map params; - - @Override - public String toString() { - StringBuffer s = new StringBuffer("('" + type + "', '" + subType + "', {"); - for (String k : params.keySet()) { - s.append("'" + k + "':'" + params.get(k) + "',"); - } - return s.append("})").toString(); - } - } - - /** - * Carves up a mime-type and returns a ParseResults object - * For example, the media range 'application/xhtml;q=0.5' would get parsed - * into: - * ('application', 'xhtml', {'q', '0.5'}) - */ - private static ParseResults parseMimeType(String mimeType) { - String[] parts = mimeType.split(";"); - ParseResults results = new ParseResults(); - results.params = new HashMap(); - - for (int i = 1; i < parts.length; ++i) { - String p = parts[i]; - String[] subParts = p.split("="); - if (subParts.length == 2) { - results.params.put(subParts[0].trim(), subParts[1].trim()); - } - } - String fullType = parts[0].trim(); - - // Java URLConnection class sends an Accept header that includes a - // single "*" - Turn it into a legal wildcard. - if (fullType.equals("*")) { - fullType = "*/*"; - } - - int slashIndex = fullType.indexOf('/'); - if (slashIndex != -1) { - results.type = fullType.substring(0, slashIndex); - results.subType = fullType.substring(slashIndex + 1); - } else { - //If the type is invalid, attempt to turn into a wildcard - results.type = fullType; - results.subType = "*"; - } - - return results; - } - - /** - * Carves up a media range and returns a ParseResults. - * For example, the media range 'application/*;q=0.5' would get parsed into: - * ('application', '*', {'q', '0.5'}) - * In addition this function also guarantees that there is a value for 'q' - * in the params dictionary, filling it in with a proper default if - * necessary. - * - * @param range - */ - private static ParseResults parseMediaRange(String range) { - ParseResults results = parseMimeType(range); - String q = results.params.get("q"); - float f = toFloat(q, 1); - if (isBlank(q) || f < 0 || f > 1) { - results.params.put("q", "1"); - } - return results; - } - - /** - * Structure for holding a fitness/quality combo - */ - private static class FitnessAndQuality implements Comparable { - int fitness; - - float quality; - - String mimeType; // optionally used - - private FitnessAndQuality(int fitness, float quality) { - this.fitness = fitness; - this.quality = quality; - } - - public int compareTo(FitnessAndQuality o) { - if (fitness == o.fitness) { - if (quality == o.quality) { - return 0; - } else { - return quality < o.quality ? -1 : 1; - } - } else { - return fitness < o.fitness ? -1 : 1; - } - } - } - - /** - * Find the best match for a given mimeType against a list of media_ranges - * that have already been parsed by MimeParse.parseMediaRange(). Returns a - * tuple of the fitness value and the value of the 'q' quality parameter of - * the best match, or (-1, 0) if no match was found. Just as for - * quality_parsed(), 'parsed_ranges' must be a list of parsed media ranges. - * - * @param mimeType - * @param parsedRanges - */ - private static FitnessAndQuality fitnessAndQualityParsed(String mimeType, Collection parsedRanges) { - int bestFitness = -1; - float bestFitQ = 0; - ParseResults target = parseMediaRange(mimeType); - - for (ParseResults range : parsedRanges) { - if ((target.type.equals(range.type) || range.type.equals("*") || target.type.equals("*")) - && (target.subType.equals(range.subType) || range.subType.equals("*") - || target.subType.equals("*"))) { - for (String k : target.params.keySet()) { - int paramMatches = 0; - if (!k.equals("q") && range.params.containsKey(k) - && target.params.get(k).equals(range.params.get(k))) { - paramMatches++; - } - int fitness = (range.type.equals(target.type)) ? 100 : 0; - fitness += (range.subType.equals(target.subType)) ? 10 : 0; - fitness += paramMatches; - if (fitness > bestFitness) { - bestFitness = fitness; - bestFitQ = toFloat(range.params.get("q"), 0); - } - } - } - } - return new FitnessAndQuality(bestFitness, bestFitQ); - } - - /** - * Finds best match - * - * @param supported the supported types - * @param header the header - * @return the best match - */ - public static String bestMatch(Collection supported, String header) { - List parseResults = new LinkedList(); - List weightedMatches = new LinkedList(); - for (String r : header.split(",")) { - parseResults.add(parseMediaRange(r)); - } - - for (String s : supported) { - FitnessAndQuality fitnessAndQuality = fitnessAndQualityParsed(s, parseResults); - fitnessAndQuality.mimeType = s; - weightedMatches.add(fitnessAndQuality); - } - Collections.sort(weightedMatches); - - FitnessAndQuality lastOne = weightedMatches.get(weightedMatches.size() - 1); - return Float.compare(lastOne.quality, 0) != 0 ? lastOne.mimeType : NO_MIME_TYPE; - } - - private static boolean isBlank(String s) { - return s == null || "".equals(s.trim()); - } - - private static float toFloat(final String str, final float defaultValue) { - if (str == null) { - return defaultValue; - } - try { - return Float.parseFloat(str); - } catch (final NumberFormatException nfe) { - return defaultValue; - } - } - - private MimeParse() { - } - -} +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * MIME-Type解析类 + * + * @author biezhi + * @since 1.0 + */ +public class MimeParse { + + /** + * Constant for no mime type + */ + public static final String NO_MIME_TYPE = ""; + + /** + * Parse results container + */ + private static class ParseResults { + String type; + + String subType; + + // !a dictionary of all the parameters for the media range + Map params; + + @Override + public String toString() { + StringBuffer s = new StringBuffer("('" + type + "', '" + subType + "', {"); + for (String k : params.keySet()) { + s.append("'" + k + "':'" + params.get(k) + "',"); + } + return s.append("})").toString(); + } + } + + /** + * Carves up a mime-type and returns a ParseResults object + * For example, the media range 'application/xhtml;q=0.5' would get parsed + * into: + * ('application', 'xhtml', {'q', '0.5'}) + */ + private static ParseResults parseMimeType(String mimeType) { + String[] parts = StringKit.split(mimeType, ";"); + ParseResults results = new ParseResults(); + results.params = new HashMap(); + + for (int i = 1; i < parts.length; ++i) { + String p = parts[i]; + String[] subParts = StringKit.split(p, "="); + if (subParts.length == 2) { + results.params.put(subParts[0].trim(), subParts[1].trim()); + } + } + String fullType = parts[0].trim(); + + // Java URLConnection class sends an Accept header that includes a + // single "*" - Turn it into a legal wildcard. + if (fullType.equals("*")) { + fullType = "*/*"; + } + + int slashIndex = fullType.indexOf('/'); + if (slashIndex != -1) { + results.type = fullType.substring(0, slashIndex); + results.subType = fullType.substring(slashIndex + 1); + } else { + //If the type is invalid, attempt to turn into a wildcard + results.type = fullType; + results.subType = "*"; + } + + return results; + } + + /** + * Carves up a media range and returns a ParseResults. + * For example, the media range 'application/*;q=0.5' would get parsed into: + * ('application', '*', {'q', '0.5'}) + * In addition this function also guarantees that there is a value for 'q' + * in the params dictionary, filling it in with a proper default if + * necessary. + * + * @param range + */ + private static ParseResults parseMediaRange(String range) { + ParseResults results = parseMimeType(range); + String q = results.params.get("q"); + float f = toFloat(q, 1); + if (isBlank(q) || f < 0 || f > 1) { + results.params.put("q", "1"); + } + return results; + } + + /** + * Structure for holding a fitness/quality combo + */ + private static class FitnessAndQuality implements Comparable { + int fitness; + + float quality; + + String mimeType; // optionally used + + private FitnessAndQuality(int fitness, float quality) { + this.fitness = fitness; + this.quality = quality; + } + + public int compareTo(FitnessAndQuality o) { + if (fitness == o.fitness) { + if (quality == o.quality) { + return 0; + } else { + return quality < o.quality ? -1 : 1; + } + } else { + return fitness < o.fitness ? -1 : 1; + } + } + } + + /** + * Find the best match for a given mimeType against a list of media_ranges + * that have already been parsed by MimeParse.parseMediaRange(). Returns a + * tuple of the fitness value and the value of the 'q' quality parameter of + * the best match, or (-1, 0) if no match was found. Just as for + * quality_parsed(), 'parsed_ranges' must be a list of parsed media ranges. + * + * @param mimeType + * @param parsedRanges + */ + private static FitnessAndQuality fitnessAndQualityParsed(String mimeType, Collection parsedRanges) { + int bestFitness = -1; + float bestFitQ = 0; + ParseResults target = parseMediaRange(mimeType); + + for (ParseResults range : parsedRanges) { + if ((target.type.equals(range.type) || range.type.equals("*") || target.type.equals("*")) + && (target.subType.equals(range.subType) || range.subType.equals("*") + || target.subType.equals("*"))) { + for (String k : target.params.keySet()) { + int paramMatches = 0; + if (!k.equals("q") && range.params.containsKey(k) + && target.params.get(k).equals(range.params.get(k))) { + paramMatches++; + } + int fitness = (range.type.equals(target.type)) ? 100 : 0; + fitness += (range.subType.equals(target.subType)) ? 10 : 0; + fitness += paramMatches; + if (fitness > bestFitness) { + bestFitness = fitness; + bestFitQ = toFloat(range.params.get("q"), 0); + } + } + } + } + return new FitnessAndQuality(bestFitness, bestFitQ); + } + + /** + * Finds best match + * + * @param supported the supported types + * @param header the header + * @return the best match + */ + public static String bestMatch(Collection supported, String header) { + List parseResults = new LinkedList(); + List weightedMatches = new LinkedList(); + String[] headers = StringKit.split(header, ","); + for (String r : headers) { + parseResults.add(parseMediaRange(r)); + } + + for (String s : supported) { + FitnessAndQuality fitnessAndQuality = fitnessAndQualityParsed(s, parseResults); + fitnessAndQuality.mimeType = s; + weightedMatches.add(fitnessAndQuality); + } + Collections.sort(weightedMatches); + + FitnessAndQuality lastOne = weightedMatches.get(weightedMatches.size() - 1); + return Float.compare(lastOne.quality, 0) != 0 ? lastOne.mimeType : NO_MIME_TYPE; + } + + private static boolean isBlank(String s) { + return s == null || "".equals(s.trim()); + } + + private static float toFloat(final String str, final float defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Float.parseFloat(str); + } catch (final NumberFormatException nfe) { + return defaultValue; + } + } + + private MimeParse() { + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/ObjectKit.java b/blade-kit/src/main/java/com/blade/kit/ObjectKit.java new file mode 100644 index 000000000..f2051aec2 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/ObjectKit.java @@ -0,0 +1,74 @@ +package com.blade.kit; + +import java.lang.reflect.Field; +import java.util.Date; +import java.util.Map; + +import com.blade.kit.exception.ReflectException; +import com.blade.kit.reflect.ReflectKit; + +public final class ObjectKit { + + private ObjectKit() { + } + + @SuppressWarnings("unchecked") + public static T model(String slug, Class clazz, Map params){ + try { + Object obj = ReflectKit.newInstance(clazz); + Field[] fields = clazz.getDeclaredFields(); + if(null == fields || fields.length == 0){ + return null; + } + + for(Field field : fields){ + field.setAccessible(true); + if(field.getName().equals("serialVersionUID")){ + continue; + } + String fieldName = slug + "." + field.getName(); + String fieldValue = params.get(fieldName); + + if(null != fieldValue){ + Object value = parse(field.getType(), fieldValue); + field.set(obj, value); + } + + } + return (T) obj; + } catch (NumberFormatException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + throw new ReflectException(e); + } catch (IllegalAccessException e) { + throw new ReflectException(e); + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + return null; + } + + public static Object parse(Class type, String value){ + if (type == Integer.class) { + return Integer.parseInt(value); + } else if (type == String.class) { + return value; + } else if (type == Date.class) { + return DateKit.convertToDate(value); + } else if (type == Double.class) { + return Double.parseDouble(value); + } else if (type == Float.class) { + return Float.parseFloat(value); + } else if (type == Long.class) { + return Long.parseLong(value); + } else if (type == Boolean.class) { + return Boolean.parseBoolean(value); + } else if (type == Short.class) { + return Short.parseShort(value); + } + return value; + } + +} diff --git a/blade-kit/src/main/java/blade/kit/PatternKit.java b/blade-kit/src/main/java/com/blade/kit/PatternKit.java similarity index 96% rename from blade-kit/src/main/java/blade/kit/PatternKit.java rename to blade-kit/src/main/java/com/blade/kit/PatternKit.java index 16c002391..20cf42f50 100644 --- a/blade-kit/src/main/java/blade/kit/PatternKit.java +++ b/blade-kit/src/main/java/com/blade/kit/PatternKit.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.kit; +package com.blade.kit; import java.util.regex.Pattern; @@ -64,7 +64,7 @@ public static boolean isImage(String suffix) { * @return 验证成功返回true,验证失败返回false */ public static boolean isMobile(String mobile) { - String regex = "(\\+\\d+)?1[3458]\\d{9}$"; + String regex = "(\\+\\d+)?1[34578]\\d{9}$"; return Pattern.matches(regex,mobile); } diff --git a/blade-kit/src/main/java/blade/kit/SerializeKit.java b/blade-kit/src/main/java/com/blade/kit/SerializeKit.java similarity index 95% rename from blade-kit/src/main/java/blade/kit/SerializeKit.java rename to blade-kit/src/main/java/com/blade/kit/SerializeKit.java index 140547c00..705fa5021 100644 --- a/blade-kit/src/main/java/blade/kit/SerializeKit.java +++ b/blade-kit/src/main/java/com/blade/kit/SerializeKit.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.kit; +package com.blade.kit; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/blade-kit/src/main/java/blade/kit/ShellKit.java b/blade-kit/src/main/java/com/blade/kit/ShellKit.java similarity index 85% rename from blade-kit/src/main/java/blade/kit/ShellKit.java rename to blade-kit/src/main/java/com/blade/kit/ShellKit.java index ec7db4db9..8ebc179ee 100644 --- a/blade-kit/src/main/java/blade/kit/ShellKit.java +++ b/blade-kit/src/main/java/com/blade/kit/ShellKit.java @@ -1,4 +1,20 @@ -package blade.kit; +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; + import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; diff --git a/blade-kit/src/main/java/blade/kit/StreamKit.java b/blade-kit/src/main/java/com/blade/kit/StreamKit.java similarity index 95% rename from blade-kit/src/main/java/blade/kit/StreamKit.java rename to blade-kit/src/main/java/com/blade/kit/StreamKit.java index d69e09cd3..6f74f42bd 100644 --- a/blade-kit/src/main/java/blade/kit/StreamKit.java +++ b/blade-kit/src/main/java/com/blade/kit/StreamKit.java @@ -1,551 +1,566 @@ -package blade.kit; - -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.StringWriter; -import java.io.Writer; - -import blade.kit.io.ByteArray; -import blade.kit.io.ByteArrayOutputStream; -import blade.kit.io.FastByteArrayOutputStream; - -/** - * 基于流的工具类 - * - * @author biezhi - * @since 1.0 - */ -public abstract class StreamKit { - - /** - * 从输入流读取内容, 写入到输出流中. 此方法使用大小为8192字节的默认的缓冲区. - * - * @param in 输入流 - * @param out 输出流 - * - * @throws IOException 输入输出异常 - */ - public static void io(InputStream in, OutputStream out) throws IOException { - io(in, out, -1); - } - - /** - * 从输入流读取内容, 写入到输出流中. 使用指定大小的缓冲区. - * - * @param in 输入流 - * @param out 输出流 - * @param bufferSize 缓冲区大小(字节数) - * - * @throws IOException 输入输出异常 - */ - public static void io(InputStream in, OutputStream out, int bufferSize) throws IOException { - if (bufferSize == -1) { - bufferSize = IOKit.DEFAULT_BUFFER_SIZE; - } - - byte[] buffer = new byte[bufferSize]; - int amount; - - while ((amount = in.read(buffer)) >= 0) { - out.write(buffer, 0, amount); - } - - out.flush(); - } - - /** - * 从输入流读取内容, 写入到输出流中. 此方法使用大小为8192字符的默认的缓冲区. - * - * @param in 输入流 - * @param out 输出流 - * - * @throws IOException 输入输出异常 - */ - public static void io(Reader in, Writer out) throws IOException { - io(in, out, -1); - } - - /** - * 从输入流读取内容, 写入到输出流中. 使用指定大小的缓冲区. - * - * @param in 输入流 - * @param out 输出流 - * @param bufferSize 缓冲区大小(字符数) - * - * @throws IOException 输入输出异常 - */ - public static void io(Reader in, Writer out, int bufferSize) throws IOException { - if (bufferSize == -1) { - bufferSize = IOKit.DEFAULT_BUFFER_SIZE >> 1; - } - - char[] buffer = new char[bufferSize]; - int amount; - - while ((amount = in.read(buffer)) >= 0) { - out.write(buffer, 0, amount); - } - - out.flush(); - } - - /** - * 从输入流读取内容, 写入到输出流中. 此方法使用大小为8192字节的默认的缓冲区. - * - * @param in 输入流 - * @param out 输出流 - * @param closeIn 是否关闭输入流 - * @param closeOut 是否关闭输出流 - * @throws IOException 输入输出异常 - */ - public static void io(InputStream in, OutputStream out, boolean closeIn, boolean closeOut) throws IOException { - - try { - io(in, out); - } finally { - if (closeIn) { - close(in); - } - - if (closeOut) { - close(out); - } - } - } - - /** - * 从输入流读取内容, 写入到输出流中. 此方法使用大小为8192字节的默认的缓冲区. - * - * @param in 输入流 - * @param out 输出流 - * @param closeIn 是否关闭输入流 - * @param closeOut 是否关闭输出流 - * @throws IOException 输入输出异常 - */ - public static void io(Reader in, Writer out, boolean closeIn, boolean closeOut) throws IOException { - try { - io(in, out); - } finally { - if (closeIn) { - close(in); - } - - if (closeOut) { - close(out); - } - } - } - - /** 从输入流读取内容, 写入到目标文件 */ - public static void io(InputStream in, File dest) throws IOException { - OutputStream out = new FileOutputStream(dest); - io(in, out); - } - - /** 从输入流读取内容, 写入到目标文件 */ - public static void io(InputStream in, File dest, boolean closeIn, boolean closeOut) throws IOException { - OutputStream out = new FileOutputStream(dest); - try { - io(in, out); - } finally { - if (closeIn) { - close(in); - } - - if (closeOut) { - close(out); - } - } - } - - /** 从输入流读取内容, 写入到目标文件 */ - public static void io(InputStream in, String dest) throws IOException { - OutputStream out = new FileOutputStream(dest); - io(in, out); - } - - /** 从输入流读取内容, 写入到目标文件 */ - public static void io(InputStream in, String dest, boolean closeIn, boolean closeOut) throws IOException { - OutputStream out = new FileOutputStream(dest); - try { - io(in, out); - } finally { - if (closeIn) { - close(in); - } - - if (closeOut) { - close(out); - } - } - } - - /** 从输入流读取内容, 写入到目标文件 */ - public static void io(Reader in, File dest) throws IOException { - Writer out = new FileWriter(dest); - io(in, out); - } - - /** 从输入流读取内容, 写入到目标文件 */ - public static void io(Reader in, File dest, boolean closeIn, boolean closeOut) throws IOException { - Writer out = new FileWriter(dest); - try { - io(in, out); - } finally { - if (closeIn) { - close(in); - } - - if (closeOut) { - close(out); - } - } - } - - /** 从输入流读取内容, 写入到目标文件 */ - public static void io(Reader in, String dest) throws IOException { - Writer out = new FileWriter(dest); - io(in, out); - } - - /** 从输入流读取内容, 写入到目标文件 */ - public static void io(Reader in, String dest, boolean closeIn, boolean closeOut) throws IOException { - Writer out = new FileWriter(dest); - try { - io(in, out); - } finally { - if (closeIn) { - close(in); - } - - if (closeOut) { - close(out); - } - } - } - - /** - * 取得同步化的输出流. - * - * @param out 要包裹的输出流 - * - * @return 线程安全的同步化输出流 - */ - public static OutputStream synchronizedOutputStream(OutputStream out) { - return new SynchronizedOutputStream(out); - } - - /** - * 取得同步化的输出流. - * - * @param out 要包裹的输出流 - * @param lock 同步锁 - * - * @return 线程安全的同步化输出流 - */ - public static OutputStream synchronizedOutputStream(OutputStream out, Object lock) { - return new SynchronizedOutputStream(out, lock); - } - - /** - * 将指定输入流的所有文本全部读出到一个字符串中. - * - * @param in 要读取的输入流 - * - * @return 从输入流中取得的文本 - * - * @throws IOException 输入输出异常 - */ - public static String readText(InputStream in) throws IOException { - return readText(in, null, -1); - } - - /** - * 将指定输入流的所有文本全部读出到一个字符串中. - * - * @param in 要读取的输入流 - * @param encoding 文本编码方式 - * - * @return 从输入流中取得的文本 - * - * @throws IOException 输入输出异常 - */ - public static String readText(InputStream in, String encoding) throws IOException { - return readText(in, encoding, -1); - } - - /** - * 将指定输入流的所有文本全部读出到一个字符串中. - * - * @param in 要读取的输入流 - * @param charset 文本编码方式 - * @param bufferSize 缓冲区大小(字符数) - * - * @return 从输入流中取得的文本 - * - * @throws IOException 输入输出异常 - */ - public static String readText(InputStream in, String charset, int bufferSize) throws IOException { - Reader reader = (charset == null) ? new InputStreamReader(in) : new InputStreamReader(in, charset); - - return readText(reader, bufferSize); - } - - /** - * 将指定输入流的所有文本全部读出到一个字符串中. - * - * @param in 要读取的输入流 - * @param charset 文本编码方式 - * @param closeIn 是否关闭输入流 - * @return 从输入流中取得的文本 - * @throws IOException 输入输出异常 - */ - public static String readText(InputStream in, String charset, boolean closeIn) throws IOException { - Reader reader = charset == null ? new InputStreamReader(in) : new InputStreamReader(in, charset); - - return readText(reader, closeIn); - } - - /** - * 将指定输入流的所有文本全部读出到一个字符串中. - * - * @param in 要读取的输入流 - * @param closeIn 是否关闭输入流 - * @return 从输入流中取得的文本 - * @throws IOException 输入输出异常 - */ - public static String readText(Reader in, boolean closeIn) throws IOException { - StringWriter out = new StringWriter(); - - io(in, out, closeIn, true); - - return out.toString(); - } - - /** - * 将指定Reader的所有文本全部读出到一个字符串中. - * - * @param reader 要读取的Reader - * - * @return 从Reader中取得的文本 - * - * @throws IOException 输入输出异常 - */ - public static String readText(Reader reader) throws IOException { - return readText(reader, -1); - } - - /** - * 将指定Reader的所有文本全部读出到一个字符串中. - * - * @param reader 要读取的Reader - * @param bufferSize 缓冲区的大小(字符数) - * - * @return 从Reader中取得的文本 - * - * @throws IOException 输入输出异常 - */ - public static String readText(Reader reader, int bufferSize) throws IOException { - StringWriter writer = new StringWriter(); - - io(reader, writer, bufferSize); - - return writer.toString(); - } - - /** - * 将指定InputStream的所有内容全部读出到一个byte数组中。 - * - * @param in 要读取的InputStream - * @return ByteArray # @see ByteArray - * @throws IOException - */ - public static ByteArray readBytes(InputStream in) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - io(in, out); - - return out.toByteArray(); - } - - /** 将指定InputStream的所有内容全部读出到一个byte数组中。 */ - public static ByteArray readBytes(InputStream in, boolean closeIn) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - io(in, out, closeIn, true); - - return out.toByteArray(); - } - - /** - * 将指定File的所有内容全部读出到一个byte数组中。 - * - * @param file 要读取的文件 - * @return ByteArray # @see ByteArray - * @throws IOException - */ - public static ByteArray readBytes(File file) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - io(new FileInputStream(file), out); - - return out.toByteArray(); - } - - /** 将指定File的所有内容全部读出到一个byte数组中。 */ - public static ByteArray readBytes(File file, boolean closeIn) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - io(new FileInputStream(file), out, closeIn, true); - - return out.toByteArray(); - } - - /** - * 通过快速缓冲将指定InputStream的所有内容全部读出到一个byte数组中。 - * - * @param in 要读取的InputStream - * @return byte[]字节数组 - * @throws IOException - */ - public static byte[] readBytesByFast(InputStream in) throws IOException { - FastByteArrayOutputStream out = new FastByteArrayOutputStream(); - io(in, out); - return out.toByteArray(); - } - - /** 通过快速缓冲将指定InputStream的所有内容全部读出到一个byte数组中。 */ - public static byte[] readBytesByFast(InputStream in, boolean closeIn) throws IOException { - FastByteArrayOutputStream out = new FastByteArrayOutputStream(); - - io(in, out, closeIn, true); - - return out.toByteArray(); - } - - /** 将字符串写入到指定输出流中。 */ - public static void writeText(CharSequence chars, OutputStream out, String charset, boolean closeOut) - throws IOException { - Writer writer = charset == null ? new OutputStreamWriter(out) : new OutputStreamWriter(out, charset); - - writeText(chars, writer, closeOut); - } - - /** 将字符串写入到指定Writer中。 */ - public static void writeText(CharSequence chars, Writer out, boolean closeOut) throws IOException { - try { - out.write(chars.toString()); - out.flush(); - } finally { - if (closeOut) { - try { - out.close(); - } catch (IOException e) { - // ignore - } - } - } - } - - /** 将byte数组写入到指定filePath中。 */ - public static void writeBytes(byte[] bytes, String filePath, boolean closeOut) throws IOException { - writeBytes(new ByteArray(bytes), new FileOutputStream(filePath), closeOut); - } - - /** 将byte数组写入到指定File中。 */ - public static void writeBytes(byte[] bytes, File file, boolean closeOut) throws IOException { - writeBytes(new ByteArray(bytes), new FileOutputStream(file), closeOut); - } - - /** 将byte数组写入到指定OutputStream中。 */ - public static void writeBytes(byte[] bytes, OutputStream out, boolean closeOut) throws IOException { - writeBytes(new ByteArray(bytes), out, closeOut); - } - - /** 将byte数组写入到指定OutputStream中。 */ - public static void writeBytes(ByteArray bytes, OutputStream out, boolean closeOut) throws IOException { - try { - out.write(bytes.getRawBytes(), bytes.getOffset(), bytes.getLength()); - out.flush(); - } finally { - if (closeOut) { - try { - out.close(); - } catch (IOException e) { - } - } - } - } - - /** - * 关闭流 - * - * @param closed 可关闭的流 - */ - public static void close(Closeable closed) { - if (closed != null) { - try { - closed.close(); - } catch (IOException ignore) { - // can ignore - } - } - } - - /** - * 同步化的输出流包裹器. - */ - private static class SynchronizedOutputStream extends OutputStream { - private OutputStream out; - private Object lock; - - SynchronizedOutputStream(OutputStream out) { - this(out, out); - } - - SynchronizedOutputStream(OutputStream out, Object lock) { - this.out = out; - this.lock = lock; - } - - public void write(int datum) throws IOException { - synchronized (lock) { - out.write(datum); - } - } - - public void write(byte[] data) throws IOException { - synchronized (lock) { - out.write(data); - } - } - - public void write(byte[] data, int offset, int length) throws IOException { - synchronized (lock) { - out.write(data, offset, length); - } - } - - public void flush() throws IOException { - synchronized (lock) { - out.flush(); - } - } - - public void close() throws IOException { - synchronized (lock) { - out.close(); - } - } - } - +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; + +import com.blade.kit.io.ByteArray; +import com.blade.kit.io.ByteArrayOutputStream; +import com.blade.kit.io.FastByteArrayOutputStream; + +/** + * 基于流的工具类 + * + * @author biezhi + * @since 1.0 + */ +public abstract class StreamKit { + + /** + * 从输入流读取内容, 写入到输出流中. 此方法使用大小为8192字节的默认的缓冲区. + * + * @param in 输入流 + * @param out 输出流 + * + * @throws IOException 输入输出异常 + */ + public static void io(InputStream in, OutputStream out) throws IOException { + io(in, out, -1); + } + + /** + * 从输入流读取内容, 写入到输出流中. 使用指定大小的缓冲区. + * + * @param in 输入流 + * @param out 输出流 + * @param bufferSize 缓冲区大小(字节数) + * + * @throws IOException 输入输出异常 + */ + public static void io(InputStream in, OutputStream out, int bufferSize) throws IOException { + if (bufferSize == -1) { + bufferSize = IOKit.DEFAULT_BUFFER_SIZE; + } + + byte[] buffer = new byte[bufferSize]; + int amount; + + while ((amount = in.read(buffer)) >= 0) { + out.write(buffer, 0, amount); + } + + out.flush(); + } + + /** + * 从输入流读取内容, 写入到输出流中. 此方法使用大小为8192字符的默认的缓冲区. + * + * @param in 输入流 + * @param out 输出流 + * + * @throws IOException 输入输出异常 + */ + public static void io(Reader in, Writer out) throws IOException { + io(in, out, -1); + } + + /** + * 从输入流读取内容, 写入到输出流中. 使用指定大小的缓冲区. + * + * @param in 输入流 + * @param out 输出流 + * @param bufferSize 缓冲区大小(字符数) + * + * @throws IOException 输入输出异常 + */ + public static void io(Reader in, Writer out, int bufferSize) throws IOException { + if (bufferSize == -1) { + bufferSize = IOKit.DEFAULT_BUFFER_SIZE >> 1; + } + + char[] buffer = new char[bufferSize]; + int amount; + + while ((amount = in.read(buffer)) >= 0) { + out.write(buffer, 0, amount); + } + + out.flush(); + } + + /** + * 从输入流读取内容, 写入到输出流中. 此方法使用大小为8192字节的默认的缓冲区. + * + * @param in 输入流 + * @param out 输出流 + * @param closeIn 是否关闭输入流 + * @param closeOut 是否关闭输出流 + * @throws IOException 输入输出异常 + */ + public static void io(InputStream in, OutputStream out, boolean closeIn, boolean closeOut) throws IOException { + + try { + io(in, out); + } finally { + if (closeIn) { + close(in); + } + + if (closeOut) { + close(out); + } + } + } + + /** + * 从输入流读取内容, 写入到输出流中. 此方法使用大小为8192字节的默认的缓冲区. + * + * @param in 输入流 + * @param out 输出流 + * @param closeIn 是否关闭输入流 + * @param closeOut 是否关闭输出流 + * @throws IOException 输入输出异常 + */ + public static void io(Reader in, Writer out, boolean closeIn, boolean closeOut) throws IOException { + try { + io(in, out); + } finally { + if (closeIn) { + close(in); + } + + if (closeOut) { + close(out); + } + } + } + + /** 从输入流读取内容, 写入到目标文件 */ + public static void io(InputStream in, File dest) throws IOException { + OutputStream out = new FileOutputStream(dest); + io(in, out); + } + + /** 从输入流读取内容, 写入到目标文件 */ + public static void io(InputStream in, File dest, boolean closeIn, boolean closeOut) throws IOException { + OutputStream out = new FileOutputStream(dest); + try { + io(in, out); + } finally { + if (closeIn) { + close(in); + } + + if (closeOut) { + close(out); + } + } + } + + /** 从输入流读取内容, 写入到目标文件 */ + public static void io(InputStream in, String dest) throws IOException { + OutputStream out = new FileOutputStream(dest); + io(in, out); + } + + /** 从输入流读取内容, 写入到目标文件 */ + public static void io(InputStream in, String dest, boolean closeIn, boolean closeOut) throws IOException { + OutputStream out = new FileOutputStream(dest); + try { + io(in, out); + } finally { + if (closeIn) { + close(in); + } + + if (closeOut) { + close(out); + } + } + } + + /** 从输入流读取内容, 写入到目标文件 */ + public static void io(Reader in, File dest) throws IOException { + Writer out = new FileWriter(dest); + io(in, out); + } + + /** 从输入流读取内容, 写入到目标文件 */ + public static void io(Reader in, File dest, boolean closeIn, boolean closeOut) throws IOException { + Writer out = new FileWriter(dest); + try { + io(in, out); + } finally { + if (closeIn) { + close(in); + } + + if (closeOut) { + close(out); + } + } + } + + /** 从输入流读取内容, 写入到目标文件 */ + public static void io(Reader in, String dest) throws IOException { + Writer out = new FileWriter(dest); + io(in, out); + } + + /** 从输入流读取内容, 写入到目标文件 */ + public static void io(Reader in, String dest, boolean closeIn, boolean closeOut) throws IOException { + Writer out = new FileWriter(dest); + try { + io(in, out); + } finally { + if (closeIn) { + close(in); + } + + if (closeOut) { + close(out); + } + } + } + + /** + * 取得同步化的输出流. + * + * @param out 要包裹的输出流 + * + * @return 线程安全的同步化输出流 + */ + public static OutputStream synchronizedOutputStream(OutputStream out) { + return new SynchronizedOutputStream(out); + } + + /** + * 取得同步化的输出流. + * + * @param out 要包裹的输出流 + * @param lock 同步锁 + * + * @return 线程安全的同步化输出流 + */ + public static OutputStream synchronizedOutputStream(OutputStream out, Object lock) { + return new SynchronizedOutputStream(out, lock); + } + + /** + * 将指定输入流的所有文本全部读出到一个字符串中. + * + * @param in 要读取的输入流 + * + * @return 从输入流中取得的文本 + * + * @throws IOException 输入输出异常 + */ + public static String readText(InputStream in) throws IOException { + return readText(in, null, -1); + } + + /** + * 将指定输入流的所有文本全部读出到一个字符串中. + * + * @param in 要读取的输入流 + * @param encoding 文本编码方式 + * + * @return 从输入流中取得的文本 + * + * @throws IOException 输入输出异常 + */ + public static String readText(InputStream in, String encoding) throws IOException { + return readText(in, encoding, -1); + } + + /** + * 将指定输入流的所有文本全部读出到一个字符串中. + * + * @param in 要读取的输入流 + * @param charset 文本编码方式 + * @param bufferSize 缓冲区大小(字符数) + * + * @return 从输入流中取得的文本 + * + * @throws IOException 输入输出异常 + */ + public static String readText(InputStream in, String charset, int bufferSize) throws IOException { + Reader reader = (charset == null) ? new InputStreamReader(in) : new InputStreamReader(in, charset); + + return readText(reader, bufferSize); + } + + /** + * 将指定输入流的所有文本全部读出到一个字符串中. + * + * @param in 要读取的输入流 + * @param charset 文本编码方式 + * @param closeIn 是否关闭输入流 + * @return 从输入流中取得的文本 + * @throws IOException 输入输出异常 + */ + public static String readText(InputStream in, String charset, boolean closeIn) throws IOException { + Reader reader = charset == null ? new InputStreamReader(in) : new InputStreamReader(in, charset); + + return readText(reader, closeIn); + } + + /** + * 将指定输入流的所有文本全部读出到一个字符串中. + * + * @param in 要读取的输入流 + * @param closeIn 是否关闭输入流 + * @return 从输入流中取得的文本 + * @throws IOException 输入输出异常 + */ + public static String readText(Reader in, boolean closeIn) throws IOException { + StringWriter out = new StringWriter(); + + io(in, out, closeIn, true); + + return out.toString(); + } + + /** + * 将指定Reader的所有文本全部读出到一个字符串中. + * + * @param reader 要读取的Reader + * + * @return 从Reader中取得的文本 + * + * @throws IOException 输入输出异常 + */ + public static String readText(Reader reader) throws IOException { + return readText(reader, -1); + } + + /** + * 将指定Reader的所有文本全部读出到一个字符串中. + * + * @param reader 要读取的Reader + * @param bufferSize 缓冲区的大小(字符数) + * + * @return 从Reader中取得的文本 + * + * @throws IOException 输入输出异常 + */ + public static String readText(Reader reader, int bufferSize) throws IOException { + StringWriter writer = new StringWriter(); + + io(reader, writer, bufferSize); + + return writer.toString(); + } + + /** + * 将指定InputStream的所有内容全部读出到一个byte数组中。 + * + * @param in 要读取的InputStream + * @return ByteArray # @see ByteArray + * @throws IOException + */ + public static ByteArray readBytes(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + io(in, out); + + return out.toByteArray(); + } + + /** 将指定InputStream的所有内容全部读出到一个byte数组中。 */ + public static ByteArray readBytes(InputStream in, boolean closeIn) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + io(in, out, closeIn, true); + + return out.toByteArray(); + } + + /** + * 将指定File的所有内容全部读出到一个byte数组中。 + * + * @param file 要读取的文件 + * @return ByteArray # @see ByteArray + * @throws IOException + */ + public static ByteArray readBytes(File file) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + io(new FileInputStream(file), out); + + return out.toByteArray(); + } + + /** 将指定File的所有内容全部读出到一个byte数组中。 */ + public static ByteArray readBytes(File file, boolean closeIn) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + io(new FileInputStream(file), out, closeIn, true); + + return out.toByteArray(); + } + + /** + * 通过快速缓冲将指定InputStream的所有内容全部读出到一个byte数组中。 + * + * @param in 要读取的InputStream + * @return byte[]字节数组 + * @throws IOException + */ + public static byte[] readBytesByFast(InputStream in) throws IOException { + FastByteArrayOutputStream out = new FastByteArrayOutputStream(); + io(in, out); + return out.toByteArray(); + } + + /** 通过快速缓冲将指定InputStream的所有内容全部读出到一个byte数组中。 */ + public static byte[] readBytesByFast(InputStream in, boolean closeIn) throws IOException { + FastByteArrayOutputStream out = new FastByteArrayOutputStream(); + + io(in, out, closeIn, true); + + return out.toByteArray(); + } + + /** 将字符串写入到指定输出流中。 */ + public static void writeText(CharSequence chars, OutputStream out, String charset, boolean closeOut) + throws IOException { + Writer writer = charset == null ? new OutputStreamWriter(out) : new OutputStreamWriter(out, charset); + + writeText(chars, writer, closeOut); + } + + /** 将字符串写入到指定Writer中。 */ + public static void writeText(CharSequence chars, Writer out, boolean closeOut) throws IOException { + try { + out.write(chars.toString()); + out.flush(); + } finally { + if (closeOut) { + try { + out.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + /** 将byte数组写入到指定filePath中。 */ + public static void writeBytes(byte[] bytes, String filePath, boolean closeOut) throws IOException { + writeBytes(new ByteArray(bytes), new FileOutputStream(filePath), closeOut); + } + + /** 将byte数组写入到指定File中。 */ + public static void writeBytes(byte[] bytes, File file, boolean closeOut) throws IOException { + writeBytes(new ByteArray(bytes), new FileOutputStream(file), closeOut); + } + + /** 将byte数组写入到指定OutputStream中。 */ + public static void writeBytes(byte[] bytes, OutputStream out, boolean closeOut) throws IOException { + writeBytes(new ByteArray(bytes), out, closeOut); + } + + /** 将byte数组写入到指定OutputStream中。 */ + public static void writeBytes(ByteArray bytes, OutputStream out, boolean closeOut) throws IOException { + try { + out.write(bytes.getRawBytes(), bytes.getOffset(), bytes.getLength()); + out.flush(); + } finally { + if (closeOut) { + try { + out.close(); + } catch (IOException e) { + } + } + } + } + + /** + * 关闭流 + * + * @param closed 可关闭的流 + */ + public static void close(Closeable closed) { + if (closed != null) { + try { + closed.close(); + } catch (IOException ignore) { + // can ignore + } + } + } + + /** + * 同步化的输出流包裹器. + */ + private static class SynchronizedOutputStream extends OutputStream { + private OutputStream out; + private Object lock; + + SynchronizedOutputStream(OutputStream out) { + this(out, out); + } + + SynchronizedOutputStream(OutputStream out, Object lock) { + this.out = out; + this.lock = lock; + } + + public void write(int datum) throws IOException { + synchronized (lock) { + out.write(datum); + } + } + + public void write(byte[] data) throws IOException { + synchronized (lock) { + out.write(data); + } + } + + public void write(byte[] data, int offset, int length) throws IOException { + synchronized (lock) { + out.write(data, offset, length); + } + } + + public void flush() throws IOException { + synchronized (lock) { + out.flush(); + } + } + + public void close() throws IOException { + synchronized (lock) { + out.close(); + } + } + } + } \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/StringKit.java b/blade-kit/src/main/java/com/blade/kit/StringKit.java similarity index 88% rename from blade-kit/src/main/java/blade/kit/StringKit.java rename to blade-kit/src/main/java/com/blade/kit/StringKit.java index 1f406731f..4b8026916 100644 --- a/blade-kit/src/main/java/blade/kit/StringKit.java +++ b/blade-kit/src/main/java/com/blade/kit/StringKit.java @@ -1,2608 +1,2870 @@ -/** - * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package blade.kit; - -import java.io.UnsupportedEncodingException; -import java.math.BigDecimal; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Random; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * 有关字符串处理的工具类。 - * - * @author biezhi - * @since 1.0 - */ -public abstract class StringKit { - - private static final String FOLDER_SEPARATOR = "/"; - private static final String WINDOWS_FOLDER_SEPARATOR = "\\"; - private static final String TOP_PATH = ".."; - private static final String CURRENT_PATH = "."; - public static String[] NUMBER = { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "百", "千", "万", "亿" }; - public static final String CREATE = "create"; - public static final String DELETE = "delete"; - public static final String SAVE = "save"; - public static final String UPDATE = "update"; - public static final String QUERY = "query"; - public static final String ERROR = "error"; - public static final String SUCCESS = "success"; - public static final String FAILED = "failed"; - public static final String IP = "strIp"; - public static final String ANSWER = "strAnswer"; - public static final String LOGIN = "login"; - public static final String INDEX = "index"; - public static final String HOME = "home"; - public static final String NORIGHT = "noRight"; - public static final String BOSSIP = "strBOSSIp"; - public static final String BOSSIPS = "BOSSIPS";// 配置文件参数 - public static final String BOSSANSWER = "BOSSANSWER";// 配置文件参数 - private static final String RANDOM_CHAR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - - /** - * 检查字符串是否为null或空字符串""。 - * - *
-     * StringUtil.isEmpty(null)      = true
-     * StringUtil.isEmpty("")        = true
-     * StringUtil.isEmpty(" ")       = false
-     * StringUtil.isEmpty("bob")     = false
-     * StringUtil.isEmpty("  bob  ") = false
-     * 
- * - * @param str 要检查的字符串 - * - * @return 如果为空, 则返回true - */ - public static boolean isEmpty(String str) { - return (str == null || str.length() == 0) ? true : false; - } - - public static boolean isAnyEmpty(String...strings) { - if (strings == null) { - return true; - } - for (String string : strings) { - if (isEmpty(string)) { - return true; - } - } - return false; - } - - /** - * 检查字符串是否不是null和空字符串""。 - * - *
-     * StringUtil.isEmpty(null)      = false
-     * StringUtil.isEmpty("")        = false
-     * StringUtil.isEmpty(" ")       = true
-     * StringUtil.isEmpty("bob")     = true
-     * StringUtil.isEmpty("  bob  ") = true
-     * 
- * - * @param str 要检查的字符串 - * - * @return 如果不为空, 则返回true - */ - public static boolean isNotEmpty(String str) { - return !isEmpty(str); - } - - /** - * 检查字符串是否是空白:null、空字符串""或只有空白字符。 - * - *
-     * StringUtil.isBlank(null)      = true
-     * StringUtil.isBlank("")        = true
-     * StringUtil.isBlank(" ")       = true
-     * StringUtil.isBlank("bob")     = false
-     * StringUtil.isBlank("  bob  ") = false
-     * 
- * - * @param str 要检查的字符串 - * - * @return 如果为空白, 则返回true - */ - public static boolean isBlank(String str) { - int length; - - if ((str == null) || ((length = str.length()) == 0)) { - return true; - } - - for (int i = 0; i < length; i++) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - - return true; - } - - public static boolean isAllBlank(String...strings) { - if (strings == null) { - return true; - } - for (String string : strings) { - if (isNotBlank(string)) { - return false; - } - } - return true; - } - - public static boolean isAnyBlank(String...strings) { - if (strings == null) { - return true; - } - for (String string : strings) { - if (isBlank(string)) { - return true; - } - } - return false; - } - - /** - * 检查字符串是否不是空白:null、空字符串""或只有空白字符。 - * - *
-     * StringUtil.isBlank(null)      = false
-     * StringUtil.isBlank("")        = false
-     * StringUtil.isBlank(" ")       = false
-     * StringUtil.isBlank("bob")     = true
-     * StringUtil.isBlank("  bob  ") = true
-     * 
- * - * @param str 要检查的字符串 - * - * @return 如果为空白, 则返回true - */ - public static boolean isNotBlank(String str) { - int length; - - if ((str == null) || ((length = str.length()) == 0)) { - return false; - } - - for (int i = 0; i < length; i++) { - if (!Character.isWhitespace(str.charAt(i))) { - return true; - } - } - - return false; - } - - /** - * 如果字符串是null,则返回指定默认字符串,否则返回字符串本身。 - * - *
-     * StringUtil.defaultIfNull(null, "default")  = "default"
-     * StringUtil.defaultIfNull("", "default")    = ""
-     * StringUtil.defaultIfNull("  ", "default")  = "  "
-     * StringUtil.defaultIfNull("bat", "default") = "bat"
-     * 
- * - * @param str 要转换的字符串 - * @param defaultStr 默认字符串 - * - * @return 字符串本身或指定的默认字符串 - */ - public static String defaultIfNull(String str) { - return (str == null) ? "" : str; - } - - /** - * 如果字符串是null,则返回指定默认字符串,否则返回字符串本身。 - * - *
-     * StringUtil.defaultIfNull(null, "default")  = "default"
-     * StringUtil.defaultIfNull("", "default")    = ""
-     * StringUtil.defaultIfNull("  ", "default")  = "  "
-     * StringUtil.defaultIfNull("bat", "default") = "bat"
-     * 
- * - * @param str 要转换的字符串 - * @param defaultStr 默认字符串 - * - * @return 字符串本身或指定的默认字符串 - */ - public static String defaultIfNull(String str, String defaultStr) { - return (str == null) ? defaultStr : str; - } - - /** - * 除去字符串头尾部的空白,如果字符串是null,依然返回null。 - * - *

- * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 - * - *

-     * StringUtil.trim(null)          = null
-     * StringUtil.trim("")            = ""
-     * StringUtil.trim("     ")       = ""
-     * StringUtil.trim("abc")         = "abc"
-     * StringUtil.trim("    abc    ") = "abc"
-     * 
- * - *

- * - * @param str 要处理的字符串 - * - * @return 除去空白的字符串,如果原字串为null,则返回null - */ - public static String trim(String str) { - return trim(str, null, 0); - } - - /** - * Trims array of strings. null array elements are ignored. - */ - public static void trimAll(String[] strings) { - if (strings == null) { - return; - } - for (int i = 0; i < strings.length; i++) { - String string = strings[i]; - if (string != null) { - strings[i] = trim(string); - } - } - } - - /** - * 除去字符串头尾部的指定字符,如果字符串是null,依然返回null。 - * - *
-     * StringUtil.trim(null, *)          = null
-     * StringUtil.trim("", *)            = ""
-     * StringUtil.trim("abc", null)      = "abc"
-     * StringUtil.trim("  abc", null)    = "abc"
-     * StringUtil.trim("abc  ", null)    = "abc"
-     * StringUtil.trim(" abc ", null)    = "abc"
-     * StringUtil.trim("  abcyx", "xyz") = "  abc"
-     * 
- * - * @param str 要处理的字符串 - * @param stripChars 要除去的字符,如果为null表示除去空白字符 - * - * @return 除去指定字符后的的字符串,如果原字串为null,则返回null - */ - public static String trim(String str, String stripChars) { - return trim(str, stripChars, 0); - } - - /** - * 除去字符串头部的空白,如果字符串是null,则返回null。 - * - *

- * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 - * - *

-     * StringUtil.trimStart(null)         = null
-     * StringUtil.trimStart("")           = ""
-     * StringUtil.trimStart("abc")        = "abc"
-     * StringUtil.trimStart("  abc")      = "abc"
-     * StringUtil.trimStart("abc  ")      = "abc  "
-     * StringUtil.trimStart(" abc ")      = "abc "
-     * 
- * - *

- * - * @param str 要处理的字符串 - * - * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null - */ - public static String trimStart(String str) { - return trim(str, null, -1); - } - - /** - * 除去字符串头部的指定字符,如果字符串是null,依然返回null。 - * - *
-     * StringUtil.trimStart(null, *)          = null
-     * StringUtil.trimStart("", *)            = ""
-     * StringUtil.trimStart("abc", "")        = "abc"
-     * StringUtil.trimStart("abc", null)      = "abc"
-     * StringUtil.trimStart("  abc", null)    = "abc"
-     * StringUtil.trimStart("abc  ", null)    = "abc  "
-     * StringUtil.trimStart(" abc ", null)    = "abc "
-     * StringUtil.trimStart("yxabc  ", "xyz") = "abc  "
-     * 
- * - * @param str 要处理的字符串 - * @param stripChars 要除去的字符,如果为null表示除去空白字符 - * - * @return 除去指定字符后的的字符串,如果原字串为null,则返回null - */ - public static String trimStart(String str, String stripChars) { - return trim(str, stripChars, -1); - } - - /** - * 除去字符串尾部的空白,如果字符串是null,则返回null。 - * - *

- * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 - * - *

-     * StringUtil.trimEnd(null)       = null
-     * StringUtil.trimEnd("")         = ""
-     * StringUtil.trimEnd("abc")      = "abc"
-     * StringUtil.trimEnd("  abc")    = "  abc"
-     * StringUtil.trimEnd("abc  ")    = "abc"
-     * StringUtil.trimEnd(" abc ")    = " abc"
-     * 
- * - *

- * - * @param str 要处理的字符串 - * - * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null - */ - public static String trimEnd(String str) { - return trim(str, null, 1); - } - - /** - * 除去字符串尾部的指定字符,如果字符串是null,依然返回null。 - * - *
-     * StringUtil.trimEnd(null, *)          = null
-     * StringUtil.trimEnd("", *)            = ""
-     * StringUtil.trimEnd("abc", "")        = "abc"
-     * StringUtil.trimEnd("abc", null)      = "abc"
-     * StringUtil.trimEnd("  abc", null)    = "  abc"
-     * StringUtil.trimEnd("abc  ", null)    = "abc"
-     * StringUtil.trimEnd(" abc ", null)    = " abc"
-     * StringUtil.trimEnd("  abcyx", "xyz") = "  abc"
-     * 
- * - * @param str 要处理的字符串 - * @param stripChars 要除去的字符,如果为null表示除去空白字符 - * - * @return 除去指定字符后的的字符串,如果原字串为null,则返回null - */ - public static String trimEnd(String str, String stripChars) { - return trim(str, stripChars, 1); - } - - /** - * 除去字符串头尾部的空白,如果结果字符串是空字符串"",则返回null。 - * - *

- * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 - * - *

-     * StringUtil.trimToNull(null)          = null
-     * StringUtil.trimToNull("")            = null
-     * StringUtil.trimToNull("     ")       = null
-     * StringUtil.trimToNull("abc")         = "abc"
-     * StringUtil.trimToNull("    abc    ") = "abc"
-     * 
- * - *

- * - * @param str 要处理的字符串 - * - * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null - */ - public static String trimToNull(String str) { - return trimToNull(str, null); - } - - /** - * 除去字符串头尾部的空白,如果结果字符串是空字符串"",则返回null。 - * - *

- * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 - * - *

-     * StringUtil.trim(null, *)          = null
-     * StringUtil.trim("", *)            = null
-     * StringUtil.trim("abc", null)      = "abc"
-     * StringUtil.trim("  abc", null)    = "abc"
-     * StringUtil.trim("abc  ", null)    = "abc"
-     * StringUtil.trim(" abc ", null)    = "abc"
-     * StringUtil.trim("  abcyx", "xyz") = "  abc"
-     * 
- * - *

- * - * @param str 要处理的字符串 - * @param stripChars 要除去的字符,如果为null表示除去空白字符 - * - * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null - */ - public static String trimToNull(String str, String stripChars) { - String result = trim(str, stripChars); - - if ((result == null) || (result.length() == 0)) { - return null; - } - - return result; - } - - /** - * 除去字符串头尾部的空白,如果字符串是null,则返回空字符串""。 - * - *

- * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 - * - *

-     * StringUtil.trimToEmpty(null)          = ""
-     * StringUtil.trimToEmpty("")            = ""
-     * StringUtil.trimToEmpty("     ")       = ""
-     * StringUtil.trimToEmpty("abc")         = "abc"
-     * StringUtil.trimToEmpty("    abc    ") = "abc"
-     * 
- * - *

- * - * @param str 要处理的字符串 - * - * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null - */ - public static String trimToEmpty(String str) { - return trimToEmpty(str, null); - } - - /** - * 除去字符串头尾部的空白,如果字符串是null,则返回空字符串""。 - * - *

- * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 - * - *

-     * StringUtil.trim(null, *)          = ""
-     * StringUtil.trim("", *)            = ""
-     * StringUtil.trim("abc", null)      = "abc"
-     * StringUtil.trim("  abc", null)    = "abc"
-     * StringUtil.trim("abc  ", null)    = "abc"
-     * StringUtil.trim(" abc ", null)    = "abc"
-     * StringUtil.trim("  abcyx", "xyz") = "  abc"
-     * 
- * - *

- * - * @param str 要处理的字符串 - * - * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null - */ - public static String trimToEmpty(String str, String stripChars) { - String result = trim(str, stripChars); - - if (result == null) { - return ""; - } - return result; - } - - /** - * 除去字符串头尾部的指定字符,如果字符串是null,依然返回null。 - * - *
-     * StringUtil.trim(null, *)          = null
-     * StringUtil.trim("", *)            = ""
-     * StringUtil.trim("abc", null)      = "abc"
-     * StringUtil.trim("  abc", null)    = "abc"
-     * StringUtil.trim("abc  ", null)    = "abc"
-     * StringUtil.trim(" abc ", null)    = "abc"
-     * StringUtil.trim("  abcyx", "xyz") = "  abc"
-     * 
- * - * @param str 要处理的字符串 - * @param stripChars 要除去的字符,如果为null表示除去空白字符 - * @param mode -1表示trimStart,0表示trim全部, 1表示trimEnd - * - * @return 除去指定字符后的的字符串,如果原字串为null,则返回null - */ - private static String trim(String str, String stripChars, int mode) { - if (str == null) { - return null; - } - - int length = str.length(); - int start = 0; - int end = length; - - // 扫描字符串头部 - if (mode <= 0) { - if (stripChars == null) { - while ((start < end) && (Character.isWhitespace(str.charAt(start)))) { - start++; - } - } else if (stripChars.length() == 0) { - return str; - } else { - while ((start < end) && (stripChars.indexOf(str.charAt(start)) != -1)) { - start++; - } - } - } - - // 扫描字符串尾部 - if (mode >= 0) { - if (stripChars == null) { - while ((start < end) && (Character.isWhitespace(str.charAt(end - 1)))) { - end--; - } - } else if (stripChars.length() == 0) { - return str; - } else { - while ((start < end) && (stripChars.indexOf(str.charAt(end - 1)) != -1)) { - end--; - } - } - } - - if ((start > 0) || (end < length)) { - return str.substring(start, end); - } - - return str; - } - - /* - * ========================================================================== == - */ - /* 比较函数。 */ - /* */ - /* 以下方法用来比较两个字符串是否相同。 */ - /* - * ========================================================================== == - */ - - /** - * 比较两个字符串(大小写敏感)。 - * - *
-     * StringUtil.equals(null, null)   = true
-     * StringUtil.equals(null, "abc")  = false
-     * StringUtil.equals("abc", null)  = false
-     * StringUtil.equals("abc", "abc") = true
-     * StringUtil.equals("abc", "ABC") = false
-     * 
- * - * @param str1 要比较的字符串1 - * @param str2 要比较的字符串2 - * - * @return 如果两个字符串相同,或者都是null,则返回true - */ - public static boolean equals(String str1, String str2) { - if (str1 == null) { - return str2 == null; - } - - return str1.equals(str2); - } - - /** - * 比较两个字符串(大小写不敏感)。 - * - *
-     * StringUtil.equalsIgnoreCase(null, null)   = true
-     * StringUtil.equalsIgnoreCase(null, "abc")  = false
-     * StringUtil.equalsIgnoreCase("abc", null)  = false
-     * StringUtil.equalsIgnoreCase("abc", "abc") = true
-     * StringUtil.equalsIgnoreCase("abc", "ABC") = true
-     * 
- * - * @param str1 要比较的字符串1 - * @param str2 要比较的字符串2 - * - * @return 如果两个字符串相同,或者都是null,则返回true - */ - public static boolean equalsIgnoreCase(String str1, String str2) { - if (str1 == null) { - return str2 == null; - } - - return str1.equalsIgnoreCase(str2); - } - - /** - * Compares string with at least one from the provided array. If at least one equal string is found, returns its - * index. Otherwise, -1 is returned. - */ - public static int equalsOne(String src, String[] dest) { - if (src == null || dest == null) { - return -1; - } - - for (int i = 0; i < dest.length; i++) { - if (src.equals(dest[i])) { - return i; - } - } - return -1; - } - - /** - * Compares string with at least one from the provided array, ignoring case. If at least one equal string is found, - * it returns its index. Otherwise, -1 is returned. - */ - public static int equalsOneIgnoreCase(String src, String[] dest) { - if (src == null || dest == null) { - return -1; - } - - for (int i = 0; i < dest.length; i++) { - if (src.equalsIgnoreCase(dest[i])) { - return i; - } - } - return -1; - } - - /** - * Compares two string arrays. - * - * @param as first string array - * @param as1 second string array - * - * @return true if all array elements matches - */ - public static boolean equalsIgnoreCase(String as[], String as1[]) { - if (as == null && as1 == null) { - return true; - } - if (as == null || as1 == null) { - return false; - } - if (as.length != as1.length) { - return false; - } - for (int i = 0; i < as.length; i++) { - if (!as[i].equalsIgnoreCase(as1[i])) { - return false; - } - } - return true; - } - - /* - * ========================================================================== == - */ - /* 字符串类型判定函数。 */ - /* */ - /* 判定字符串的类型是否为:字母、数字、空白等 */ - /* - * ========================================================================== == - */ - - /** - * 判断字符串是否只包含unicode字母。 - * - *

- * null将返回false,空字符串""将返回 true。 - *

- * - *
-     * StringUtil.isAlpha(null)   = false
-     * StringUtil.isAlpha("")     = true
-     * StringUtil.isAlpha("  ")   = false
-     * StringUtil.isAlpha("abc")  = true
-     * StringUtil.isAlpha("ab2c") = false
-     * StringUtil.isAlpha("ab-c") = false
-     * 
- * - * @param str 要检查的字符串 - * - * @return 如果字符串非null并且全由unicode字母组成,则返回true - */ - public static boolean isAlpha(String str) { - if (str == null) { - return false; - } - - int length = str.length(); - - for (int i = 0; i < length; i++) { - if (!Character.isLetter(str.charAt(i))) { - return false; - } - } - - return true; - } - - /** - * 判断字符串是否只包含unicode字母和空格' '。 - * - *

- * null将返回false,空字符串""将返回 true。 - *

- * - *
-     * StringUtil.isAlphaSpace(null)   = false
-     * StringUtil.isAlphaSpace("")     = true
-     * StringUtil.isAlphaSpace("  ")   = true
-     * StringUtil.isAlphaSpace("abc")  = true
-     * StringUtil.isAlphaSpace("ab c") = true
-     * StringUtil.isAlphaSpace("ab2c") = false
-     * StringUtil.isAlphaSpace("ab-c") = false
-     * 
- * - * @param str 要检查的字符串 - * - * @return 如果字符串非null并且全由unicode字母和空格组成,则返回true - */ - public static boolean isAlphaSpace(String str) { - if (str == null) { - return false; - } - - int length = str.length(); - - for (int i = 0; i < length; i++) { - if (!Character.isLetter(str.charAt(i)) && (str.charAt(i) != ' ')) { - return false; - } - } - - return true; - } - - /** - * 判断字符串是否只包含unicode字母和数字。 - * - *

- * null将返回false,空字符串""将返回 true。 - *

- * - *
-     * StringUtil.isAlphanumeric(null)   = false
-     * StringUtil.isAlphanumeric("")     = true
-     * StringUtil.isAlphanumeric("  ")   = false
-     * StringUtil.isAlphanumeric("abc")  = true
-     * StringUtil.isAlphanumeric("ab c") = false
-     * StringUtil.isAlphanumeric("ab2c") = true
-     * StringUtil.isAlphanumeric("ab-c") = false
-     * 
- * - * @param str 要检查的字符串 - * - * @return 如果字符串非null并且全由unicode字母数字组成,则返回true - */ - public static boolean isAlphanumeric(String str) { - if (str == null) { - return false; - } - - int length = str.length(); - - for (int i = 0; i < length; i++) { - if (!Character.isLetterOrDigit(str.charAt(i))) { - return false; - } - } - - return true; - } - - /** - * 判断字符串是否只包含unicode字母数字和空格' '。 - * - *

- * null将返回false,空字符串""将返回 true。 - *

- * - *
-     * StringUtil.isAlphanumericSpace(null)   = false
-     * StringUtil.isAlphanumericSpace("")     = true
-     * StringUtil.isAlphanumericSpace("  ")   = true
-     * StringUtil.isAlphanumericSpace("abc")  = true
-     * StringUtil.isAlphanumericSpace("ab c") = true
-     * StringUtil.isAlphanumericSpace("ab2c") = true
-     * StringUtil.isAlphanumericSpace("ab-c") = false
-     * 
- * - * @param str 要检查的字符串 - * - * @return 如果字符串非null并且全由unicode字母数字和空格组成,则返回true - */ - public static boolean isAlphanumericSpace(String str) { - if (str == null) { - return false; - } - - int length = str.length(); - - for (int i = 0; i < length; i++) { - if (!Character.isLetterOrDigit(str.charAt(i)) && (str.charAt(i) != ' ')) { - return false; - } - } - - return true; - } - - /** - * 判断字符串是否只包含unicode数字。 - * - *

- * null将返回false,空字符串""将返回 true。 - *

- * - *
-     * StringUtil.isNumeric(null)   = false
-     * StringUtil.isNumeric("")     = true
-     * StringUtil.isNumeric("  ")   = false
-     * StringUtil.isNumeric("123")  = true
-     * StringUtil.isNumeric("12 3") = false
-     * StringUtil.isNumeric("ab2c") = false
-     * StringUtil.isNumeric("12-3") = false
-     * StringUtil.isNumeric("12.3") = false
-     * 
- * - * @param str 要检查的字符串 - * - * @return 如果字符串非null并且全由unicode数字组成,则返回true - */ - public static boolean isNumeric(String str) { - if (str == null) { - return false; - } - - int length = str.length(); - - for (int i = 0; i < length; i++) { - if (!Character.isDigit(str.charAt(i))) { - return false; - } - } - - return true; - } - - /** - * 判断字符串是否只包含unicode数字和空格' '。 - * - *

- * null将返回false,空字符串""将返回 true。 - *

- * - *
-     * StringUtil.isNumericSpace(null)   = false
-     * StringUtil.isNumericSpace("")     = true
-     * StringUtil.isNumericSpace("  ")   = true
-     * StringUtil.isNumericSpace("123")  = true
-     * StringUtil.isNumericSpace("12 3") = true
-     * StringUtil.isNumericSpace("ab2c") = false
-     * StringUtil.isNumericSpace("12-3") = false
-     * StringUtil.isNumericSpace("12.3") = false
-     * 
- * - * @param str 要检查的字符串 - * - * @return 如果字符串非null并且全由unicode数字和空格组成,则返回true - */ - public static boolean isNumericSpace(String str) { - if (str == null) { - return false; - } - - int length = str.length(); - - for (int i = 0; i < length; i++) { - if (!Character.isDigit(str.charAt(i)) && (str.charAt(i) != ' ')) { - return false; - } - } - - return true; - } - - /** - * 判断字符串是否只包含unicode空白。 - * - *

- * null将返回false,空字符串""将返回 true。 - *

- * - *
-     * StringUtil.isWhitespace(null)   = false
-     * StringUtil.isWhitespace("")     = true
-     * StringUtil.isWhitespace("  ")   = true
-     * StringUtil.isWhitespace("abc")  = false
-     * StringUtil.isWhitespace("ab2c") = false
-     * StringUtil.isWhitespace("ab-c") = false
-     * 
- * - * @param str 要检查的字符串 - * - * @return 如果字符串非null并且全由unicode空白组成,则返回true - */ - public static boolean isWhitespace(String str) { - if (str == null) { - return false; - } - - int length = str.length(); - - for (int i = 0; i < length; i++) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - - return true; - } - - /* - * ========================================================================== == - */ - /* 大小写转换。 */ - /* - * ========================================================================== == - */ - - /** - * 将字符串转换成大写。 - * - *

- * 如果字符串是null则返回null。 - * - *

-     * StringUtil.toUpperCasing(null)  = null
-     * StringUtil.toUpperCasing("")    = ""
-     * StringUtil.toUpperCasing("aBc") = "ABC"
-     * 
- * - *

- * - * @param str 要转换的字符串 - * - * @return 大写字符串,如果原字符串为null,则返回null - */ - public static String toUpperCase(String str) { - if (str == null) { - return null; - } - - return str.toUpperCase(); - } - - /** - * 将字符串转换成小写。 - * - *

- * 如果字符串是null则返回null。 - * - *

-     * StringUtil.toLowerCasing(null)  = null
-     * StringUtil.toLowerCasing("")    = ""
-     * StringUtil.toLowerCasing("aBc") = "abc"
-     * 
- * - *

- * - * @param str 要转换的字符串 - * - * @return 大写字符串,如果原字符串为null,则返回null - */ - public static String toLowerCase(String str) { - if (str == null) { - return null; - } - - return str.toLowerCase(); - } - - /** - * 将字符串的首字符转成大写(Character.toTitleCase),其它字符不变。 - * - *

- * 如果字符串是null则返回null。 - * - *

-     * StringUtil.capitalize(null)  = null
-     * StringUtil.capitalize("")    = ""
-     * StringUtil.capitalize("cat") = "Cat"
-     * StringUtil.capitalize("cAt") = "CAt"
-     * 
- * - *

- * - * @param str 要转换的字符串 - * - * @return 首字符为大写的字符串,如果原字符串为null,则返回null - */ - public static String capitalize(String str) { - int strLen; - - if ((str == null) || ((strLen = str.length()) == 0)) { - return str; - } - - return new StringBuilder(strLen).append(Character.toTitleCase(str.charAt(0))).append(str.substring(1)) - .toString(); - } - - /** - * 将字符串的首字符转成小写,其它字符不变。 - * - *

- * 如果字符串是null则返回null。 - * - *

-     * StringUtil.uncapitalize(null)  = null
-     * StringUtil.uncapitalize("")    = ""
-     * StringUtil.uncapitalize("Cat") = "cat"
-     * StringUtil.uncapitalize("CAT") = "cAT"
-     * 
- * - *

- * - * @param str 要转换的字符串 - * - * @return 首字符为小写的字符串,如果原字符串为null,则返回null - */ - public static String uncapitalize(String str) { - int strLen; - - if ((str == null) || ((strLen = str.length()) == 0)) { - return str; - } - - return new StringBuilder(strLen).append(Character.toLowerCase(str.charAt(0))).append(str.substring(1)) - .toString(); - } - - /** - * 与 {@link #uncapitalize(String)}不同,连续大写字符将不做改变 - * - *

- * 如果字符串是null则返回null。 - * - *

-     * StringUtil.uncapitalize(null)  = null
-     * StringUtil.uncapitalize("")    = ""
-     * StringUtil.uncapitalize("Cat") = "cat"
-     * StringUtil.uncapitalize("CAT") = "CAT"
-     * 
- * - *

- * - * @param str 要转换的字符串 - * - * @return 首字符为小写的字符串,如果原字符串为null,则返回null - */ - public static String decapitalize(String str) { - if ((str == null) || str.length() == 0) { - return str; - } - if (str.length() > 1 && Character.isUpperCase(str.charAt(1)) && Character.isUpperCase(str.charAt(0))) { - return str; - } - - char chars[] = str.toCharArray(); - char c = chars[0]; - char modifiedChar = Character.toLowerCase(c); - if (modifiedChar == c) { - return str; - } - chars[0] = modifiedChar; - return new String(chars); - } - - /** - * 反转字符串的大小写。 - * - *

- * 如果字符串是null则返回null。 - * - *

-     * StringUtil.swapCasing(null)                 = null
-     * StringUtil.swapCasing("")                   = ""
-     * StringUtil.swapCasing("The dog has a BONE") = "tHE DOG HAS A bone"
-     * 
- * - *

- * - * @param str 要转换的字符串 - * - * @return 大小写被反转的字符串,如果原字符串为null,则返回null - */ - public static String swapCase(String str) { - int strLen; - - if ((str == null) || ((strLen = str.length()) == 0)) { - return str; - } - - StringBuilder builder = new StringBuilder(strLen); - - char ch = 0; - - for (int i = 0; i < strLen; i++) { - ch = str.charAt(i); - - if (Character.isUpperCase(ch)) { - ch = Character.toLowerCase(ch); - } else if (Character.isTitleCase(ch)) { - ch = Character.toLowerCase(ch); - } else if (Character.isLowerCase(ch)) { - ch = Character.toUpperCase(ch); - } - - builder.append(ch); - } - - return builder.toString(); - } - - public static String fromCamelCase(String str, char separator) { - int strLen; - - if ((str == null) || ((strLen = str.length()) == 0)) { - return str; - } - StringBuilder result = new StringBuilder(strLen * 2); - int resultLength = 0; - boolean prevTranslated = false; - for (int i = 0; i < strLen; i++) { - char c = str.charAt(i); - if (i > 0 || c != separator) {// skip first starting separator - if (Character.isUpperCase(c)) { - if (!prevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != separator) { - result.append(separator); - resultLength++; - } - c = Character.toLowerCase(c); - prevTranslated = true; - } else { - prevTranslated = false; - } - result.append(c); - resultLength++; - } - } - return resultLength > 0 ? result.toString() : str; - } - - /** - * String2Short - * - * @param s - * @param def - * @return - */ - public static short toShort(String s, short def) { - try { - return (isEmpty(s)) ? def : Short.parseShort(s); - } catch (NumberFormatException e) { - return def; - } - } - - /** - * String2Int - * - * @param s - * @param def - * @return - */ - public static int toInt(String s, int def) { - try { - return (isEmpty(s)) ? def : Integer.parseInt(s); - } catch (NumberFormatException e) { - return def; - } - } - - /** - * String2Long - * - * @param s - * @param def - * @return - */ - public static long toLong(String s, long def) { - try { - return (isEmpty(s)) ? def : Long.parseLong(s); - } catch (NumberFormatException e) { - return def; - } - } - - /** - * String2Float - * - * @param s - * @param def - * @return - */ - public static float toFloat(String s, float def) { - try { - return (isEmpty(s)) ? def : Float.parseFloat(s); - } catch (NumberFormatException e) { - return def; - } - } - - /** - * String2Double - * - * @param s - * @param def - * @return - */ - public static double toDouble(String s, double def) { - try { - return (isEmpty(s)) ? def : Double.parseDouble(s); - } catch (NumberFormatException e) { - return def; - } - } - - /** - * String2Boolean - * - * @param s - * @param def - * @return - */ - public static boolean toBoolean(String s, boolean def) { - if (isEmpty(s)) - return def; - else { - return "true".equalsIgnoreCase(s); - } - } - - // ----------- 字符串截取 ----------- // - /** - * 截取 float/double 类型 一位小数 - * - * @param f - * @return - */ - public static String getDelFormat(Object f) { - DecimalFormat df = new DecimalFormat("0.0"); - return df.format(f); - } - - /** - * 截取 float/double 类型 两位小数 - * - * @param f - * @return - */ - public static String getDelFormat2(Object f) { - DecimalFormat df = new DecimalFormat("0.00"); - return df.format(f); - } - - /** - * 截取三位 - * - * @param f - * @return - */ - public static String getDelFormat3(Object f) { - DecimalFormat df = new DecimalFormat("0.000"); - return df.format(f); - } - - /** - * 截取字符串 先是一定的长多 多余... - * - * @param source - * 元字符串 - * @param len - * 显示长多 - * @return - */ - public static String getStringsubstr(String source, int len) { - if (null == source || "".equals(source)) { - return ""; - } - if (source.length() > len) { - return source.substring(0, len) + "..."; - } - - return source; - } - - // ----------- 随机数 ----------- // - /** - * 获得0-max的随机数 - * - * @param length - * @return String - */ - public static String getRandomNumber(int length, int max) { - Random random = new Random(); - StringBuffer buffer = new StringBuffer(); - - for (int i = 0; i < length; i++) { - buffer.append(random.nextInt(max)); - } - return buffer.toString(); - } - - /** - * 获取指定长度的随机数字组成的字符串 - * - * @param size - * @return - */ - public static String getRandomNumber(int size) { - String num = ""; - for (int i = 0; i < size; i++) { - double a = Math.random() * 9; - a = Math.ceil(a); - int randomNum = new Double(a).intValue(); - num += randomNum; - } - return num; - } - - /** - * 获取随机字符 - * - * @param size - * @return - */ - public static String getRandomChar(int size) { - String sRand = ""; - Random random = new Random();// 创建一个随机类 - for (int i = 0; i < size; i++) { - String ch = String.valueOf(RANDOM_CHAR.charAt(random.nextInt(RANDOM_CHAR.length()))); - sRand += ch; - } - return sRand; - } - - // ----------- 验证 ----------- // - /** - * 判断字符串是否为数字和有正确的值 - * - * @param str - * @return - */ - public static boolean isNumber(String str) { - // Pattern pattern=Pattern.compile("[0-9]*"); - // return pattern.matcher(str).matches(); - if (null != str && 0 != str.trim().length() && str.matches("\\d*")) { - return true; - } - - return false; - } - - /** - * 将阿拉伯数字转为中文数字 - * - * @return - */ - public static String toChineseNumber(int number, int depth) { - if (depth < 0) - depth = 0; - if (number <= 0 && depth == 0) - return NUMBER[0]; - - String chinese = ""; - String src = number + ""; - if (src.charAt(src.length() - 1) == 'l' || src.charAt(src.length() - 1) == 'L') { - src = src.substring(0, src.length() - 1); - } - - if (src.length() > 4) - chinese = toChineseNumber(Integer.parseInt(src.substring(0, src.length() - 4)), depth + 1) - + toChineseNumber(Integer.parseInt(src.substring(src.length() - 4, src.length())), depth - 1); - else { - char prv = 0; - for (int i = 0; i < src.length(); i++) { - switch (src.charAt(i)) { - case '0': - if (prv != '0') - chinese = chinese + NUMBER[0]; - break; - case '1': - chinese = chinese + NUMBER[1]; - break; - case '2': - chinese = chinese + NUMBER[2]; - break; - case '3': - chinese = chinese + NUMBER[3]; - break; - case '4': - chinese = chinese + NUMBER[4]; - break; - case '5': - chinese = chinese + NUMBER[5]; - break; - case '6': - chinese = chinese + NUMBER[6]; - break; - case '7': - chinese = chinese + NUMBER[7]; - break; - case '8': - chinese = chinese + NUMBER[8]; - break; - case '9': - chinese = chinese + NUMBER[9]; - break; - } - prv = src.charAt(i); - - switch (src.length() - 1 - i) { - case 1:// 十 - if (prv != '0') - chinese = chinese + NUMBER[10]; - break; - case 2:// 百 - if (prv != '0') - chinese = chinese + NUMBER[11]; - break; - case 3:// 千 - if (prv != '0') - chinese = chinese + NUMBER[12]; - break; - } - } - } - while (chinese.length() > 0 && chinese.lastIndexOf(NUMBER[0]) == chinese.length() - 1) - chinese = chinese.substring(0, chinese.length() - 1); - if (depth == 1) - chinese += NUMBER[13]; - if (depth == 2) - chinese += NUMBER[14]; - return chinese; - } - - /** - * 验证字符串是否含有特殊字符和中文 - * - * @param - * - * @return - */ - public static boolean checkIsEnglish(String s) { - String Letters = "(){}[]\",.<>\\/~!@#$%^&*;': "; - int i; - int c; - - if (s.charAt(0) == '-') { - return false; - } - if (s.charAt(s.length() - 1) == '-') { - return false; - } - - for (i = 0; i < s.length(); i++) { - c = s.charAt(i); - if (Letters.indexOf(c) > -1) { - return false; - } - } - - // 验证是否刚好匹配 - boolean yesorno = isChineseStr(s); - if (yesorno) { - return false; - } - return true; - } - - public static boolean isChineseStr(String pValue) { - for (int i = 0; i < pValue.length(); i++) { - if ((int) pValue.charAt(i) > 256) - return true; - } - return false; - } - - /** - * 判断一个字符是Ascill字符还是其它字符(如汉,日,韩文字符) - * - * @param char c, 需要判断的字符 - * @return boolean, 返回true,Ascill字符 - */ - public static boolean isLetter(char c) { - int k = 0x80; - return c / k == 0 ? true : false; - } - - - // ----------- 格式化 ---------- // - /** - * 格式化数据 - * - * @param decima - * l3453454 - * @return 3,453,454 - */ - public final static String FormatDecimalString(String decimal) { - double dValue = Double.valueOf(decimal).doubleValue(); - DecimalFormat df = new DecimalFormat(); - String positivePattern = " ,000"; - String negativePattern = " ,000"; - if (dValue < 0) { - df.applyPattern(negativePattern); - return df.format(dValue).replace(',', ','); - } else { - df.applyPattern(positivePattern); - return df.format(dValue).replace(',', ','); - } - } - - /** - * 格式化数据 - * - * @param source - * 3453454 - * @return 3,453,454 - */ - public static String getNumberFormat(long source) { - NumberFormat usFormat = NumberFormat.getIntegerInstance(Locale.US); - return usFormat.format(source); - } - - // ----------- 过滤 ---------- // - /** - * 过滤字符串里的的特殊字符 - * - * @param str - * 要过滤的字符串 - * @return 过滤后的字符串 - */ - public static String stringFilter(String str) { - // 过滤通过页面表单提交的字符 - String[][] FilterChars = { { "<", "<" }, { ">", ">" }, { " ", " " }, { "\"", """ }, { "&", "&" }, - { "/", "/" }, { "\\", "\" }, { "'", "\\'" }, { "%", "%" } }; - - String[] str_arr = stringSpilit(str, ""); - - for (int i = 0; i < str_arr.length; i++) { - for (int j = 0; j < FilterChars.length; j++) { - if (FilterChars[j][0].equals(str_arr[i])) - str_arr[i] = FilterChars[j][1]; - } - } - return (stringConnect(str_arr, "")).trim(); - } - - // 关健字过滤 - public static String stringKeyWorldFilter(String str) { - // 过滤通过页面表单提交的字符 - String[][] FilterChars = { { "<", "" }, { ">", "" }, { "\"", "" }, { "&", "" }, { "/", "" }, { "\\", "" }, { "'", "" }, - { "%", "" } }; - - String[] str_arr = stringSpilit(str, ""); - - for (int i = 0; i < str_arr.length; i++) { - for (int j = 0; j < FilterChars.length; j++) { - if (FilterChars[j][0].equals(str_arr[i])) - str_arr[i] = FilterChars[j][1]; - } - } - return (stringConnect(str_arr, "")).trim(); - } - - // ----------- 切割合并 ---------- // - /** - * 分割字符串 - * - * @param str - * 要分割的字符串 - * @param spilit_sign - * 字符串的分割标志 - * @return 分割后得到的字符串数组 - */ - public static String[] stringSpilit(String str, String spilit_sign) { - String[] spilit_string = str.split(spilit_sign); - if (spilit_string[0].equals("")) { - String[] new_string = new String[spilit_string.length - 1]; - for (int i = 1; i < spilit_string.length; i++) - new_string[i - 1] = spilit_string[i]; - return new_string; - } else - return spilit_string; - } - - /** - * 用特殊的字符连接字符串 - * - * @param strings - * 要连接的字符串数组 - * @param spilit_sign - * 连接字符 - * @return 连接字符串 - */ - public static String stringConnect(String[] strings, String spilit_sign) { - StringBuffer str = new StringBuffer(""); - for (int i = 0; i < strings.length; i++) { - str.append(strings[i]).append(spilit_sign); - } - return str.toString(); - } - - /** - * 四舍五入 返回int类型 - * - * @param dSource - * 2342.45 - * @return 2342 - */ - public static int getRound(double dSource) { - int iRound; - // BigDecimal的构造函数参数类型是double - BigDecimal deSource = new BigDecimal(dSource); - // deSource.setScale(0,BigDecimal.ROUND_HALF_UP) 返回值类型 BigDecimal - // intValue() 方法将BigDecimal转化为int - iRound = deSource.setScale(0, BigDecimal.ROUND_HALF_UP).intValue(); - return iRound; - } - - /** - * 提供小数位四舍五入处理。 - * - * @param s - * 需要四舍五入的数字 - * @return 四舍五入后的结果 - */ - public static double round(double s) { - BigDecimal b = new BigDecimal(Double.toString(s)); - BigDecimal one = new BigDecimal("1"); - return b.divide(one, BigDecimal.ROUND_HALF_UP).doubleValue(); - } - - /** - * 提供小数位四舍五入处理。 - * - * @param s - * 需要四舍五入的数字 - * @return 四舍五入后的结果 - */ - public static long roundlong(double s) { - BigDecimal b = new BigDecimal(Double.toString(s)); - BigDecimal one = new BigDecimal("1"); - return b.divide(one, BigDecimal.ROUND_HALF_UP).longValue(); - } - - /** - * 得到一个字符串的长度,显示的长度,一个汉字或日韩文长度为2,英文字符长度为1 - * - * @param String - * s ,需要得到长度的字符串 - * @return int, 得到的字符串长度 - */ - public static int length(String s) { - if (s == null) - return 0; - char[] c = s.toCharArray(); - int len = 0; - for (int i = 0; i < c.length; i++) { - len++; - if (!isLetter(c[i])) { - len++; - } - } - return len; - } - - /** - * 功能:获得配置文件中指定编码字符串 - * - * @param str - * 解码字符串 charset 指定编码 - * - */ - public static String getStrByCharset(String str, String charset) throws UnsupportedEncodingException { - return new String(str.getBytes("ISO-8859-1"), charset); - } - - /** - * 提取字符串中的中文字符 - * - * @param str - * @return - */ - public static String getChineseByStr(String str) { - - StringBuilder resultStr = new StringBuilder(""); - - Pattern pcn = Pattern.compile("[\u4e00-\u9fa5]"); - Matcher m = pcn.matcher(str); - while (m.find()) { - resultStr.append(m.group().toString()); - } - - return resultStr.toString(); - } - - // 将两位小数的字符串*100 - public static String parseStrInt(String strDouble) { - String fen = ""; - int dotIndex = strDouble.lastIndexOf("."); - if (dotIndex >= 0) {// 有小数点字符串 - String zs = strDouble.substring(0, dotIndex); - if (!isNumber(zs)) { - return null; - } - fen = zs; - if (strDouble.substring(dotIndex).length() > 1) {// 有小数部分 - String xs = strDouble.substring(dotIndex).substring(1); - if (!isNumber(xs)) { - return null; - } - if (xs.length() >= 3) {// 截取后面部分 - xs = xs.substring(0, 2); - } else { - if (xs.length() < 2) { - xs = xs + "0"; - } - } - fen = zs + xs; - } else {// 没有小数 - return null; - } - - } else {// 无小数点字符串 - fen = strDouble + "00"; - } - return fen; - } - - /** - * 检查浮点数 - * - * @param num - * @param type - * "0+":非负浮点数 "+":正浮点数 "-0":非正浮点数 "-":负浮点数 "":浮点数 - * @return - */ - public static boolean checkFloat(String num, String type) { - String eL = ""; - if (type.equals("0+")) - eL = "^\\d+(\\.\\d+)?$";// 非负浮点数 - else if (type.equals("+")) - eL = "^((\\d+\\.\\d*[1-9]\\d*)|(\\d*[1-9]\\d*\\.\\d+)|(\\d*[1-9]\\d*))$";// 正浮点数 - else if (type.equals("-0")) - eL = "^((-\\d+(\\.\\d+)?)|(0+(\\.0+)?))$";// 非正浮点数 - else if (type.equals("-")) - eL = "^(-((\\d+\\.\\d*[1-9]\\d*)|(\\d*[1-9]\\d*\\.\\d+)|(\\d*[1-9]\\d*)))$";// 负浮点数 - else - eL = "^(-?\\d+)(\\.\\d+)?$";// 浮点数 - Pattern p = Pattern.compile(eL); - Matcher m = p.matcher(num); - boolean b = m.matches(); - return b; - } - - /** - * 替换字符串 - * - * @param inString - * @param oldPattern - * @param newPattern - * @return - */ - public static String replace(String inString, String oldPattern, String newPattern) { - if (isEmpty(inString) || isEmpty(oldPattern) || newPattern == null) { - return inString; - } - StringBuilder sb = new StringBuilder(); - int pos = 0; // our position in the old string - int index = inString.indexOf(oldPattern); - // the index of an occurrence we've found, or -1 - int patLen = oldPattern.length(); - while (index >= 0) { - sb.append(inString.substring(pos, index)); - sb.append(newPattern); - pos = index + patLen; - index = inString.indexOf(oldPattern, pos); - } - sb.append(inString.substring(pos)); - // remember to append any characters to the right of a match - return sb.toString(); - } - - public static String deleteAny(String inString, String charsToDelete) { - if (isEmpty(inString) || isEmpty(charsToDelete)) { - return inString; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < inString.length(); i++) { - char c = inString.charAt(i); - if (charsToDelete.indexOf(c) == -1) { - sb.append(c); - } - } - return sb.toString(); - } - - /** - * 获取文件名称 - * - * @param path - * @return - */ - public static String getFilename(String path) { - if (path == null) { - return null; - } - int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); - return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path); - } - - public static String applyRelativePath(String path, String relativePath) { - int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); - if (separatorIndex != -1) { - String newPath = path.substring(0, separatorIndex); - if (!relativePath.startsWith(FOLDER_SEPARATOR)) { - newPath += FOLDER_SEPARATOR; - } - return newPath + relativePath; - } else { - return relativePath; - } - } - - public static String cleanPath(String path) { - if (path == null) { - return null; - } - String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR); - - // Strip prefix from path to analyze, to not treat it as part of the - // first path element. This is necessary to correctly parse paths like - // "file:core/../core/io/Resource.class", where the ".." should just - // strip the first "core" directory while keeping the "file:" prefix. - int prefixIndex = pathToUse.indexOf(":"); - String prefix = ""; - if (prefixIndex != -1) { - prefix = pathToUse.substring(0, prefixIndex + 1); - pathToUse = pathToUse.substring(prefixIndex + 1); - } - if (pathToUse.startsWith(FOLDER_SEPARATOR)) { - prefix = prefix + FOLDER_SEPARATOR; - pathToUse = pathToUse.substring(1); - } - - String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR); - List pathElements = new LinkedList(); - int tops = 0; - - for (int i = pathArray.length - 1; i >= 0; i--) { - String element = pathArray[i]; - if (CURRENT_PATH.equals(element)) { - // Points to current directory - drop it. - } else if (TOP_PATH.equals(element)) { - // Registering top path found. - tops++; - } else { - if (tops > 0) { - // Merging path element with element corresponding to top path. - tops--; - } else { - // Normal path element found. - pathElements.add(0, element); - } - } - } - - // Remaining top paths need to be retained. - for (int i = 0; i < tops; i++) { - pathElements.add(0, TOP_PATH); - } - - return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR); - } - - public static String[] toStringArray(Collection collection) { - if (collection == null) { - return null; - } - return collection.toArray(new String[collection.size()]); - } - - public static String[] delimitedListToStringArray(String str, String delimiter) { - return delimitedListToStringArray(str, delimiter, null); - } - - public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) { - if (str == null) { - return new String[0]; - } - if (delimiter == null) { - return new String[] {str}; - } - List result = new ArrayList(); - if ("".equals(delimiter)) { - for (int i = 0; i < str.length(); i++) { - result.add(deleteAny(str.substring(i, i + 1), charsToDelete)); - } - } else { - int pos = 0; - int delPos; - while ((delPos = str.indexOf(delimiter, pos)) != -1) { - result.add(deleteAny(str.substring(pos, delPos), charsToDelete)); - pos = delPos + delimiter.length(); - } - if (str.length() > 0 && pos <= str.length()) { - // Add rest of String, but not in case of empty input. - result.add(deleteAny(str.substring(pos), charsToDelete)); - } - } - return toStringArray(result); - } - - public static String collectionToDelimitedString(Collection coll, String delim, String prefix, String suffix) { - if (CollectionKit.isEmpty(coll)) { - return ""; - } - StringBuilder sb = new StringBuilder(); - Iterator it = coll.iterator(); - while (it.hasNext()) { - sb.append(prefix).append(it.next()).append(suffix); - if (it.hasNext()) { - sb.append(delim); - } - } - return sb.toString(); - } - - public static String collectionToDelimitedString(Collection coll, String delim) { - return collectionToDelimitedString(coll, delim, "", ""); - } - - public static String firstUpperCase(String str) { - return str.substring(0, 1).toUpperCase() + str.substring(1); - } - - public static String join(final String[] array, final String separator) { - if (array == null) { - return null; - } - final int noOfItems = array.length; - if (noOfItems <= 0) { - return null; - } - if (noOfItems == 1) { - return array[0].toString(); - } - final StringBuilder buf = new StringBuilder(noOfItems * 16); - for (int i = 0; i < noOfItems; i++) { - buf.append(separator); - if (array[i] != null) { - buf.append(array[i]); - } - } - return buf.toString(); - } - - public static String join(final Object[] array, final String separator) { - if (array == null) { - return null; - } - final int noOfItems = array.length; - if (noOfItems <= 0) { - return null; - } - if (noOfItems == 1) { - return array[0].toString(); - } - final StringBuilder buf = new StringBuilder(noOfItems * 16); - for (int i = 0; i < noOfItems; i++) { - buf.append(separator); - if (array[i] != null) { - buf.append(array[i]); - } - } - return buf.toString(); - } - - public static String join(final List array, final String separator) { - if (array == null) { - return null; - } - final int noOfItems = array.size(); - if (noOfItems <= 0) { - return null; - } - if (noOfItems == 1) { - return array.get(0).toString(); - } - final StringBuilder buf = new StringBuilder(noOfItems * 16); - for (int i = 0; i < noOfItems; i++) { - buf.append(separator); - if (array.get(i) != null) { - buf.append(array.get(i)); - } - } - return buf.toString(); - } - - public static String join(String... parts) { - StringBuilder sb = new StringBuilder(parts.length); - for (String part : parts) { - sb.append(part); - } - return sb.toString(); - } - - public static String join(Iterable elements, String separator) { - if (elements == null) { - return ""; - } - return join(elements.iterator(), separator); - } - - public static String join(Iterator elements, String separator) { - if (elements == null) { - return ""; - } - StringBuilder sb = new StringBuilder(); - while (elements.hasNext()) { - Object o = elements.next(); - if (sb.length() > 0 && separator != null) { - sb.append(separator); - } - sb.append(o); - } - return sb.toString(); - } - - /** - * 随机获取UUID字符串(无中划线) - * - * @return UUID字符串 - */ - public static String getUUID() { - String uuid = UUID.randomUUID().toString(); - return uuid.substring(0, 8) + uuid.substring(9, 13) + uuid.substring(14, 18) + uuid.substring(19, 23) - + uuid.substring(24); - } - - /** - * 随机获取字符串 - * - * @param length 随机字符串长度 - * @return 随机字符串 - */ - public static String random(int length) { - if (length <= 0) { - return ""; - } - char[] randomChar = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', - 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm' }; - Random random = new Random(); - StringBuffer stringBuffer = new StringBuffer(); - for (int i = 0; i < length; i++) { - stringBuffer.append(randomChar[Math.abs(random.nextInt()) % randomChar.length]); - } - return stringBuffer.toString(); - } - - /** - * 根据指定长度 分隔字符串 - * - * @param str 需要处理的字符串 - * @param length 分隔长度 - * @return 字符串集合 - */ - public static List split(String str, int length) { - List list = new ArrayList(); - for (int i = 0; i < str.length(); i += length) { - int endIndex = i + length; - if (endIndex <= str.length()) { - list.add(str.substring(i, i + length)); - } else { - list.add(str.substring(i, str.length() - 1)); - } - } - return list; - } - - /** - * 将字符串按空白字符分割。 - * - *

- * 分隔符不会出现在目标数组中,连续的分隔符就被看作一个。如果字符串为null,则返回null。 - * - *

-     * StringUtil.split(null)       = null
-     * StringUtil.split("")         = []
-     * StringUtil.split("abc def")  = ["abc", "def"]
-     * StringUtil.split("abc  def") = ["abc", "def"]
-     * StringUtil.split(" abc ")    = ["abc"]
-     * 
- * - *

- * - * @param str 要分割的字符串 - * - * @return 分割后的字符串数组,如果原字符串为null,则返回null - */ - public static String[] split(String str) { - return split(str, null, -1); - } - - /** - * 将String to long list - * - * @param source - * @param token - * @return - */ - public static List parseStringToLongList(String source, String token) { - - if (isBlank(source) || isEmpty(token)) { - return null; - } - - List result = new ArrayList(); - String[] units = source.split(token); - for (String unit : units) { - result.add(Long.valueOf(unit)); - } - - return result; - } - - /** - * Splits a string in several parts (tokens) that are separated by delimiter. Delimiter is always surrounded - * by two strings! If there is no content between two delimiters, empty string will be returned for that token. - * Therefore, the length of the returned array will always be: #delimiters + 1. - *

- * Method is much, much faster then regexp String.split(), and a bit faster then - * StringTokenizer. - * - * @param src string to split - * @param delimiter split delimiter - * - * @return array of split strings - */ - public static String[] splitNoCompress(String src, String delimiter) { - if (src == null || delimiter == null) { - return null; - } - int maxparts = (src.length() / delimiter.length()) + 2; // one more for - // the last - int[] positions = new int[maxparts]; - int dellen = delimiter.length(); - - int i, j = 0; - int count = 0; - positions[0] = -dellen; - while ((i = src.indexOf(delimiter, j)) != -1) { - count++; - positions[count] = i; - j = i + dellen; - } - count++; - positions[count] = src.length(); - - String[] result = new String[count]; - - for (i = 0; i < count; i++) { - result[i] = src.substring(positions[i] + dellen, positions[i + 1]); - } - return result; - } - - public static String[] splitc(String src, String d) { - if (isAnyEmpty(src, d)) { - return new String[] { src }; - } - char[] delimiters = d.toCharArray(); - char[] srcc = src.toCharArray(); - - int maxparts = srcc.length + 1; - int[] start = new int[maxparts]; - int[] end = new int[maxparts]; - - int count = 0; - - start[0] = 0; - int s = 0, e; - if (CharKit.equalsOne(srcc[0], delimiters)) { // string starts with - // delimiter - end[0] = 0; - count++; - s = CharKit.findFirstDiff(srcc, 1, delimiters); - if (s == -1) { // nothing after delimiters - return new String[] { "", "" }; - } - start[1] = s; // new start - } - while (true) { - // find new end - e = CharKit.findFirstEqual(srcc, s, delimiters); - if (e == -1) { - end[count] = srcc.length; - break; - } - end[count] = e; - // find new start - count++; - s = CharKit.findFirstDiff(srcc, e, delimiters); - if (s == -1) { - start[count] = end[count] = srcc.length; - break; - } - start[count] = s; - } - count++; - String[] result = new String[count]; - for (int i = 0; i < count; i++) { - result[i] = src.substring(start[i], end[i]); - } - return result; - } - - public static String[] splitc(String src, char delimiter) { - if (isEmpty(src)) { - return new String[] { "" }; - } - char[] srcc = src.toCharArray(); - - int maxparts = srcc.length + 1; - int[] start = new int[maxparts]; - int[] end = new int[maxparts]; - - int count = 0; - - start[0] = 0; - int s = 0, e; - if (srcc[0] == delimiter) { // string starts with delimiter - end[0] = 0; - count++; - s = CharKit.findFirstDiff(srcc, 1, delimiter); - if (s == -1) { // nothing after delimiters - return new String[] { "", "" }; - } - start[1] = s; // new start - } - while (true) { - // find new end - e = CharKit.findFirstEqual(srcc, s, delimiter); - if (e == -1) { - end[count] = srcc.length; - break; - } - end[count] = e; - // find new start - count++; - s = CharKit.findFirstDiff(srcc, e, delimiter); - if (s == -1) { - start[count] = end[count] = srcc.length; - break; - } - start[count] = s; - } - count++; - String[] result = new String[count]; - for (int i = 0; i < count; i++) { - result[i] = src.substring(start[i], end[i]); - } - return result; - } - - public static String[] splitc(String src, char[] delimiters) { - if (isEmpty(src) || null == delimiters || delimiters.length == 0) { - return new String[] { src }; - } - char[] srcc = src.toCharArray(); - - int maxparts = srcc.length + 1; - int[] start = new int[maxparts]; - int[] end = new int[maxparts]; - - int count = 0; - - start[0] = 0; - int s = 0, e; - if (CharKit.equalsOne(srcc[0], delimiters) == true) { // string start - // with - // delimiter - end[0] = 0; - count++; - s = CharKit.findFirstDiff(srcc, 1, delimiters); - if (s == -1) { // nothing after delimiters - return new String[] { "", "" }; - } - start[1] = s; // new start - } - while (true) { - // find new end - e = CharKit.findFirstEqual(srcc, s, delimiters); - if (e == -1) { - end[count] = srcc.length; - break; - } - end[count] = e; - - // find new start - count++; - s = CharKit.findFirstDiff(srcc, e, delimiters); - if (s == -1) { - start[count] = end[count] = srcc.length; - break; - } - start[count] = s; - } - count++; - String[] result = new String[count]; - for (int i = 0; i < count; i++) { - result[i] = src.substring(start[i], end[i]); - } - return result; - } - - /** - * 将字符串按指定字符分割。 - * - *

- * 分隔符不会出现在目标数组中,连续的分隔符就被看作一个。如果字符串为null,则返回null。 - * - *

-     * StringUtil.split(null, *)         = null
-     * StringUtil.split("", *)           = []
-     * StringUtil.split("a.b.c", '.')    = ["a", "b", "c"]
-     * StringUtil.split("a..b.c", '.')   = ["a", "b", "c"]
-     * StringUtil.split("a:b:c", '.')    = ["a:b:c"]
-     * StringUtil.split("a b c", ' ')    = ["a", "b", "c"]
-     * 
- * - *

- * - * @param str 要分割的字符串 - * @param separatorChar 分隔符 - * - * @return 分割后的字符串数组,如果原字符串为null,则返回null - */ - public static String[] split(String str, char separatorChar) { - if (str == null) { - return null; - } - - int length = str.length(); - - if (length == 0) { - return Emptys.EMPTY_STRING_ARRAY; - } - - List list = CollectionKit.createArrayList(); - int i = 0; - int start = 0; - boolean match = false; - - while (i < length) { - if (str.charAt(i) == separatorChar) { - if (match) { - list.add(str.substring(start, i)); - match = false; - } - - start = ++i; - continue; - } - - match = true; - i++; - } - - if (match) { - list.add(str.substring(start, i)); - } - - return list.toArray(new String[list.size()]); - } - - /** - * 将字符串按指定字符分割。 - * - *

- * 分隔符不会出现在目标数组中,连续的分隔符就被看作一个。如果字符串为null,则返回null。 - * - *

-     * StringUtil.split(null, *)                = null
-     * StringUtil.split("", *)                  = []
-     * StringUtil.split("abc def", null)        = ["abc", "def"]
-     * StringUtil.split("abc def", " ")         = ["abc", "def"]
-     * StringUtil.split("abc  def", " ")        = ["abc", "def"]
-     * StringUtil.split(" ab:  cd::ef  ", ":")  = ["ab", "cd", "ef"]
-     * StringUtil.split("abc.def", "")          = ["abc.def"]
-     * 
- * - *

- * - * @param str 要分割的字符串 - * @param separatorChars 分隔符 - * - * @return 分割后的字符串数组,如果原字符串为null,则返回null - */ - public static String[] split(String str, String separatorChars) { - return split(str, separatorChars, -1); - } - - /** - * 将字符串按指定字符分割。 - * - *

- * 分隔符不会出现在目标数组中,连续的分隔符就被看作一个。如果字符串为null,则返回null。 - * - *

-     * StringUtil.split(null, *, *)                 = null
-     * StringUtil.split("", *, *)                   = []
-     * StringUtil.split("ab cd ef", null, 0)        = ["ab", "cd", "ef"]
-     * StringUtil.split("  ab   cd ef  ", null, 0)  = ["ab", "cd", "ef"]
-     * StringUtil.split("ab:cd::ef", ":", 0)        = ["ab", "cd", "ef"]
-     * StringUtil.split("ab:cd:ef", ":", 2)         = ["ab", "cdef"]
-     * StringUtil.split("abc.def", "", 2)           = ["abc.def"]
-     * 
- * - *

- * - * @param str 要分割的字符串 - * @param separatorChars 分隔符 - * @param max 返回的数组的最大个数,如果小于等于0,则表示无限制 - * - * @return 分割后的字符串数组,如果原字符串为null,则返回null - */ - public static String[] split(String str, String separatorChars, int max) { - if (str == null) { - return null; - } - - int length = str.length(); - - if (length == 0) { - return Emptys.EMPTY_STRING_ARRAY; - } - - List list = CollectionKit.createArrayList(); - int sizePlus1 = 1; - int i = 0; - int start = 0; - boolean match = false; - - if (separatorChars == null) { - // null表示使用空白作为分隔符 - while (i < length) { - if (Character.isWhitespace(str.charAt(i))) { - if (match) { - if (sizePlus1++ == max) { - i = length; - } - - list.add(str.substring(start, i)); - match = false; - } - - start = ++i; - continue; - } - - match = true; - i++; - } - } else if (separatorChars.length() == 1) { - // 优化分隔符长度为1的情形 - char sep = separatorChars.charAt(0); - - while (i < length) { - if (str.charAt(i) == sep) { - if (match) { - if (sizePlus1++ == max) { - i = length; - } - - list.add(str.substring(start, i)); - match = false; - } - - start = ++i; - continue; - } - - match = true; - i++; - } - } else { - // 一般情形 - while (i < length) { - if (separatorChars.indexOf(str.charAt(i)) >= 0) { - if (match) { - if (sizePlus1++ == max) { - i = length; - } - - list.add(str.substring(start, i)); - match = false; - } - - start = ++i; - continue; - } - - match = true; - i++; - } - } - - if (match) { - list.add(str.substring(start, i)); - } - - return list.toArray(new String[list.size()]); - } - - /** - * 将字符串List转化为字符串,以分隔符间隔. - * - * @param list 需要处理的List. - * @param separator 分隔符. - * @return 转化后的字符串 - */ - public static String toString(List list, String separator) { - StringBuffer stringBuffer = new StringBuffer(); - for (String str : list) { - stringBuffer.append(separator + str); - } - stringBuffer.deleteCharAt(0); - return stringBuffer.toString(); - } - - public static String toString(Object value) { - if (value == null) { - return null; - } - return value.toString(); - } - - public static String toString(Collection collection) { - return toString(collection, " "); - } - - public static String toString(Collection collection, String split) { - if (collection == null || split == null) { - return null; - } - - StringBuilder builder = new StringBuilder(); - for (Object object : collection) { - builder.append(object).append(split); - } - - builder.setLength(builder.length() - split.length()); - return builder.toString(); - } - - -} +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Random; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 有关字符串处理的工具类。 + * + * @author biezhi + * @since 1.0 + */ +public abstract class StringKit { + + private static final String FOLDER_SEPARATOR = "/"; + private static final String WINDOWS_FOLDER_SEPARATOR = "\\"; + private static final String TOP_PATH = ".."; + private static final String CURRENT_PATH = "."; + public static String[] NUMBER = { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "百", "千", "万", "亿" }; + public static final String CREATE = "create"; + public static final String DELETE = "delete"; + public static final String SAVE = "save"; + public static final String UPDATE = "update"; + public static final String QUERY = "query"; + public static final String ERROR = "error"; + public static final String SUCCESS = "success"; + public static final String FAILED = "failed"; + public static final String IP = "strIp"; + public static final String ANSWER = "strAnswer"; + public static final String LOGIN = "login"; + public static final String INDEX = "index"; + public static final String HOME = "home"; + public static final String NORIGHT = "noRight"; + public static final String BOSSIP = "strBOSSIp"; + public static final String BOSSIPS = "BOSSIPS";// 配置文件参数 + public static final String BOSSANSWER = "BOSSANSWER";// 配置文件参数 + private static final String RANDOM_CHAR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + /** + * 检查字符串是否为null或空字符串""。 + * + *
+     * StringUtil.isEmpty(null)      = true
+     * StringUtil.isEmpty("")        = true
+     * StringUtil.isEmpty(" ")       = false
+     * StringUtil.isEmpty("bob")     = false
+     * StringUtil.isEmpty("  bob  ") = false
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果为空, 则返回true + */ + public static boolean isEmpty(String str) { + return (str == null || str.length() == 0) ? true : false; + } + + public static boolean isAnyEmpty(String...strings) { + if (strings == null) { + return true; + } + for (String string : strings) { + if (isEmpty(string)) { + return true; + } + } + return false; + } + + /** + * 检查字符串是否不是null和空字符串""。 + * + *
+     * StringUtil.isEmpty(null)      = false
+     * StringUtil.isEmpty("")        = false
+     * StringUtil.isEmpty(" ")       = true
+     * StringUtil.isEmpty("bob")     = true
+     * StringUtil.isEmpty("  bob  ") = true
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果不为空, 则返回true + */ + public static boolean isNotEmpty(String str) { + return !isEmpty(str); + } + + /** + * 检查字符串是否是空白:null、空字符串""或只有空白字符。 + * + *
+     * StringUtil.isBlank(null)      = true
+     * StringUtil.isBlank("")        = true
+     * StringUtil.isBlank(" ")       = true
+     * StringUtil.isBlank("bob")     = false
+     * StringUtil.isBlank("  bob  ") = false
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果为空白, 则返回true + */ + public static boolean isBlank(String str) { + int length; + + if ((str == null) || ((length = str.length()) == 0)) { + return true; + } + + for (int i = 0; i < length; i++) { + if (!Character.isWhitespace(str.charAt(i))) { + return false; + } + } + + return true; + } + + public static boolean isAllBlank(String...strings) { + if (strings == null) { + return true; + } + for (String string : strings) { + if (isNotBlank(string)) { + return false; + } + } + return true; + } + + public static boolean isAnyBlank(String...strings) { + if (strings == null) { + return true; + } + for (String string : strings) { + if (isBlank(string)) { + return true; + } + } + return false; + } + + /** + * 检查字符串是否不是空白:null、空字符串""或只有空白字符。 + * + *
+     * StringUtil.isBlank(null)      = false
+     * StringUtil.isBlank("")        = false
+     * StringUtil.isBlank(" ")       = false
+     * StringUtil.isBlank("bob")     = true
+     * StringUtil.isBlank("  bob  ") = true
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果为空白, 则返回true + */ + public static boolean isNotBlank(String str) { + int length; + + if ((str == null) || ((length = str.length()) == 0)) { + return false; + } + + for (int i = 0; i < length; i++) { + if (!Character.isWhitespace(str.charAt(i))) { + return true; + } + } + + return false; + } + + /** + * 如果字符串是null,则返回指定默认字符串,否则返回字符串本身。 + * + *
+     * StringUtil.defaultIfNull(null, "default")  = "default"
+     * StringUtil.defaultIfNull("", "default")    = ""
+     * StringUtil.defaultIfNull("  ", "default")  = "  "
+     * StringUtil.defaultIfNull("bat", "default") = "bat"
+     * 
+ * + * @param str 要转换的字符串 + * @param defaultStr 默认字符串 + * + * @return 字符串本身或指定的默认字符串 + */ + public static String defaultIfNull(String str) { + return (str == null) ? "" : str; + } + + /** + * 如果字符串是null,则返回指定默认字符串,否则返回字符串本身。 + * + *
+     * StringUtil.defaultIfNull(null, "default")  = "default"
+     * StringUtil.defaultIfNull("", "default")    = ""
+     * StringUtil.defaultIfNull("  ", "default")  = "  "
+     * StringUtil.defaultIfNull("bat", "default") = "bat"
+     * 
+ * + * @param str 要转换的字符串 + * @param defaultStr 默认字符串 + * + * @return 字符串本身或指定的默认字符串 + */ + public static String defaultIfNull(String str, String defaultStr) { + return (str == null) ? defaultStr : str; + } + + /** + * 除去字符串头尾部的空白,如果字符串是null,依然返回null。 + * + *

+ * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 + * + *

+     * StringUtil.trim(null)          = null
+     * StringUtil.trim("")            = ""
+     * StringUtil.trim("     ")       = ""
+     * StringUtil.trim("abc")         = "abc"
+     * StringUtil.trim("    abc    ") = "abc"
+     * 
+ * + *

+ * + * @param str 要处理的字符串 + * + * @return 除去空白的字符串,如果原字串为null,则返回null + */ + public static String trim(String str) { + return trim(str, null, 0); + } + + /** + * Trims array of strings. null array elements are ignored. + */ + public static void trimAll(String[] strings) { + if (strings == null) { + return; + } + for (int i = 0; i < strings.length; i++) { + String string = strings[i]; + if (string != null) { + strings[i] = trim(string); + } + } + } + + /** + * 除去字符串头尾部的指定字符,如果字符串是null,依然返回null。 + * + *
+     * StringUtil.trim(null, *)          = null
+     * StringUtil.trim("", *)            = ""
+     * StringUtil.trim("abc", null)      = "abc"
+     * StringUtil.trim("  abc", null)    = "abc"
+     * StringUtil.trim("abc  ", null)    = "abc"
+     * StringUtil.trim(" abc ", null)    = "abc"
+     * StringUtil.trim("  abcyx", "xyz") = "  abc"
+     * 
+ * + * @param str 要处理的字符串 + * @param stripChars 要除去的字符,如果为null表示除去空白字符 + * + * @return 除去指定字符后的的字符串,如果原字串为null,则返回null + */ + public static String trim(String str, String stripChars) { + return trim(str, stripChars, 0); + } + + /** + * 除去字符串头部的空白,如果字符串是null,则返回null。 + * + *

+ * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 + * + *

+     * StringUtil.trimStart(null)         = null
+     * StringUtil.trimStart("")           = ""
+     * StringUtil.trimStart("abc")        = "abc"
+     * StringUtil.trimStart("  abc")      = "abc"
+     * StringUtil.trimStart("abc  ")      = "abc  "
+     * StringUtil.trimStart(" abc ")      = "abc "
+     * 
+ * + *

+ * + * @param str 要处理的字符串 + * + * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null + */ + public static String trimStart(String str) { + return trim(str, null, -1); + } + + /** + * 除去字符串头部的指定字符,如果字符串是null,依然返回null。 + * + *
+     * StringUtil.trimStart(null, *)          = null
+     * StringUtil.trimStart("", *)            = ""
+     * StringUtil.trimStart("abc", "")        = "abc"
+     * StringUtil.trimStart("abc", null)      = "abc"
+     * StringUtil.trimStart("  abc", null)    = "abc"
+     * StringUtil.trimStart("abc  ", null)    = "abc  "
+     * StringUtil.trimStart(" abc ", null)    = "abc "
+     * StringUtil.trimStart("yxabc  ", "xyz") = "abc  "
+     * 
+ * + * @param str 要处理的字符串 + * @param stripChars 要除去的字符,如果为null表示除去空白字符 + * + * @return 除去指定字符后的的字符串,如果原字串为null,则返回null + */ + public static String trimStart(String str, String stripChars) { + return trim(str, stripChars, -1); + } + + /** + * 除去字符串尾部的空白,如果字符串是null,则返回null。 + * + *

+ * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 + * + *

+     * StringUtil.trimEnd(null)       = null
+     * StringUtil.trimEnd("")         = ""
+     * StringUtil.trimEnd("abc")      = "abc"
+     * StringUtil.trimEnd("  abc")    = "  abc"
+     * StringUtil.trimEnd("abc  ")    = "abc"
+     * StringUtil.trimEnd(" abc ")    = " abc"
+     * 
+ * + *

+ * + * @param str 要处理的字符串 + * + * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null + */ + public static String trimEnd(String str) { + return trim(str, null, 1); + } + + /** + * 除去字符串尾部的指定字符,如果字符串是null,依然返回null。 + * + *
+     * StringUtil.trimEnd(null, *)          = null
+     * StringUtil.trimEnd("", *)            = ""
+     * StringUtil.trimEnd("abc", "")        = "abc"
+     * StringUtil.trimEnd("abc", null)      = "abc"
+     * StringUtil.trimEnd("  abc", null)    = "  abc"
+     * StringUtil.trimEnd("abc  ", null)    = "abc"
+     * StringUtil.trimEnd(" abc ", null)    = " abc"
+     * StringUtil.trimEnd("  abcyx", "xyz") = "  abc"
+     * 
+ * + * @param str 要处理的字符串 + * @param stripChars 要除去的字符,如果为null表示除去空白字符 + * + * @return 除去指定字符后的的字符串,如果原字串为null,则返回null + */ + public static String trimEnd(String str, String stripChars) { + return trim(str, stripChars, 1); + } + + /** + * 除去字符串头尾部的空白,如果结果字符串是空字符串"",则返回null。 + * + *

+ * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 + * + *

+     * StringUtil.trimToNull(null)          = null
+     * StringUtil.trimToNull("")            = null
+     * StringUtil.trimToNull("     ")       = null
+     * StringUtil.trimToNull("abc")         = "abc"
+     * StringUtil.trimToNull("    abc    ") = "abc"
+     * 
+ * + *

+ * + * @param str 要处理的字符串 + * + * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null + */ + public static String trimToNull(String str) { + return trimToNull(str, null); + } + + /** + * 除去字符串头尾部的空白,如果结果字符串是空字符串"",则返回null。 + * + *

+ * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 + * + *

+     * StringUtil.trim(null, *)          = null
+     * StringUtil.trim("", *)            = null
+     * StringUtil.trim("abc", null)      = "abc"
+     * StringUtil.trim("  abc", null)    = "abc"
+     * StringUtil.trim("abc  ", null)    = "abc"
+     * StringUtil.trim(" abc ", null)    = "abc"
+     * StringUtil.trim("  abcyx", "xyz") = "  abc"
+     * 
+ * + *

+ * + * @param str 要处理的字符串 + * @param stripChars 要除去的字符,如果为null表示除去空白字符 + * + * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null + */ + public static String trimToNull(String str, String stripChars) { + String result = trim(str, stripChars); + + if ((result == null) || (result.length() == 0)) { + return null; + } + + return result; + } + + /** + * 除去字符串头尾部的空白,如果字符串是null,则返回空字符串""。 + * + *

+ * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 + * + *

+     * StringUtil.trimToEmpty(null)          = ""
+     * StringUtil.trimToEmpty("")            = ""
+     * StringUtil.trimToEmpty("     ")       = ""
+     * StringUtil.trimToEmpty("abc")         = "abc"
+     * StringUtil.trimToEmpty("    abc    ") = "abc"
+     * 
+ * + *

+ * + * @param str 要处理的字符串 + * + * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null + */ + public static String trimToEmpty(String str) { + return trimToEmpty(str, null); + } + + /** + * 除去字符串头尾部的空白,如果字符串是null,则返回空字符串""。 + * + *

+ * 注意,和String.trim不同,此方法使用Character.isWhitespace 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 + * + *

+     * StringUtil.trim(null, *)          = ""
+     * StringUtil.trim("", *)            = ""
+     * StringUtil.trim("abc", null)      = "abc"
+     * StringUtil.trim("  abc", null)    = "abc"
+     * StringUtil.trim("abc  ", null)    = "abc"
+     * StringUtil.trim(" abc ", null)    = "abc"
+     * StringUtil.trim("  abcyx", "xyz") = "  abc"
+     * 
+ * + *

+ * + * @param str 要处理的字符串 + * + * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null + */ + public static String trimToEmpty(String str, String stripChars) { + String result = trim(str, stripChars); + + if (result == null) { + return ""; + } + return result; + } + + /** + * 除去字符串头尾部的指定字符,如果字符串是null,依然返回null。 + * + *
+     * StringUtil.trim(null, *)          = null
+     * StringUtil.trim("", *)            = ""
+     * StringUtil.trim("abc", null)      = "abc"
+     * StringUtil.trim("  abc", null)    = "abc"
+     * StringUtil.trim("abc  ", null)    = "abc"
+     * StringUtil.trim(" abc ", null)    = "abc"
+     * StringUtil.trim("  abcyx", "xyz") = "  abc"
+     * 
+ * + * @param str 要处理的字符串 + * @param stripChars 要除去的字符,如果为null表示除去空白字符 + * @param mode -1表示trimStart,0表示trim全部, 1表示trimEnd + * + * @return 除去指定字符后的的字符串,如果原字串为null,则返回null + */ + private static String trim(String str, String stripChars, int mode) { + if (str == null) { + return null; + } + + int length = str.length(); + int start = 0; + int end = length; + + // 扫描字符串头部 + if (mode <= 0) { + if (stripChars == null) { + while ((start < end) && (Character.isWhitespace(str.charAt(start)))) { + start++; + } + } else if (stripChars.length() == 0) { + return str; + } else { + while ((start < end) && (str.startsWith(stripChars) && stripChars.indexOf(str.charAt(start)) != -1)) { + start++; + } + } + } + + // 扫描字符串尾部 + if (mode >= 0) { + if (stripChars == null) { + while ((start < end) && (Character.isWhitespace(str.charAt(end - 1)))) { + end--; + } + } else if (stripChars.length() == 0) { + return str; + } else { + while ((start < end) && (str.endsWith(stripChars) && stripChars.indexOf(str.charAt(end - 1)) != -1)) { + end--; + } + } + } + + if ((start > 0) || (end < length)) { + return str.substring(start, end); + } + + return str; + } + + /* + * ========================================================================== == + */ + /* 比较函数。 */ + /* */ + /* 以下方法用来比较两个字符串是否相同。 */ + /* + * ========================================================================== == + */ + + /** + * 比较两个字符串(大小写敏感)。 + * + *
+     * StringUtil.equals(null, null)   = true
+     * StringUtil.equals(null, "abc")  = false
+     * StringUtil.equals("abc", null)  = false
+     * StringUtil.equals("abc", "abc") = true
+     * StringUtil.equals("abc", "ABC") = false
+     * 
+ * + * @param str1 要比较的字符串1 + * @param str2 要比较的字符串2 + * + * @return 如果两个字符串相同,或者都是null,则返回true + */ + public static boolean equals(String str1, String str2) { + if (str1 == null) { + return str2 == null; + } + + return str1.equals(str2); + } + + /** + * 比较两个字符串(大小写不敏感)。 + * + *
+     * StringUtil.equalsIgnoreCase(null, null)   = true
+     * StringUtil.equalsIgnoreCase(null, "abc")  = false
+     * StringUtil.equalsIgnoreCase("abc", null)  = false
+     * StringUtil.equalsIgnoreCase("abc", "abc") = true
+     * StringUtil.equalsIgnoreCase("abc", "ABC") = true
+     * 
+ * + * @param str1 要比较的字符串1 + * @param str2 要比较的字符串2 + * + * @return 如果两个字符串相同,或者都是null,则返回true + */ + public static boolean equalsIgnoreCase(String str1, String str2) { + if (str1 == null) { + return str2 == null; + } + + return str1.equalsIgnoreCase(str2); + } + + /** + * Compares string with at least one from the provided array. If at least one equal string is found, returns its + * index. Otherwise, -1 is returned. + */ + public static int equalsOne(String src, String[] dest) { + if (src == null || dest == null) { + return -1; + } + + for (int i = 0; i < dest.length; i++) { + if (src.equals(dest[i])) { + return i; + } + } + return -1; + } + + /** + * Compares string with at least one from the provided array, ignoring case. If at least one equal string is found, + * it returns its index. Otherwise, -1 is returned. + */ + public static int equalsOneIgnoreCase(String src, String[] dest) { + if (src == null || dest == null) { + return -1; + } + + for (int i = 0; i < dest.length; i++) { + if (src.equalsIgnoreCase(dest[i])) { + return i; + } + } + return -1; + } + + /** + * Compares two string arrays. + * + * @param as first string array + * @param as1 second string array + * + * @return true if all array elements matches + */ + public static boolean equalsIgnoreCase(String as[], String as1[]) { + if (as == null && as1 == null) { + return true; + } + if (as == null || as1 == null) { + return false; + } + if (as.length != as1.length) { + return false; + } + for (int i = 0; i < as.length; i++) { + if (!as[i].equalsIgnoreCase(as1[i])) { + return false; + } + } + return true; + } + + /* + * ========================================================================== == + */ + /* 字符串类型判定函数。 */ + /* */ + /* 判定字符串的类型是否为:字母、数字、空白等 */ + /* + * ========================================================================== == + */ + + /** + * 判断字符串是否只包含unicode字母。 + * + *

+ * null将返回false,空字符串""将返回 true。 + *

+ * + *
+     * StringUtil.isAlpha(null)   = false
+     * StringUtil.isAlpha("")     = true
+     * StringUtil.isAlpha("  ")   = false
+     * StringUtil.isAlpha("abc")  = true
+     * StringUtil.isAlpha("ab2c") = false
+     * StringUtil.isAlpha("ab-c") = false
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果字符串非null并且全由unicode字母组成,则返回true + */ + public static boolean isAlpha(String str) { + if (str == null) { + return false; + } + + int length = str.length(); + + for (int i = 0; i < length; i++) { + if (!Character.isLetter(str.charAt(i))) { + return false; + } + } + + return true; + } + + /** + * 判断字符串是否只包含unicode字母和空格' '。 + * + *

+ * null将返回false,空字符串""将返回 true。 + *

+ * + *
+     * StringUtil.isAlphaSpace(null)   = false
+     * StringUtil.isAlphaSpace("")     = true
+     * StringUtil.isAlphaSpace("  ")   = true
+     * StringUtil.isAlphaSpace("abc")  = true
+     * StringUtil.isAlphaSpace("ab c") = true
+     * StringUtil.isAlphaSpace("ab2c") = false
+     * StringUtil.isAlphaSpace("ab-c") = false
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果字符串非null并且全由unicode字母和空格组成,则返回true + */ + public static boolean isAlphaSpace(String str) { + if (str == null) { + return false; + } + + int length = str.length(); + + for (int i = 0; i < length; i++) { + if (!Character.isLetter(str.charAt(i)) && (str.charAt(i) != ' ')) { + return false; + } + } + + return true; + } + + /** + * 判断字符串是否只包含unicode字母和数字。 + * + *

+ * null将返回false,空字符串""将返回 true。 + *

+ * + *
+     * StringUtil.isAlphanumeric(null)   = false
+     * StringUtil.isAlphanumeric("")     = true
+     * StringUtil.isAlphanumeric("  ")   = false
+     * StringUtil.isAlphanumeric("abc")  = true
+     * StringUtil.isAlphanumeric("ab c") = false
+     * StringUtil.isAlphanumeric("ab2c") = true
+     * StringUtil.isAlphanumeric("ab-c") = false
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果字符串非null并且全由unicode字母数字组成,则返回true + */ + public static boolean isAlphanumeric(String str) { + if (str == null) { + return false; + } + + int length = str.length(); + + for (int i = 0; i < length; i++) { + if (!Character.isLetterOrDigit(str.charAt(i))) { + return false; + } + } + + return true; + } + + /** + * 判断字符串是否只包含unicode字母数字和空格' '。 + * + *

+ * null将返回false,空字符串""将返回 true。 + *

+ * + *
+     * StringUtil.isAlphanumericSpace(null)   = false
+     * StringUtil.isAlphanumericSpace("")     = true
+     * StringUtil.isAlphanumericSpace("  ")   = true
+     * StringUtil.isAlphanumericSpace("abc")  = true
+     * StringUtil.isAlphanumericSpace("ab c") = true
+     * StringUtil.isAlphanumericSpace("ab2c") = true
+     * StringUtil.isAlphanumericSpace("ab-c") = false
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果字符串非null并且全由unicode字母数字和空格组成,则返回true + */ + public static boolean isAlphanumericSpace(String str) { + if (str == null) { + return false; + } + + int length = str.length(); + + for (int i = 0; i < length; i++) { + if (!Character.isLetterOrDigit(str.charAt(i)) && (str.charAt(i) != ' ')) { + return false; + } + } + + return true; + } + + /** + * 判断字符串是否只包含unicode数字。 + * + *

+ * null将返回false,空字符串""将返回 true。 + *

+ * + *
+     * StringUtil.isNumeric(null)   = false
+     * StringUtil.isNumeric("")     = true
+     * StringUtil.isNumeric("  ")   = false
+     * StringUtil.isNumeric("123")  = true
+     * StringUtil.isNumeric("12 3") = false
+     * StringUtil.isNumeric("ab2c") = false
+     * StringUtil.isNumeric("12-3") = false
+     * StringUtil.isNumeric("12.3") = false
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果字符串非null并且全由unicode数字组成,则返回true + */ + public static boolean isNumeric(String str) { + if (str == null) { + return false; + } + + int length = str.length(); + + for (int i = 0; i < length; i++) { + if (!Character.isDigit(str.charAt(i))) { + return false; + } + } + + return true; + } + + /** + * 判断字符串是否只包含unicode数字和空格' '。 + * + *

+ * null将返回false,空字符串""将返回 true。 + *

+ * + *
+     * StringUtil.isNumericSpace(null)   = false
+     * StringUtil.isNumericSpace("")     = true
+     * StringUtil.isNumericSpace("  ")   = true
+     * StringUtil.isNumericSpace("123")  = true
+     * StringUtil.isNumericSpace("12 3") = true
+     * StringUtil.isNumericSpace("ab2c") = false
+     * StringUtil.isNumericSpace("12-3") = false
+     * StringUtil.isNumericSpace("12.3") = false
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果字符串非null并且全由unicode数字和空格组成,则返回true + */ + public static boolean isNumericSpace(String str) { + if (str == null) { + return false; + } + + int length = str.length(); + + for (int i = 0; i < length; i++) { + if (!Character.isDigit(str.charAt(i)) && (str.charAt(i) != ' ')) { + return false; + } + } + + return true; + } + + /** + * 判断字符串是否只包含unicode空白。 + * + *

+ * null将返回false,空字符串""将返回 true。 + *

+ * + *
+     * StringUtil.isWhitespace(null)   = false
+     * StringUtil.isWhitespace("")     = true
+     * StringUtil.isWhitespace("  ")   = true
+     * StringUtil.isWhitespace("abc")  = false
+     * StringUtil.isWhitespace("ab2c") = false
+     * StringUtil.isWhitespace("ab-c") = false
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果字符串非null并且全由unicode空白组成,则返回true + */ + public static boolean isWhitespace(String str) { + if (str == null) { + return false; + } + int length = str.length(); + for (int i = 0; i < length; i++) { + if (!Character.isWhitespace(str.charAt(i))) { + return false; + } + } + return true; + } + + /* + * ========================================================================== == + */ + /* 大小写转换。 */ + /* + * ========================================================================== == + */ + + /** + * 将字符串转换成大写。 + * + *

+ * 如果字符串是null则返回null。 + * + *

+     * StringUtil.toUpperCasing(null)  = null
+     * StringUtil.toUpperCasing("")    = ""
+     * StringUtil.toUpperCasing("aBc") = "ABC"
+     * 
+ * + *

+ * + * @param str 要转换的字符串 + * + * @return 大写字符串,如果原字符串为null,则返回null + */ + public static String toUpperCase(String str) { + if (str == null) { + return null; + } + + return str.toUpperCase(); + } + + /** + * 将字符串转换成小写。 + * + *

+ * 如果字符串是null则返回null。 + * + *

+     * StringUtil.toLowerCasing(null)  = null
+     * StringUtil.toLowerCasing("")    = ""
+     * StringUtil.toLowerCasing("aBc") = "abc"
+     * 
+ * + *

+ * + * @param str 要转换的字符串 + * + * @return 大写字符串,如果原字符串为null,则返回null + */ + public static String toLowerCase(String str) { + if (str == null) { + return null; + } + + return str.toLowerCase(); + } + + /** + * 将字符串的首字符转成大写(Character.toTitleCase),其它字符不变。 + * + *

+ * 如果字符串是null则返回null。 + * + *

+     * StringUtil.capitalize(null)  = null
+     * StringUtil.capitalize("")    = ""
+     * StringUtil.capitalize("cat") = "Cat"
+     * StringUtil.capitalize("cAt") = "CAt"
+     * 
+ * + *

+ * + * @param str 要转换的字符串 + * + * @return 首字符为大写的字符串,如果原字符串为null,则返回null + */ + public static String capitalize(String str) { + int strLen; + + if ((str == null) || ((strLen = str.length()) == 0)) { + return str; + } + + return new StringBuilder(strLen).append(Character.toTitleCase(str.charAt(0))).append(str.substring(1)) + .toString(); + } + + /** + * 将字符串的首字符转成小写,其它字符不变。 + * + *

+ * 如果字符串是null则返回null。 + * + *

+     * StringUtil.uncapitalize(null)  = null
+     * StringUtil.uncapitalize("")    = ""
+     * StringUtil.uncapitalize("Cat") = "cat"
+     * StringUtil.uncapitalize("CAT") = "cAT"
+     * 
+ * + *

+ * + * @param str 要转换的字符串 + * + * @return 首字符为小写的字符串,如果原字符串为null,则返回null + */ + public static String uncapitalize(String str) { + int strLen; + + if ((str == null) || ((strLen = str.length()) == 0)) { + return str; + } + + return new StringBuilder(strLen).append(Character.toLowerCase(str.charAt(0))).append(str.substring(1)) + .toString(); + } + + /** + * 与 {@link #uncapitalize(String)}不同,连续大写字符将不做改变 + * + *

+ * 如果字符串是null则返回null。 + * + *

+     * StringUtil.uncapitalize(null)  = null
+     * StringUtil.uncapitalize("")    = ""
+     * StringUtil.uncapitalize("Cat") = "cat"
+     * StringUtil.uncapitalize("CAT") = "CAT"
+     * 
+ * + *

+ * + * @param str 要转换的字符串 + * + * @return 首字符为小写的字符串,如果原字符串为null,则返回null + */ + public static String decapitalize(String str) { + if ((str == null) || str.length() == 0) { + return str; + } + if (str.length() > 1 && Character.isUpperCase(str.charAt(1)) && Character.isUpperCase(str.charAt(0))) { + return str; + } + + char chars[] = str.toCharArray(); + char c = chars[0]; + char modifiedChar = Character.toLowerCase(c); + if (modifiedChar == c) { + return str; + } + chars[0] = modifiedChar; + return new String(chars); + } + + /** + * 反转字符串的大小写。 + * + *

+ * 如果字符串是null则返回null。 + * + *

+     * StringUtil.swapCasing(null)                 = null
+     * StringUtil.swapCasing("")                   = ""
+     * StringUtil.swapCasing("The dog has a BONE") = "tHE DOG HAS A bone"
+     * 
+ * + *

+ * + * @param str 要转换的字符串 + * + * @return 大小写被反转的字符串,如果原字符串为null,则返回null + */ + public static String swapCase(String str) { + int strLen; + + if ((str == null) || ((strLen = str.length()) == 0)) { + return str; + } + + StringBuilder builder = new StringBuilder(strLen); + + char ch = 0; + + for (int i = 0; i < strLen; i++) { + ch = str.charAt(i); + + if (Character.isUpperCase(ch)) { + ch = Character.toLowerCase(ch); + } else if (Character.isTitleCase(ch)) { + ch = Character.toLowerCase(ch); + } else if (Character.isLowerCase(ch)) { + ch = Character.toUpperCase(ch); + } + + builder.append(ch); + } + + return builder.toString(); + } + + public static String fromCamelCase(String str, char separator) { + int strLen; + + if ((str == null) || ((strLen = str.length()) == 0)) { + return str; + } + StringBuilder result = new StringBuilder(strLen * 2); + int resultLength = 0; + boolean prevTranslated = false; + for (int i = 0; i < strLen; i++) { + char c = str.charAt(i); + if (i > 0 || c != separator) {// skip first starting separator + if (Character.isUpperCase(c)) { + if (!prevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != separator) { + result.append(separator); + resultLength++; + } + c = Character.toLowerCase(c); + prevTranslated = true; + } else { + prevTranslated = false; + } + result.append(c); + resultLength++; + } + } + return resultLength > 0 ? result.toString() : str; + } + + /** + * String2Short + * + * @param s + * @param def + * @return + */ + public static short toShort(String s, short def) { + try { + return (isEmpty(s)) ? def : Short.parseShort(s); + } catch (NumberFormatException e) { + return def; + } + } + + /** + * String2Int + * + * @param s + * @param def + * @return + */ + public static int toInt(String s, int def) { + try { + return (isEmpty(s)) ? def : Integer.parseInt(s); + } catch (NumberFormatException e) { + return def; + } + } + + /** + * String2Long + * + * @param s + * @param def + * @return + */ + public static long toLong(String s, long def) { + try { + return (isEmpty(s)) ? def : Long.parseLong(s); + } catch (NumberFormatException e) { + return def; + } + } + + /** + * String2Float + * + * @param s + * @param def + * @return + */ + public static float toFloat(String s, float def) { + try { + return (isEmpty(s)) ? def : Float.parseFloat(s); + } catch (NumberFormatException e) { + return def; + } + } + + /** + * String2Double + * + * @param s + * @param def + * @return + */ + public static double toDouble(String s, double def) { + try { + return (isEmpty(s)) ? def : Double.parseDouble(s); + } catch (NumberFormatException e) { + return def; + } + } + + /** + * String2Boolean + * + * @param s + * @param def + * @return + */ + public static boolean toBoolean(String s, boolean def) { + if (isEmpty(s)) + return def; + else { + return "true".equalsIgnoreCase(s); + } + } + + // ----------- 字符串截取 ----------- // + /** + * 截取 float/double 类型 一位小数 + * + * @param f + * @return + */ + public static String getDelFormat(Object f) { + DecimalFormat df = new DecimalFormat("0.0"); + return df.format(f); + } + + /** + * 截取 float/double 类型 两位小数 + * + * @param f + * @return + */ + public static String getDelFormat2(Object f) { + DecimalFormat df = new DecimalFormat("0.00"); + return df.format(f); + } + + /** + * 截取三位 + * + * @param f + * @return + */ + public static String getDelFormat3(Object f) { + DecimalFormat df = new DecimalFormat("0.000"); + return df.format(f); + } + + /** + * 截取字符串 先是一定的长多 多余... + * + * @param source + * 元字符串 + * @param len + * 显示长多 + * @return + */ + public static String getStringsubstr(String source, int len) { + if (null == source || "".equals(source)) { + return ""; + } + if (source.length() > len) { + return source.substring(0, len) + "..."; + } + + return source; + } + + // ----------- 随机数 ----------- // + /** + * 获得0-max的随机数 + * + * @param length + * @return String + */ + public static String getRandomNumber(int length, int max) { + Random random = new Random(); + StringBuffer buffer = new StringBuffer(); + + for (int i = 0; i < length; i++) { + buffer.append(random.nextInt(max)); + } + return buffer.toString(); + } + + /** + * 获取指定长度的随机数字组成的字符串 + * + * @param size + * @return + */ + public static String getRandomNumber(int size) { + String num = ""; + for (int i = 0; i < size; i++) { + double a = Math.random() * 9; + a = Math.ceil(a); + int randomNum = new Double(a).intValue(); + num += randomNum; + } + return num; + } + + /** + * 获取随机字符 + * + * @param size + * @return + */ + public static String getRandomChar(int size) { + String sRand = ""; + Random random = new Random();// 创建一个随机类 + for (int i = 0; i < size; i++) { + String ch = String.valueOf(RANDOM_CHAR.charAt(random.nextInt(RANDOM_CHAR.length()))); + sRand += ch; + } + return sRand; + } + + // ----------- 验证 ----------- // + /** + * 判断字符串是否为数字和有正确的值 + * + * @param str + * @return + */ + public static boolean isNumber(String str) { + // Pattern pattern=Pattern.compile("[0-9]*"); + // return pattern.matcher(str).matches(); + if (null != str && 0 != str.trim().length() && str.matches("\\d*")) { + return true; + } + + return false; + } + + public static boolean isBoolean(String value) { + if(null != value){ + String val = value.toLowerCase(); + if(val.equals("true") || val.equals("false")){ + return true; + } + } + return false; + } + + /** + * 将阿拉伯数字转为中文数字 + * + * @return + */ + public static String toChineseNumber(int number, int depth) { + if (depth < 0) + depth = 0; + if (number <= 0 && depth == 0) + return NUMBER[0]; + + String chinese = ""; + String src = number + ""; + if (src.charAt(src.length() - 1) == 'l' || src.charAt(src.length() - 1) == 'L') { + src = src.substring(0, src.length() - 1); + } + + if (src.length() > 4) + chinese = toChineseNumber(Integer.parseInt(src.substring(0, src.length() - 4)), depth + 1) + + toChineseNumber(Integer.parseInt(src.substring(src.length() - 4, src.length())), depth - 1); + else { + char prv = 0; + for (int i = 0; i < src.length(); i++) { + switch (src.charAt(i)) { + case '0': + if (prv != '0') + chinese = chinese + NUMBER[0]; + break; + case '1': + chinese = chinese + NUMBER[1]; + break; + case '2': + chinese = chinese + NUMBER[2]; + break; + case '3': + chinese = chinese + NUMBER[3]; + break; + case '4': + chinese = chinese + NUMBER[4]; + break; + case '5': + chinese = chinese + NUMBER[5]; + break; + case '6': + chinese = chinese + NUMBER[6]; + break; + case '7': + chinese = chinese + NUMBER[7]; + break; + case '8': + chinese = chinese + NUMBER[8]; + break; + case '9': + chinese = chinese + NUMBER[9]; + break; + } + prv = src.charAt(i); + + switch (src.length() - 1 - i) { + case 1:// 十 + if (prv != '0') + chinese = chinese + NUMBER[10]; + break; + case 2:// 百 + if (prv != '0') + chinese = chinese + NUMBER[11]; + break; + case 3:// 千 + if (prv != '0') + chinese = chinese + NUMBER[12]; + break; + } + } + } + while (chinese.length() > 0 && chinese.lastIndexOf(NUMBER[0]) == chinese.length() - 1) + chinese = chinese.substring(0, chinese.length() - 1); + if (depth == 1) + chinese += NUMBER[13]; + if (depth == 2) + chinese += NUMBER[14]; + return chinese; + } + + /** + * 验证字符串是否含有特殊字符和中文 + * + * @param + * + * @return + */ + public static boolean checkIsEnglish(String s) { + String Letters = "(){}[]\",.<>\\/~!@#$%^&*;': "; + int i; + int c; + + if (s.charAt(0) == '-') { + return false; + } + if (s.charAt(s.length() - 1) == '-') { + return false; + } + + for (i = 0; i < s.length(); i++) { + c = s.charAt(i); + if (Letters.indexOf(c) > -1) { + return false; + } + } + + // 验证是否刚好匹配 + boolean yesorno = isChineseStr(s); + if (yesorno) { + return false; + } + return true; + } + + public static boolean isChineseStr(String pValue) { + for (int i = 0; i < pValue.length(); i++) { + if ((int) pValue.charAt(i) > 256) + return true; + } + return false; + } + + /** + * 判断一个字符是Ascill字符还是其它字符(如汉,日,韩文字符) + * + * @param char c, 需要判断的字符 + * @return boolean, 返回true,Ascill字符 + */ + public static boolean isLetter(char c) { + int k = 0x80; + return c / k == 0 ? true : false; + } + + + // ----------- 格式化 ---------- // + /** + * 格式化数据 + * + * @param decima + * l3453454 + * @return 3,453,454 + */ + public final static String FormatDecimalString(String decimal) { + double dValue = Double.valueOf(decimal).doubleValue(); + DecimalFormat df = new DecimalFormat(); + String positivePattern = " ,000"; + String negativePattern = " ,000"; + if (dValue < 0) { + df.applyPattern(negativePattern); + return df.format(dValue).replace(',', ','); + } else { + df.applyPattern(positivePattern); + return df.format(dValue).replace(',', ','); + } + } + + /** + * 格式化数据 + * + * @param source + * 3453454 + * @return 3,453,454 + */ + public static String getNumberFormat(long source) { + NumberFormat usFormat = NumberFormat.getIntegerInstance(Locale.US); + return usFormat.format(source); + } + + // ----------- 过滤 ---------- // + /** + * 过滤字符串里的的特殊字符 + * + * @param str + * 要过滤的字符串 + * @return 过滤后的字符串 + */ + public static String stringFilter(String str) { + // 过滤通过页面表单提交的字符 + String[][] FilterChars = { { "<", "<" }, { ">", ">" }, { " ", " " }, { "\"", """ }, { "&", "&" }, + { "/", "/" }, { "\\", "\" }, { "'", "\\'" }, { "%", "%" } }; + + String[] str_arr = stringSpilit(str, ""); + + for (int i = 0; i < str_arr.length; i++) { + for (int j = 0; j < FilterChars.length; j++) { + if (FilterChars[j][0].equals(str_arr[i])) + str_arr[i] = FilterChars[j][1]; + } + } + return (stringConnect(str_arr, "")).trim(); + } + + // 关健字过滤 + public static String stringKeyWorldFilter(String str) { + // 过滤通过页面表单提交的字符 + String[][] FilterChars = { { "<", "" }, { ">", "" }, { "\"", "" }, { "&", "" }, { "/", "" }, { "\\", "" }, { "'", "" }, + { "%", "" } }; + + String[] str_arr = stringSpilit(str, ""); + + for (int i = 0; i < str_arr.length; i++) { + for (int j = 0; j < FilterChars.length; j++) { + if (FilterChars[j][0].equals(str_arr[i])) + str_arr[i] = FilterChars[j][1]; + } + } + return (stringConnect(str_arr, "")).trim(); + } + + public static String escapeJavaScript(String value) { + return escapeJava(value); + } + + public static String unescapeJavaScript(String value) { + return unescapeJava(value); + } + + public static String escapeJava(String value) { + if (value == null || value.length() == 0) { + return value; + } + int len = value.length(); + StringBuilder buf = null; + for (int i = 0; i < len; i++) { + char ch = value.charAt(i); + String rep; + switch (ch) { + case '\\': + rep = "\\\\"; + break; + case '\"': + rep = "\\\""; + break; + case '\'': + rep = "\\\'"; + break; + case '\t': + rep = "\\t"; + break; + case '\n': + rep = "\\n"; + break; + case '\r': + rep = "\\r"; + break; + case '\b': + rep = "\\b"; + break; + case '\f': + rep = "\\f"; + break; + default: + rep = null; + break; + } + if (rep != null) { + if (buf == null) { + buf = new StringBuilder(len * 2); + if (i > 0) { + buf.append(value.substring(0, i)); + } + } + buf.append(rep); + } else { + if (buf != null) { + buf.append(ch); + } + } + } + if (buf != null) { + return buf.toString(); + } + return value; + } + + public static String unescapeJava(String value) { + if (value == null || value.length() == 0) { + return value; + } + StringBuilder buf = null; + int len = value.length(); + int len1 = len - 1; + for (int i = 0; i < len; i++) { + char ch = value.charAt(i); + if (ch == '\\' && i < len1) { + int j = i; + i++; + ch = value.charAt(i); + switch (ch) { + case '\\': + ch = '\\'; + break; + case '\"': + ch = '\"'; + break; + case '\'': + ch = '\''; + break; + case 't': + ch = '\t'; + break; + case 'n': + ch = '\n'; + break; + case 'r': + ch = '\r'; + break; + case 'b': + ch = '\b'; + break; + case 'f': + ch = '\f'; + break; + case 'u': + ch = (char) Integer.parseInt(value.substring(i + 1, i + 5), 16); + i = i + 4; + break; + default: + j--; + } + if (buf == null) { + buf = new StringBuilder(len); + if (j > 0) { + buf.append(value.substring(0, j)); + } + } + buf.append(ch); + } else if (buf != null) { + buf.append(ch); + } + } + if (buf != null) { + return buf.toString(); + } + return value; + } + + // ----------- 切割合并 ---------- // + /** + * 分割字符串 + * + * @param str + * 要分割的字符串 + * @param spilit_sign + * 字符串的分割标志 + * @return 分割后得到的字符串数组 + */ + public static String[] stringSpilit(String str, String spilit_sign) { + String[] spilit_string = str.split(spilit_sign); + if (spilit_string[0].equals("")) { + String[] new_string = new String[spilit_string.length - 1]; + for (int i = 1; i < spilit_string.length; i++) + new_string[i - 1] = spilit_string[i]; + return new_string; + } else + return spilit_string; + } + + /** + * 用特殊的字符连接字符串 + * + * @param strings + * 要连接的字符串数组 + * @param spilit_sign + * 连接字符 + * @return 连接字符串 + */ + public static String stringConnect(String[] strings, String spilit_sign) { + StringBuffer str = new StringBuffer(""); + for (int i = 0; i < strings.length; i++) { + str.append(strings[i]).append(spilit_sign); + } + return str.toString(); + } + + /** + * 四舍五入 返回int类型 + * + * @param dSource + * 2342.45 + * @return 2342 + */ + public static int getRound(double dSource) { + int iRound; + // BigDecimal的构造函数参数类型是double + BigDecimal deSource = new BigDecimal(dSource); + // deSource.setScale(0,BigDecimal.ROUND_HALF_UP) 返回值类型 BigDecimal + // intValue() 方法将BigDecimal转化为int + iRound = deSource.setScale(0, BigDecimal.ROUND_HALF_UP).intValue(); + return iRound; + } + + /** + * 提供小数位四舍五入处理。 + * + * @param s + * 需要四舍五入的数字 + * @return 四舍五入后的结果 + */ + public static double round(double s) { + BigDecimal b = new BigDecimal(Double.toString(s)); + BigDecimal one = new BigDecimal("1"); + return b.divide(one, BigDecimal.ROUND_HALF_UP).doubleValue(); + } + + /** + * 提供小数位四舍五入处理。 + * + * @param s + * 需要四舍五入的数字 + * @return 四舍五入后的结果 + */ + public static long roundlong(double s) { + BigDecimal b = new BigDecimal(Double.toString(s)); + BigDecimal one = new BigDecimal("1"); + return b.divide(one, BigDecimal.ROUND_HALF_UP).longValue(); + } + + /** + * 得到一个字符串的长度,显示的长度,一个汉字或日韩文长度为2,英文字符长度为1 + * + * @param String + * s ,需要得到长度的字符串 + * @return int, 得到的字符串长度 + */ + public static int length(String s) { + if (s == null) + return 0; + char[] c = s.toCharArray(); + int len = 0; + for (int i = 0; i < c.length; i++) { + len++; + if (!isLetter(c[i])) { + len++; + } + } + return len; + } + + /** + * 功能:获得配置文件中指定编码字符串 + * + * @param str + * 解码字符串 charset 指定编码 + * + */ + public static String getStrByCharset(String str, String charset) throws UnsupportedEncodingException { + return new String(str.getBytes("ISO-8859-1"), charset); + } + + /** + * 提取字符串中的中文字符 + * + * @param str + * @return + */ + public static String getChineseByStr(String str) { + + StringBuilder resultStr = new StringBuilder(""); + + Pattern pcn = Pattern.compile("[\u4e00-\u9fa5]"); + Matcher m = pcn.matcher(str); + while (m.find()) { + resultStr.append(m.group().toString()); + } + + return resultStr.toString(); + } + + // 将两位小数的字符串*100 + public static String parseStrInt(String strDouble) { + String fen = ""; + int dotIndex = strDouble.lastIndexOf("."); + if (dotIndex >= 0) {// 有小数点字符串 + String zs = strDouble.substring(0, dotIndex); + if (!isNumber(zs)) { + return null; + } + fen = zs; + if (strDouble.substring(dotIndex).length() > 1) {// 有小数部分 + String xs = strDouble.substring(dotIndex).substring(1); + if (!isNumber(xs)) { + return null; + } + if (xs.length() >= 3) {// 截取后面部分 + xs = xs.substring(0, 2); + } else { + if (xs.length() < 2) { + xs = xs + "0"; + } + } + fen = zs + xs; + } else {// 没有小数 + return null; + } + + } else {// 无小数点字符串 + fen = strDouble + "00"; + } + return fen; + } + + /** + * 检查浮点数 + * + * @param num + * @param type + * "0+":非负浮点数 "+":正浮点数 "-0":非正浮点数 "-":负浮点数 "":浮点数 + * @return + */ + public static boolean checkFloat(String num, String type) { + String eL = ""; + if (type.equals("0+")) + eL = "^\\d+(\\.\\d+)?$";// 非负浮点数 + else if (type.equals("+")) + eL = "^((\\d+\\.\\d*[1-9]\\d*)|(\\d*[1-9]\\d*\\.\\d+)|(\\d*[1-9]\\d*))$";// 正浮点数 + else if (type.equals("-0")) + eL = "^((-\\d+(\\.\\d+)?)|(0+(\\.0+)?))$";// 非正浮点数 + else if (type.equals("-")) + eL = "^(-((\\d+\\.\\d*[1-9]\\d*)|(\\d*[1-9]\\d*\\.\\d+)|(\\d*[1-9]\\d*)))$";// 负浮点数 + else + eL = "^(-?\\d+)(\\.\\d+)?$";// 浮点数 + Pattern p = Pattern.compile(eL); + Matcher m = p.matcher(num); + boolean b = m.matches(); + return b; + } + + /** + * 替换字符串 + * + * @param inString + * @param oldPattern + * @param newPattern + * @return + */ + public static String replace(String inString, String oldPattern, String newPattern) { + if (isEmpty(inString) || isEmpty(oldPattern) || newPattern == null) { + return inString; + } + StringBuilder sb = new StringBuilder(); + int pos = 0; // our position in the old string + int index = inString.indexOf(oldPattern); + // the index of an occurrence we've found, or -1 + int patLen = oldPattern.length(); + while (index >= 0) { + sb.append(inString.substring(pos, index)); + sb.append(newPattern); + pos = index + patLen; + index = inString.indexOf(oldPattern, pos); + } + sb.append(inString.substring(pos)); + // remember to append any characters to the right of a match + return sb.toString(); + } + + public static String deleteAny(String inString, String charsToDelete) { + if (isEmpty(inString) || isEmpty(charsToDelete)) { + return inString; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < inString.length(); i++) { + char c = inString.charAt(i); + if (charsToDelete.indexOf(c) == -1) { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * 获取文件名称 + * + * @param path + * @return + */ + public static String getFilename(String path) { + if (path == null) { + return null; + } + int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); + return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path); + } + + public static String applyRelativePath(String path, String relativePath) { + int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); + if (separatorIndex != -1) { + String newPath = path.substring(0, separatorIndex); + if (!relativePath.startsWith(FOLDER_SEPARATOR)) { + newPath += FOLDER_SEPARATOR; + } + return newPath + relativePath; + } else { + return relativePath; + } + } + + public static String cleanPath(String path) { + if (path == null) { + return null; + } + String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR); + + // Strip prefix from path to analyze, to not treat it as part of the + // first path element. This is necessary to correctly parse paths like + // "file:core/../core/io/Resource.class", where the ".." should just + // strip the first "core" directory while keeping the "file:" prefix. + int prefixIndex = pathToUse.indexOf(":"); + String prefix = ""; + if (prefixIndex != -1) { + prefix = pathToUse.substring(0, prefixIndex + 1); + pathToUse = pathToUse.substring(prefixIndex + 1); + } + if (pathToUse.startsWith(FOLDER_SEPARATOR)) { + prefix = prefix + FOLDER_SEPARATOR; + pathToUse = pathToUse.substring(1); + } + + String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR); + List pathElements = new LinkedList(); + int tops = 0; + + for (int i = pathArray.length - 1; i >= 0; i--) { + String element = pathArray[i]; + if (CURRENT_PATH.equals(element)) { + // Points to current directory - drop it. + } else if (TOP_PATH.equals(element)) { + // Registering top path found. + tops++; + } else { + if (tops > 0) { + // Merging path element with element corresponding to top path. + tops--; + } else { + // Normal path element found. + pathElements.add(0, element); + } + } + } + + // Remaining top paths need to be retained. + for (int i = 0; i < tops; i++) { + pathElements.add(0, TOP_PATH); + } + + return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR); + } + + public static String[] toStringArray(Collection collection) { + if (collection == null) { + return null; + } + return collection.toArray(new String[collection.size()]); + } + + public static String[] delimitedListToStringArray(String str, String delimiter) { + return delimitedListToStringArray(str, delimiter, null); + } + + public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) { + if (str == null) { + return new String[0]; + } + if (delimiter == null) { + return new String[] {str}; + } + List result = new ArrayList(); + if ("".equals(delimiter)) { + for (int i = 0; i < str.length(); i++) { + result.add(deleteAny(str.substring(i, i + 1), charsToDelete)); + } + } else { + int pos = 0; + int delPos; + while ((delPos = str.indexOf(delimiter, pos)) != -1) { + result.add(deleteAny(str.substring(pos, delPos), charsToDelete)); + pos = delPos + delimiter.length(); + } + if (str.length() > 0 && pos <= str.length()) { + // Add rest of String, but not in case of empty input. + result.add(deleteAny(str.substring(pos), charsToDelete)); + } + } + return toStringArray(result); + } + + public static String collectionToDelimitedString(Collection coll, String delim, String prefix, String suffix) { + if (CollectionKit.isEmpty(coll)) { + return ""; + } + StringBuilder sb = new StringBuilder(); + Iterator it = coll.iterator(); + while (it.hasNext()) { + sb.append(prefix).append(it.next()).append(suffix); + if (it.hasNext()) { + sb.append(delim); + } + } + return sb.toString(); + } + + public static String collectionToDelimitedString(Collection coll, String delim) { + return collectionToDelimitedString(coll, delim, "", ""); + } + + public static String firstUpperCase(String str) { + return str.substring(0, 1).toUpperCase() + str.substring(1); + } + + public static String join(final String[] array, final String separator) { + if (array == null) { + return null; + } + final int noOfItems = array.length; + if (noOfItems <= 0) { + return null; + } + if (noOfItems == 1) { + return array[0].toString(); + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = 0; i < noOfItems; i++) { + buf.append(separator); + if (array[i] != null) { + buf.append(array[i]); + } + } + return buf.toString(); + } + + public static String join(final Object[] array, final String separator) { + if (array == null) { + return null; + } + final int noOfItems = array.length; + if (noOfItems <= 0) { + return null; + } + if (noOfItems == 1) { + return array[0].toString(); + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = 0; i < noOfItems; i++) { + buf.append(separator); + if (array[i] != null) { + buf.append(array[i]); + } + } + return buf.toString(); + } + + public static String join(final List array, final String separator) { + if (array == null) { + return null; + } + final int noOfItems = array.size(); + if (noOfItems <= 0) { + return null; + } + if (noOfItems == 1) { + return array.get(0).toString(); + } + final StringBuilder buf = new StringBuilder(noOfItems * 16); + for (int i = 0; i < noOfItems; i++) { + buf.append(separator); + if (array.get(i) != null) { + buf.append(array.get(i)); + } + } + return buf.toString(); + } + + public static String join(String... parts) { + StringBuilder sb = new StringBuilder(parts.length); + for (String part : parts) { + sb.append(part); + } + return sb.toString(); + } + + public static String join(Iterable elements, String separator) { + if (elements == null) { + return ""; + } + return join(elements.iterator(), separator); + } + + public static String join(Iterator elements, String separator) { + if (elements == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + while (elements.hasNext()) { + Object o = elements.next(); + if (sb.length() > 0 && separator != null) { + sb.append(separator); + } + sb.append(o); + } + return sb.toString(); + } + + /** + * 随机获取UUID字符串(无中划线) + * + * @return UUID字符串 + */ + public static String getUUID() { + String uuid = UUID.randomUUID().toString(); + return uuid.substring(0, 8) + uuid.substring(9, 13) + uuid.substring(14, 18) + uuid.substring(19, 23) + + uuid.substring(24); + } + + /** + * 随机获取字符串 + * + * @param length 随机字符串长度 + * @return 随机字符串 + */ + public static String random(int length) { + if (length <= 0) { + return ""; + } + char[] randomChar = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', + 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm' }; + Random random = new Random(); + StringBuffer stringBuffer = new StringBuffer(); + for (int i = 0; i < length; i++) { + stringBuffer.append(randomChar[Math.abs(random.nextInt()) % randomChar.length]); + } + return stringBuffer.toString(); + } + + /** + * 根据指定长度 分隔字符串 + * + * @param str 需要处理的字符串 + * @param length 分隔长度 + * @return 字符串集合 + */ + public static List split(String str, int length) { + List list = new ArrayList(); + for (int i = 0; i < str.length(); i += length) { + int endIndex = i + length; + if (endIndex <= str.length()) { + list.add(str.substring(i, i + length)); + } else { + list.add(str.substring(i, str.length() - 1)); + } + } + return list; + } + + /** + * 将字符串按空白字符分割。 + * + *

+ * 分隔符不会出现在目标数组中,连续的分隔符就被看作一个。如果字符串为null,则返回null。 + * + *

+     * StringUtil.split(null)       = null
+     * StringUtil.split("")         = []
+     * StringUtil.split("abc def")  = ["abc", "def"]
+     * StringUtil.split("abc  def") = ["abc", "def"]
+     * StringUtil.split(" abc ")    = ["abc"]
+     * 
+ * + *

+ * + * @param str 要分割的字符串 + * + * @return 分割后的字符串数组,如果原字符串为null,则返回null + */ + public static String[] split(String str) { + return split(str, null, -1); + } + + /** + * 将String to long list + * + * @param source + * @param token + * @return + */ + public static List parseStringToLongList(String source, String token) { + + if (isBlank(source) || isEmpty(token)) { + return null; + } + + List result = new ArrayList(); + String[] units = source.split(token); + for (String unit : units) { + result.add(Long.valueOf(unit)); + } + + return result; + } + + /** + * Splits a string in several parts (tokens) that are separated by delimiter. Delimiter is always surrounded + * by two strings! If there is no content between two delimiters, empty string will be returned for that token. + * Therefore, the length of the returned array will always be: #delimiters + 1. + *

+ * Method is much, much faster then regexp String.split(), and a bit faster then + * StringTokenizer. + * + * @param src string to split + * @param delimiter split delimiter + * + * @return array of split strings + */ + public static String[] splitNoCompress(String src, String delimiter) { + if (src == null || delimiter == null) { + return null; + } + int maxparts = (src.length() / delimiter.length()) + 2; // one more for + // the last + int[] positions = new int[maxparts]; + int dellen = delimiter.length(); + + int i, j = 0; + int count = 0; + positions[0] = -dellen; + while ((i = src.indexOf(delimiter, j)) != -1) { + count++; + positions[count] = i; + j = i + dellen; + } + count++; + positions[count] = src.length(); + + String[] result = new String[count]; + + for (i = 0; i < count; i++) { + result[i] = src.substring(positions[i] + dellen, positions[i + 1]); + } + return result; + } + + public static String[] splitc(String src, String d) { + if (isAnyEmpty(src, d)) { + return new String[] { src }; + } + char[] delimiters = d.toCharArray(); + char[] srcc = src.toCharArray(); + + int maxparts = srcc.length + 1; + int[] start = new int[maxparts]; + int[] end = new int[maxparts]; + + int count = 0; + + start[0] = 0; + int s = 0, e; + if (CharKit.equalsOne(srcc[0], delimiters)) { // string starts with + // delimiter + end[0] = 0; + count++; + s = CharKit.findFirstDiff(srcc, 1, delimiters); + if (s == -1) { // nothing after delimiters + return new String[] { "", "" }; + } + start[1] = s; // new start + } + while (true) { + // find new end + e = CharKit.findFirstEqual(srcc, s, delimiters); + if (e == -1) { + end[count] = srcc.length; + break; + } + end[count] = e; + // find new start + count++; + s = CharKit.findFirstDiff(srcc, e, delimiters); + if (s == -1) { + start[count] = end[count] = srcc.length; + break; + } + start[count] = s; + } + count++; + String[] result = new String[count]; + for (int i = 0; i < count; i++) { + result[i] = src.substring(start[i], end[i]); + } + return result; + } + + public static String[] splitc(String src, char delimiter) { + if (isEmpty(src)) { + return new String[] { "" }; + } + char[] srcc = src.toCharArray(); + + int maxparts = srcc.length + 1; + int[] start = new int[maxparts]; + int[] end = new int[maxparts]; + + int count = 0; + + start[0] = 0; + int s = 0, e; + if (srcc[0] == delimiter) { // string starts with delimiter + end[0] = 0; + count++; + s = CharKit.findFirstDiff(srcc, 1, delimiter); + if (s == -1) { // nothing after delimiters + return new String[] { "", "" }; + } + start[1] = s; // new start + } + while (true) { + // find new end + e = CharKit.findFirstEqual(srcc, s, delimiter); + if (e == -1) { + end[count] = srcc.length; + break; + } + end[count] = e; + // find new start + count++; + s = CharKit.findFirstDiff(srcc, e, delimiter); + if (s == -1) { + start[count] = end[count] = srcc.length; + break; + } + start[count] = s; + } + count++; + String[] result = new String[count]; + for (int i = 0; i < count; i++) { + result[i] = src.substring(start[i], end[i]); + } + return result; + } + + public static String[] splitc(String src, char[] delimiters) { + if (isEmpty(src) || null == delimiters || delimiters.length == 0) { + return new String[] { src }; + } + char[] srcc = src.toCharArray(); + + int maxparts = srcc.length + 1; + int[] start = new int[maxparts]; + int[] end = new int[maxparts]; + + int count = 0; + + start[0] = 0; + int s = 0, e; + if (CharKit.equalsOne(srcc[0], delimiters) == true) { // string start + // with + // delimiter + end[0] = 0; + count++; + s = CharKit.findFirstDiff(srcc, 1, delimiters); + if (s == -1) { // nothing after delimiters + return new String[] { "", "" }; + } + start[1] = s; // new start + } + while (true) { + // find new end + e = CharKit.findFirstEqual(srcc, s, delimiters); + if (e == -1) { + end[count] = srcc.length; + break; + } + end[count] = e; + + // find new start + count++; + s = CharKit.findFirstDiff(srcc, e, delimiters); + if (s == -1) { + start[count] = end[count] = srcc.length; + break; + } + start[count] = s; + } + count++; + String[] result = new String[count]; + for (int i = 0; i < count; i++) { + result[i] = src.substring(start[i], end[i]); + } + return result; + } + + /** + * 将字符串按指定字符分割。 + * + *

+ * 分隔符不会出现在目标数组中,连续的分隔符就被看作一个。如果字符串为null,则返回null。 + * + *

+     * StringUtil.split(null, *)         = null
+     * StringUtil.split("", *)           = []
+     * StringUtil.split("a.b.c", '.')    = ["a", "b", "c"]
+     * StringUtil.split("a..b.c", '.')   = ["a", "b", "c"]
+     * StringUtil.split("a:b:c", '.')    = ["a:b:c"]
+     * StringUtil.split("a b c", ' ')    = ["a", "b", "c"]
+     * 
+ * + *

+ * + * @param str 要分割的字符串 + * @param separatorChar 分隔符 + * + * @return 分割后的字符串数组,如果原字符串为null,则返回null + */ + public static String[] split(String str, char separatorChar) { + if (str == null) { + return null; + } + + int length = str.length(); + + if (length == 0) { + return Emptys.EMPTY_STRING_ARRAY; + } + + List list = CollectionKit.createArrayList(); + int i = 0; + int start = 0; + boolean match = false; + + while (i < length) { + if (str.charAt(i) == separatorChar) { + if (match) { + list.add(str.substring(start, i)); + match = false; + } + + start = ++i; + continue; + } + + match = true; + i++; + } + + if (match) { + list.add(str.substring(start, i)); + } + + return list.toArray(new String[list.size()]); + } + + /** + * 将字符串按指定字符分割。 + * + *

+ * 分隔符不会出现在目标数组中,连续的分隔符就被看作一个。如果字符串为null,则返回null。 + * + *

+     * StringUtil.split(null, *)                = null
+     * StringUtil.split("", *)                  = []
+     * StringUtil.split("abc def", null)        = ["abc", "def"]
+     * StringUtil.split("abc def", " ")         = ["abc", "def"]
+     * StringUtil.split("abc  def", " ")        = ["abc", "def"]
+     * StringUtil.split(" ab:  cd::ef  ", ":")  = ["ab", "cd", "ef"]
+     * StringUtil.split("abc.def", "")          = ["abc.def"]
+     * 
+ * + *

+ * + * @param str 要分割的字符串 + * @param separatorChars 分隔符 + * + * @return 分割后的字符串数组,如果原字符串为null,则返回null + */ + public static String[] split(String str, String separatorChars) { + return split(str, separatorChars, -1); + } + + /** + * 将字符串按指定字符分割。 + * + *

+ * 分隔符不会出现在目标数组中,连续的分隔符就被看作一个。如果字符串为null,则返回null。 + * + *

+     * StringUtil.split(null, *, *)                 = null
+     * StringUtil.split("", *, *)                   = []
+     * StringUtil.split("ab cd ef", null, 0)        = ["ab", "cd", "ef"]
+     * StringUtil.split("  ab   cd ef  ", null, 0)  = ["ab", "cd", "ef"]
+     * StringUtil.split("ab:cd::ef", ":", 0)        = ["ab", "cd", "ef"]
+     * StringUtil.split("ab:cd:ef", ":", 2)         = ["ab", "cdef"]
+     * StringUtil.split("abc.def", "", 2)           = ["abc.def"]
+     * 
+ * + *

+ * + * @param str 要分割的字符串 + * @param separatorChars 分隔符 + * @param max 返回的数组的最大个数,如果小于等于0,则表示无限制 + * + * @return 分割后的字符串数组,如果原字符串为null,则返回null + */ + public static String[] split(String str, String separatorChars, int max) { + if (str == null) { + return null; + } + + int length = str.length(); + + if (length == 0) { + return Emptys.EMPTY_STRING_ARRAY; + } + + List list = CollectionKit.createArrayList(); + int sizePlus1 = 1; + int i = 0; + int start = 0; + boolean match = false; + + if (separatorChars == null) { + // null表示使用空白作为分隔符 + while (i < length) { + if (Character.isWhitespace(str.charAt(i))) { + if (match) { + if (sizePlus1++ == max) { + i = length; + } + + list.add(str.substring(start, i)); + match = false; + } + + start = ++i; + continue; + } + + match = true; + i++; + } + } else if (separatorChars.length() == 1) { + // 优化分隔符长度为1的情形 + char sep = separatorChars.charAt(0); + + while (i < length) { + if (str.charAt(i) == sep) { + if (match) { + if (sizePlus1++ == max) { + i = length; + } + + list.add(str.substring(start, i)); + match = false; + } + + start = ++i; + continue; + } + + match = true; + i++; + } + } else { + // 一般情形 + while (i < length) { + if (separatorChars.indexOf(str.charAt(i)) >= 0) { + if (match) { + if (sizePlus1++ == max) { + i = length; + } + + list.add(str.substring(start, i)); + match = false; + } + + start = ++i; + continue; + } + + match = true; + i++; + } + } + + if (match) { + list.add(str.substring(start, i)); + } + + return list.toArray(new String[list.size()]); + } + + /** + * Convenience method to return a String array as a CSV String. + * E.g. useful for {@code toString()} implementations. + * @param arr the array to display + * @return the delimited String + */ + public static String arrayToCommaDelimitedString(Object[] arr) { + return arrayToDelimitedString(arr, ","); + } + + /** + * Convenience method to return a String array as a delimited (e.g. CSV) + * String. E.g. useful for {@code toString()} implementations. + * @param arr the array to display + * @param delim the delimiter to use (probably a ",") + * @return the delimited String + */ + public static String arrayToDelimitedString(Object[] arr, String delim) { + if (arr == null || arr.length == 0) { + return ""; + } + + if (arr.length == 1) { + return nullSafeToString(arr[0]); + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < arr.length; i++) { + if (i > 0) { + sb.append(delim); + } + sb.append(arr[i]); + } + return sb.toString(); + } + + /** + * Determine if the given objects are equal, returning {@code true} + * if both are {@code null} or {@code false} if only one is + * {@code null}. + *

Compares arrays with {@code Arrays.equals}, performing an equality + * check based on the array elements rather than the array reference. + * @param o1 first Object to compare + * @param o2 second Object to compare + * @return whether the given objects are equal + * @see java.util.Arrays#equals + */ + public static boolean nullSafeEquals(Object o1, Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null || o2 == null) { + return false; + } + if (o1.equals(o2)) { + return true; + } + if (o1.getClass().isArray() && o2.getClass().isArray()) { + if (o1 instanceof Object[] && o2 instanceof Object[]) { + return Arrays.equals((Object[]) o1, (Object[]) o2); + } + if (o1 instanceof boolean[] && o2 instanceof boolean[]) { + return Arrays.equals((boolean[]) o1, (boolean[]) o2); + } + if (o1 instanceof byte[] && o2 instanceof byte[]) { + return Arrays.equals((byte[]) o1, (byte[]) o2); + } + if (o1 instanceof char[] && o2 instanceof char[]) { + return Arrays.equals((char[]) o1, (char[]) o2); + } + if (o1 instanceof double[] && o2 instanceof double[]) { + return Arrays.equals((double[]) o1, (double[]) o2); + } + if (o1 instanceof float[] && o2 instanceof float[]) { + return Arrays.equals((float[]) o1, (float[]) o2); + } + if (o1 instanceof int[] && o2 instanceof int[]) { + return Arrays.equals((int[]) o1, (int[]) o2); + } + if (o1 instanceof long[] && o2 instanceof long[]) { + return Arrays.equals((long[]) o1, (long[]) o2); + } + if (o1 instanceof short[] && o2 instanceof short[]) { + return Arrays.equals((short[]) o1, (short[]) o2); + } + } + return false; + } + + public static String nullSafeToString(Object obj) { + if (obj == null) { + return "null"; + } + if (obj instanceof String) { + return (String) obj; + } + if (obj instanceof Object[]) { + return nullSafeToString((Object[]) obj); + } + if (obj instanceof boolean[]) { + return nullSafeToString((boolean[]) obj); + } + if (obj instanceof byte[]) { + return nullSafeToString((byte[]) obj); + } + if (obj instanceof char[]) { + return nullSafeToString((char[]) obj); + } + if (obj instanceof double[]) { + return nullSafeToString((double[]) obj); + } + if (obj instanceof float[]) { + return nullSafeToString((float[]) obj); + } + if (obj instanceof int[]) { + return nullSafeToString((int[]) obj); + } + if (obj instanceof long[]) { + return nullSafeToString((long[]) obj); + } + if (obj instanceof short[]) { + return nullSafeToString((short[]) obj); + } + String str = obj.toString(); + return (str != null ? str : ""); + } + + /** + * 将字符串List转化为字符串,以分隔符间隔. + * + * @param list 需要处理的List. + * @param separator 分隔符. + * @return 转化后的字符串 + */ + public static String toString(List list, String separator) { + StringBuffer stringBuffer = new StringBuffer(); + for (String str : list) { + stringBuffer.append(separator + str); + } + stringBuffer.deleteCharAt(0); + return stringBuffer.toString(); + } + + public static String toString(Object value) { + if (value == null) { + return null; + } + return value.toString(); + } + + public static String toString(Collection collection) { + return toString(collection, " "); + } + + public static String toString(Collection collection, String split) { + if (collection == null || split == null) { + return null; + } + + StringBuilder builder = new StringBuilder(); + for (Object object : collection) { + builder.append(object).append(split); + } + + builder.setLength(builder.length() - split.length()); + return builder.toString(); + } + + +} diff --git a/blade-kit/src/main/java/blade/kit/SystemKit.java b/blade-kit/src/main/java/com/blade/kit/SystemKit.java similarity index 95% rename from blade-kit/src/main/java/blade/kit/SystemKit.java rename to blade-kit/src/main/java/com/blade/kit/SystemKit.java index a8b1a1fcc..886056b5d 100644 --- a/blade-kit/src/main/java/blade/kit/SystemKit.java +++ b/blade-kit/src/main/java/com/blade/kit/SystemKit.java @@ -1,4 +1,19 @@ -package blade.kit; +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; import java.io.PrintWriter; import java.net.InetAddress; @@ -1498,7 +1513,7 @@ public static final void dumpSystemInfo(PrintWriter out) { * * @return 属性值或null */ - private static String getSystemProperty(String name, boolean quiet) { + public static String getSystemProperty(String name, boolean quiet) { try { return System.getProperty(name); } catch (SecurityException e) { @@ -1506,7 +1521,6 @@ private static String getSystemProperty(String name, boolean quiet) { System.err.println("Caught a SecurityException reading the system property '" + name + "'; the SystemUtil property value will default to null."); } - return null; } } diff --git a/blade-kit/src/main/java/blade/kit/TaskKit.java b/blade-kit/src/main/java/com/blade/kit/TaskKit.java similarity index 86% rename from blade-kit/src/main/java/blade/kit/TaskKit.java rename to blade-kit/src/main/java/com/blade/kit/TaskKit.java index 220520313..c2ce77b15 100644 --- a/blade-kit/src/main/java/blade/kit/TaskKit.java +++ b/blade-kit/src/main/java/com/blade/kit/TaskKit.java @@ -1,4 +1,19 @@ -package blade.kit; +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit; import java.text.ParseException; import java.util.ArrayList; @@ -10,7 +25,8 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import blade.kit.log.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * 定时任务 @@ -20,7 +36,7 @@ */ public abstract class TaskKit { - private static Logger logger = Logger.getLogger(TaskKit.class); + private static Logger logger = LoggerFactory.getLogger(TaskKit.class); private static ScheduledThreadPoolExecutor taskScheduler = new ScheduledThreadPoolExecutor(getBestPoolSize()); private static List timerList = new ArrayList(); @@ -159,7 +175,7 @@ public static void depose() { } List awaitingExecution = taskScheduler.shutdownNow(); - logger.info("Tasks stopping. Tasks awaiting execution: %d", timerNum + awaitingExecution.size()); + logger.info("Tasks stopping. Tasks awaiting execution: {}", timerNum + awaitingExecution.size()); } /** diff --git a/blade-kit/src/main/java/com/blade/kit/TimeKit.java b/blade-kit/src/main/java/com/blade/kit/TimeKit.java new file mode 100644 index 000000000..cb192dfd5 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/TimeKit.java @@ -0,0 +1,68 @@ +package com.blade.kit; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * 时间处理类 + * + * @author biezhi + * @since 1.0 + */ +public class TimeKit { + + public static final SimpleDateFormat DEFAULT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + public static final SimpleDateFormat DATE_FORMAT_DATE = new SimpleDateFormat("yyyy-MM-dd"); + + private TimeKit() { + throw new AssertionError(); + } + + /** + * long time to string + * + * @param timeInMillis + * @param dateFormat + * @return + */ + public static String getTime(long timeInMillis, SimpleDateFormat dateFormat) { + return dateFormat.format(new Date(timeInMillis)); + } + + /** + * long time to string, format is {@link #DEFAULT_DATE_FORMAT} + * + * @param timeInMillis + * @return + */ + public static String getTime(long timeInMillis) { + return getTime(timeInMillis, DEFAULT_DATE_FORMAT); + } + + /** + * get current time in milliseconds + * + * @return + */ + public static long getCurrentTimeInLong() { + return System.currentTimeMillis(); + } + + /** + * get current time in milliseconds, format is {@link #DEFAULT_DATE_FORMAT} + * + * @return + */ + public static String getCurrentTimeInString() { + return getTime(getCurrentTimeInLong()); + } + + /** + * get current time in milliseconds + * + * @return + */ + public static String getCurrentTimeInString(SimpleDateFormat dateFormat) { + return getTime(getCurrentTimeInLong(), dateFormat); + } +} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/TimwKit.java b/blade-kit/src/main/java/com/blade/kit/TimwKit.java similarity index 74% rename from blade-kit/src/main/java/blade/kit/TimwKit.java rename to blade-kit/src/main/java/com/blade/kit/TimwKit.java index 73b5b043d..7e251d324 100644 --- a/blade-kit/src/main/java/blade/kit/TimwKit.java +++ b/blade-kit/src/main/java/com/blade/kit/TimwKit.java @@ -13,12 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.kit; +package com.blade.kit; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import blade.kit.log.Logger; /** * 计数均衡器 @@ -31,7 +34,7 @@ */ public class TimwKit { - private static final Logger LOGGER = Logger.getLogger(TimwKit.class); + private static final Logger LOGGER = LoggerFactory.getLogger(TimwKit.class); private ArrayList numList = new ArrayList(); @@ -56,16 +59,24 @@ public void clear() { * * @return */ - public Number size() { + public int size() { return numList.size(); } + public List numbers() { + return numList; + } + + public Number current() { + return numList.get(numList.size() - 1); + } + /** * 获取平均数 * * @return */ - public Number getAverage() { + public Number avg() { if (numList.size() == 0) { return 0; } else { @@ -83,7 +94,7 @@ public Number getAverage() { * @return */ public String print() { - String str = "执行(" + size() + ")次,耗时: " + numList + " ms"; + String str = "Execute count = " + size() + ", elapsed time: " + numList + " ms."; LOGGER.debug(str); return str; } @@ -94,11 +105,10 @@ public String print() { * @return */ public String printAvg() { - Number number = getAverage(); + Number number = avg(); BigDecimal b = new BigDecimal(number.doubleValue()); double avg = b.setScale(3, BigDecimal.ROUND_HALF_UP).doubleValue(); - String str = "平均耗时: " + avg + " ms"; - + String str = "average time cost: " + avg + " ms"; LOGGER.debug(str); return str; } diff --git a/blade-kit/src/main/java/com/blade/kit/XmlKit.java b/blade-kit/src/main/java/com/blade/kit/XmlKit.java new file mode 100644 index 000000000..d6ae01b1c --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/XmlKit.java @@ -0,0 +1,43 @@ +package com.blade.kit; + +import java.io.File; + +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Node; +import org.dom4j.io.SAXReader; +import org.xml.sax.SAXException; + +public class XmlKit { + + private Document document; + + public XmlKit(String filePath) { + String xmlPath = XmlKit.class.getResource(filePath).toString(); + if (xmlPath.substring(5).indexOf(":") > 0) { + // 路径中含有:分隔符,windows系统 + xmlPath = xmlPath.substring(6); + } else { + xmlPath = xmlPath.substring(5); + } + SAXReader reader = new SAXReader(); + try { + reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + this.document = reader.read(new File(xmlPath)); + } catch (SAXException e) { + e.printStackTrace(); + } catch (DocumentException e) { + e.printStackTrace(); + } + } + + public String attrValue(String strXPath) { + Node n = document.selectSingleNode(strXPath); + if (n != null) { + return (n.valueOf("@value")); + } else { + return null; + } + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/base/Config.java b/blade-kit/src/main/java/com/blade/kit/base/Config.java new file mode 100644 index 000000000..ae1136833 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/base/Config.java @@ -0,0 +1,238 @@ +package com.blade.kit.base; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.locks.Condition; + +import javax.servlet.ServletContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.blade.kit.IOKit; +import com.blade.kit.StringKit; + +public class Config { + + private static final Logger LOGGER = LoggerFactory.getLogger(Config.class); + + private final Map config; + + public Config() { + config = new HashMap(32); + } + + public Config load(Properties props) { + for (String key : props.stringPropertyNames()) { + String value = props.getProperty(key); + config.put(key, value); + } + return this; + } + + public Config load(Map map) { + config.putAll(map); + return this; + } + + /** + * 从文件路径或者classpath路径中载入配置. + * @param location - 配置文件路径 + * @return this + */ + public static Config load(String location) { + return new Config().loadLoaction(location); + } + + private Config loadLoaction(String location){ + if (location.startsWith("classpath:")) { + location = location.substring("classpath:".length()); + return loadClasspath(location); + } else if (location.startsWith("file:")) { + location = location.substring("file:".length()); + return load(new File(location)); + } else { + return loadClasspath(location); + } + } + + public void add(String location){ + Config config = loadLoaction(location); + if(null != config){ + this.config.putAll(config.asMap()); + } + } + + // 从 URL 载入 + public Config load(URL url) { + String location = url.getPath(); + try { + location = URLDecoder.decode(location, "utf-8"); + } catch (UnsupportedEncodingException e) { + } + + try { + return loadInputStream(url.openStream(), location); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + // 从 classpath 下面载入 + private Config loadClasspath(String classpath) { + if (classpath.startsWith("/")) { + classpath = classpath.substring(1); + } + InputStream is = getDefault().getResourceAsStream(classpath); + LOGGER.info("Load config [classpath:" + classpath + "]"); + return loadInputStream(is, classpath); + } + + // 从 File 载入 + public Config load(File file) { + try { + LOGGER.info("Load config [file:" + file.getPath() + "]"); + return loadInputStream(new FileInputStream(file), file.getName()); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + // 载入 web 资源文件 + public Config load(String location, ServletContext sc) { + if (location.startsWith("classpath:") || location.startsWith("file:")) { + return load(location); + } else { + if (location.startsWith("webroot:")) { + location = location.substring("webroot:".length()); + } + if (!location.startsWith("/")) { + location = "/" + location; + } + InputStream is = sc.getResourceAsStream(location); + return loadInputStream(is, location); + } + } + + private Config loadInputStream(InputStream is, String location) { + if (is == null) { + throw new IllegalStateException("InputStream not found: " + location); + } + location = location.toLowerCase(); + try { + Properties config = new Properties(); + config.load(is); + load(config); + return this; + } catch (IOException e) { + throw new IllegalStateException(e); + } finally { + IOKit.closeQuietly(is); + } + } + + public Config loadSystemProperties() { + return load(System.getProperties()); + } + + public Config loadSystemEnvs() { + return load(System.getenv()); + } + + public Map asMap(){ + return this.config; + } + + /** + * Returns current thread's context class loader + */ + private static ClassLoader getDefault() { + ClassLoader loader = null; + try { + loader = Thread.currentThread().getContextClassLoader(); + } catch (Exception e) { + } + if (loader == null) { + loader = Config.class.getClassLoader(); + if (loader == null) { + try { + // getClassLoader() returning null indicates the bootstrap ClassLoader + loader = ClassLoader.getSystemClassLoader(); + } catch (Exception e) { + // Cannot access system ClassLoader - oh well, maybe the caller can live with null... + } + } + } + return loader; + } + + public String get(String key){ + return config.get(key); + } + + public String get(String key, String defaultValue) { + return null != config.get(key) ? config.get(key) : defaultValue; + } + + public Integer getInt(String key) { + String value = get(key); + if (StringKit.isNotBlank(value)) { + return Integer.valueOf(value); + } + return null; + } + + public Integer getInt(String key, Integer defaultValue) { + return null != getInt(key) ? getInt(key) : defaultValue; + } + + public Long getLong(String key) { + String value = get(key); + if (StringKit.isNotBlank(value)) { + return Long.valueOf(value); + } + return null; + } + + public Long getLong(String key, Long defaultValue) { + return null != getLong(key) ? getLong(key) : defaultValue; + } + + public Double getDouble(String key) { + String value = get(key); + if (StringKit.isNotBlank(value)) { + return Double.valueOf(value); + } + return null; + } + + public double getDouble(String key, double defaultValue) { + return null != getDouble(key) ? getDouble(key) : defaultValue; + } + + public Boolean getBoolean(String key) { + String value = get(key); + if (StringKit.isNotBlank(value)) { + return Boolean.valueOf(value); + } + return null; + } + + public Boolean getBoolean(String key, boolean defaultValue) { + return null != getBoolean(key) ? getBoolean(key) : defaultValue; + } + + public Config put(String key, Object value){ + config.put(key, value.toString()); + return this; + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/base/ThrowableKit.java b/blade-kit/src/main/java/com/blade/kit/base/ThrowableKit.java new file mode 100644 index 000000000..364893183 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/base/ThrowableKit.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blade.kit.base; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; + +import com.blade.kit.Assert; + +/** + * Static utility methods pertaining to instances of {@link Throwable}. + * + *

+ * See the Guava User Guide entry on + * + * Throwables. + * + * @author Kevin Bourrillion + * @author Ben Yu + * @since 1.0 + */ +public final class ThrowableKit { + private ThrowableKit() { + } + + /** + * Propagates {@code throwable} exactly as-is, if and only if it is an + * instance of {@code declaredType}. Example usage: + * + *

+	 * try {
+	 * 	someMethodThatCouldThrowAnything();
+	 * } catch (IKnowWhatToDoWithThisException e) {
+	 * 	handle(e);
+	 * } catch (Throwable t) {
+	 * 	Throwables.propagateIfInstanceOf(t, IOException.class);
+	 * 	Throwables.propagateIfInstanceOf(t, SQLException.class);
+	 * 	throw Throwables.propagate(t);
+	 * }
+	 * 
+ */ + public static void propagateIfInstanceOf(Throwable throwable, Class declaredType) + throws X { + // Check for null is needed to avoid frequent JNI calls to isInstance(). + if (throwable != null && declaredType.isInstance(throwable)) { + throw declaredType.cast(throwable); + } + } + + /** + * Propagates {@code throwable} exactly as-is, if and only if it is an + * instance of {@link RuntimeException} or {@link Error}. Example usage: + * + *
+	 * try {
+	 * 	someMethodThatCouldThrowAnything();
+	 * } catch (IKnowWhatToDoWithThisException e) {
+	 * 	handle(e);
+	 * } catch (Throwable t) {
+	 * 	Throwables.propagateIfPossible(t);
+	 * 	throw new RuntimeException("unexpected", t);
+	 * }
+	 * 
+ */ + public static void propagateIfPossible(Throwable throwable) { + propagateIfInstanceOf(throwable, Error.class); + propagateIfInstanceOf(throwable, RuntimeException.class); + } + + /** + * Propagates {@code throwable} exactly as-is, if and only if it is an + * instance of {@link RuntimeException}, {@link Error}, or + * {@code declaredType}. Example usage: + * + *
+	 * try {
+	 * 	someMethodThatCouldThrowAnything();
+	 * } catch (IKnowWhatToDoWithThisException e) {
+	 * 	handle(e);
+	 * } catch (Throwable t) {
+	 * 	Throwables.propagateIfPossible(t, OtherException.class);
+	 * 	throw new RuntimeException("unexpected", t);
+	 * }
+	 * 
+ * + * @param throwable + * the Throwable to possibly propagate + * @param declaredType + * the single checked exception type declared by the calling + * method + */ + public static void propagateIfPossible(Throwable throwable, Class declaredType) throws X { + propagateIfInstanceOf(throwable, declaredType); + propagateIfPossible(throwable); + } + + /** + * Propagates {@code throwable} exactly as-is, if and only if it is an + * instance of {@link RuntimeException}, {@link Error}, + * {@code declaredType1}, or {@code declaredType2}. In the unlikely case + * that you have three or more declared checked exception types, you can + * handle them all by invoking these methods repeatedly. See usage example + * in {@link #propagateIfPossible(Throwable, Class)}. + * + * @param throwable + * the Throwable to possibly propagate + * @param declaredType1 + * any checked exception type declared by the calling method + * @param declaredType2 + * any other checked exception type declared by the calling + * method + */ + public static void propagateIfPossible(Throwable throwable, + Class declaredType1, Class declaredType2) throws X1, X2 { + Assert.notNull(declaredType2); + propagateIfInstanceOf(throwable, declaredType1); + propagateIfPossible(throwable, declaredType2); + } + + /** + * Propagates {@code throwable} as-is if it is an instance of + * {@link RuntimeException} or {@link Error}, or else as a last resort, + * wraps it in a {@code RuntimeException} then propagates. + *

+ * This method always throws an exception. The {@code RuntimeException} + * return type is only for client code to make Java type system happy in + * case a return value is required by the enclosing method. Example usage: + * + *

+	 * T doSomething() {
+	 * 	try {
+	 * 		return someMethodThatCouldThrowAnything();
+	 * 	} catch (IKnowWhatToDoWithThisException e) {
+	 * 		return handle(e);
+	 * 	} catch (Throwable t) {
+	 * 		throw Throwables.propagate(t);
+	 * 	}
+	 * }
+	 * 
+ * + * @param throwable + * the Throwable to propagate + * @return nothing will ever be returned; this return type is only for your + * convenience, as illustrated in the example above + */ + public static RuntimeException propagate(Throwable throwable) { + propagateIfPossible(Assert.checkNotNull(throwable)); + throw new RuntimeException(throwable); + } + + /** + * Returns the innermost cause of {@code throwable}. The first throwable in + * a chain provides context from when the error or exception was initially + * detected. Example usage: + * + *
+	 * assertEquals("Unable to assign a customer id", Throwables.getRootCause(e).getMessage());
+	 * 
+ */ + public static Throwable getRootCause(Throwable throwable) { + Throwable cause; + while ((cause = throwable.getCause()) != null) { + throwable = cause; + } + return throwable; + } + + /** + * Returns a string containing the result of {@link Throwable#toString() + * toString()}, followed by the full, recursive stack trace of + * {@code throwable}. Note that you probably should not be parsing the + * resulting string; if you need programmatic access to the stack frames, + * you can call {@link Throwable#getStackTrace()}. + */ + public static String getStackTraceAsString(Throwable throwable) { + StringWriter stringWriter = new StringWriter(); + throwable.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } + + public static RuntimeException unchecked(Throwable e) { + if (e instanceof RuntimeException) { + return (RuntimeException) e; + } + if (e instanceof InvocationTargetException) { + return unchecked(((InvocationTargetException) e).getTargetException()); + } + return new RuntimeException(e); + } +} diff --git a/blade-kit/src/main/java/com/blade/kit/exception/ClassReaderException.java b/blade-kit/src/main/java/com/blade/kit/exception/ClassReaderException.java new file mode 100644 index 000000000..6058d1413 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/exception/ClassReaderException.java @@ -0,0 +1,32 @@ +package com.blade.kit.exception; + +/** + * 类读取异常 + * + * @author biezhi + * @since 1.0 + */ +public class ClassReaderException extends RuntimeException { + + private static final long serialVersionUID = -1L; + + public ClassReaderException() { + super(); + } + + public ClassReaderException(Exception e) { + super(e); + } + + public ClassReaderException(String msg) { + super(msg); + } + + public ClassReaderException(String msg, Exception e) { + super(msg, e); + } + + public Throwable fillInStackTrace() { + return null; + } +} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/exception/IllegalPathException.java b/blade-kit/src/main/java/com/blade/kit/exception/IllegalPathException.java similarity index 90% rename from blade-kit/src/main/java/blade/exception/IllegalPathException.java rename to blade-kit/src/main/java/com/blade/kit/exception/IllegalPathException.java index 3c8362144..7f877e79d 100644 --- a/blade-kit/src/main/java/blade/exception/IllegalPathException.java +++ b/blade-kit/src/main/java/com/blade/kit/exception/IllegalPathException.java @@ -1,4 +1,4 @@ -package blade.exception; +package com.blade.kit.exception; /** * 代表非法的路径。 diff --git a/blade-kit/src/main/java/com/blade/kit/exception/ReflectException.java b/blade-kit/src/main/java/com/blade/kit/exception/ReflectException.java new file mode 100644 index 000000000..0a6faa5fe --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/exception/ReflectException.java @@ -0,0 +1,22 @@ +package com.blade.kit.exception; + +public class ReflectException extends RuntimeException{ + + private static final long serialVersionUID = -3979699728217399193L; + + public ReflectException() { + super(); + } + + public ReflectException(String msg) { + super(msg); + } + + public ReflectException(Throwable t) { + super(t); + } + + public ReflectException(String msg, Throwable t) { + super(msg, t); + } +} diff --git a/blade-kit/src/main/java/com/blade/kit/http/ConnectionFactory.java b/blade-kit/src/main/java/com/blade/kit/http/ConnectionFactory.java new file mode 100644 index 000000000..e12793b82 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/http/ConnectionFactory.java @@ -0,0 +1,42 @@ +package com.blade.kit.http; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.Proxy; +import java.net.URL; + +/** + * HttpURLConnection工厂 + * + * @author biezhi + * @since 1.0 + */ +public interface ConnectionFactory { + /** + * 根据URL创建一个HttpURLConnection + * + * @throws IOException + */ + HttpURLConnection create(URL url) throws IOException; + + /** + * 根据URL和代理对象创建一个HttpURLConnection + * + * @throws IOException + */ + HttpURLConnection create(URL url, Proxy proxy) throws IOException; + + /** + * 一个默认的连接工厂 + */ + ConnectionFactory DEFAULT = new ConnectionFactory() { + public HttpURLConnection create(URL url) throws IOException { + return (HttpURLConnection) url.openConnection(); + } + + public HttpURLConnection create(URL url, Proxy proxy) + throws IOException { + return (HttpURLConnection) url.openConnection(proxy); + } + }; +} \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/http/Header.java b/blade-kit/src/main/java/com/blade/kit/http/Header.java new file mode 100644 index 000000000..a375f21ab --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/http/Header.java @@ -0,0 +1,133 @@ +package com.blade.kit.http; + +/** + * 头信息常量 + * + * @author biezhi + * @since 1.0 + */ +public class Header { + + public static final String CHARSET_UTF8 = "UTF-8"; + + /** + * 表单类型头信息 + */ + public static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded"; + + /** + * json类型头信息 + */ + public static final String CONTENT_TYPE_JSON = "application/json"; + + /** + * gzip压缩 + */ + public static final String ENCODING_GZIP = "gzip"; + + /** + * Accept + */ + public static final String HEADER_ACCEPT = "Accept"; + + /** + * Accept-Charset + */ + public static final String HEADER_ACCEPT_CHARSET = "Accept-Charset"; + + /** + * Accept-Encoding + */ + public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; + + /** + * Authorization + */ + public static final String HEADER_AUTHORIZATION = "Authorization"; + + /** + * 'Cache-Control' header name + */ + public static final String HEADER_CACHE_CONTROL = "Cache-Control"; + + /** + * 'Content-Encoding' header name + */ + public static final String HEADER_CONTENT_ENCODING = "Content-Encoding"; + + /** + * 'Content-Length' header name + */ + public static final String HEADER_CONTENT_LENGTH = "Content-Length"; + + /** + * 'Content-Type' header name + */ + public static final String HEADER_CONTENT_TYPE = "Content-Type"; + + /** + * 'Cookie' header name + */ + public static final String HEADER_COOKIE = "Cookie"; + + /** + * 'Set-Cookie' header name + */ + public static final String HEADER_SET_COOKIE = "Set-Cookie"; + + /** + * 'Date' header name + */ + public static final String HEADER_DATE = "Date"; + + /** + * 'ETag' header name + */ + public static final String HEADER_ETAG = "ETag"; + + /** + * 'Expires' header name + */ + public static final String HEADER_EXPIRES = "Expires"; + + /** + * 'If-None-Match' header name + */ + public static final String HEADER_IF_NONE_MATCH = "If-None-Match"; + + /** + * 'Last-Modified' header name + */ + public static final String HEADER_LAST_MODIFIED = "Last-Modified"; + + /** + * 'Location' header name + */ + public static final String HEADER_LOCATION = "Location"; + + /** + * 'Proxy-Authorization' header name + */ + public static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization"; + + /** + * 'Referer' header name + */ + public static final String HEADER_REFERER = "Referer"; + + /** + * 'Server' header name + */ + public static final String HEADER_SERVER = "Server"; + + /** + * 'User-Agent' header name + */ + public static final String HEADER_USER_AGENT = "User-Agent"; + + /** + * 'charset' header value parameter + */ + public static final String PARAM_CHARSET = "charset"; + +} diff --git a/blade-kit/src/main/java/com/blade/kit/http/HttpRequest.java b/blade-kit/src/main/java/com/blade/kit/http/HttpRequest.java new file mode 100644 index 000000000..9228f6691 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/http/HttpRequest.java @@ -0,0 +1,2814 @@ +/* + * Copyright (c) 2014 Kevin Sawicki + * + * 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 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. + */ +package com.blade.kit.http; + +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static java.net.HttpURLConnection.HTTP_CREATED; +import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; +import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED; +import static java.net.HttpURLConnection.HTTP_NO_CONTENT; +import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.Proxy.Type.HTTP; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.Flushable; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.CharBuffer; +import java.security.AccessController; +import java.security.GeneralSecurityException; +import java.security.PrivilegedAction; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.zip.GZIPInputStream; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import com.blade.kit.Assert; +import com.blade.kit.Base64; + +/** + * Http请求类 + * + * @author biezhi + * @since 1.0 + */ +public class HttpRequest { + + private static final String BOUNDARY = "00content0boundary00"; + + private static final String CONTENT_TYPE_MULTIPART = "multipart/form-data; boundary=" + BOUNDARY; + + private static final String CRLF = "\r\n"; + + private static final String[] EMPTY_STRINGS = new String[0]; + + private static SSLSocketFactory TRUSTED_FACTORY; + + private static HostnameVerifier TRUSTED_VERIFIER; + + private static ConnectionFactory CONNECTION_FACTORY = ConnectionFactory.DEFAULT; + + public static String getValidCharset(final String charset) { + if (charset != null && charset.length() > 0) + return charset; + else + return Header.CHARSET_UTF8; + } + + /** + * @return 返回SSL套接字工厂 + * @throws HttpRequestException + */ + private static SSLSocketFactory getTrustedFactory() throws HttpRequestException { + if (TRUSTED_FACTORY == null) { + final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) { + // Intentionally left blank + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) { + // Intentionally left blank + } + } }; + try { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, trustAllCerts, new SecureRandom()); + TRUSTED_FACTORY = context.getSocketFactory(); + } catch (GeneralSecurityException e) { + IOException ioException = new IOException("Security exception configuring SSL context"); + ioException.initCause(e); + throw new HttpRequestException(ioException); + } + } + + return TRUSTED_FACTORY; + } + + private static HostnameVerifier getTrustedVerifier() { + if (TRUSTED_VERIFIER == null) + TRUSTED_VERIFIER = new HostnameVerifier() { + + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + + return TRUSTED_VERIFIER; + } + + private static StringBuilder addPathSeparator(final String baseUrl, final StringBuilder result) { + // Add trailing slash if the base URL doesn't have any path segments. + // + // The following test is checking for the last slash not being part of + // the protocol to host separator: '://'. + if (baseUrl.indexOf(':') + 2 == baseUrl.lastIndexOf('/')) + result.append('/'); + return result; + } + + private static StringBuilder addParamPrefix(final String baseUrl, final StringBuilder result) { + // Add '?' if missing and add '&' if params already exist in base url + final int queryStart = baseUrl.indexOf('?'); + final int lastChar = result.length() - 1; + if (queryStart == -1) + result.append('?'); + else if (queryStart < lastChar && baseUrl.charAt(lastChar) != '&') + result.append('&'); + return result; + } + + private static StringBuilder addParam(final Object key, Object value, final StringBuilder result) { + if (value != null && value.getClass().isArray()) + value = arrayToList(value); + + if (value instanceof Iterable) { + Iterator iterator = ((Iterable) value).iterator(); + while (iterator.hasNext()) { + result.append(key); + result.append("[]="); + Object element = iterator.next(); + if (element != null) + result.append(element); + if (iterator.hasNext()) + result.append("&"); + } + } else { + result.append(key); + result.append("="); + if (value != null) + result.append(value); + } + + return result; + } + + /** + * 设置一个ConnectionFactory,用于创建新的请求 + */ + public static void setConnectionFactory(final ConnectionFactory connectionFactory) { + if (connectionFactory == null) + CONNECTION_FACTORY = ConnectionFactory.DEFAULT; + else + CONNECTION_FACTORY = connectionFactory; + } + + /** + * 操作执行一个回调处理完成后和处理嵌套的异常 + * + * @param + */ + protected static abstract class Operation implements Callable { + + /** + * 执行操作 + * + * @return result + * @throws HttpRequestException + * @throws IOException + */ + protected abstract V run() throws HttpRequestException, IOException; + + /** + * 操作完成回调 + * + * @throws IOException + */ + protected abstract void done() throws IOException; + + public V call() throws HttpRequestException { + boolean thrown = false; + try { + return run(); + } catch (HttpRequestException e) { + thrown = true; + throw e; + } catch (IOException e) { + thrown = true; + throw new HttpRequestException(e); + } finally { + try { + done(); + } catch (IOException e) { + if (!thrown) + throw new HttpRequestException(e); + } + } + } + } + + /** + * 确保Closeable类关闭,使用适当的异常处理 + * + * @param + */ + protected static abstract class CloseOperation extends Operation { + + private final Closeable closeable; + + private final boolean ignoreCloseExceptions; + + /** + * 创建一个关闭操作 + * + * @param closeable + * @param ignoreCloseExceptions + */ + protected CloseOperation(final Closeable closeable, final boolean ignoreCloseExceptions) { + this.closeable = closeable; + this.ignoreCloseExceptions = ignoreCloseExceptions; + } + + @Override + protected void done() throws IOException { + if (closeable instanceof Flushable) + ((Flushable) closeable).flush(); + if (ignoreCloseExceptions) + try { + closeable.close(); + } catch (IOException e) { + // Ignored + } + else + closeable.close(); + } + } + + /** + * Class that and ensures a {@link Flushable} gets flushed with proper + * exception handling. + * + * @param + */ + protected static abstract class FlushOperation extends Operation { + + private final Flushable flushable; + + /** + * Create flush operation + * + * @param flushable + */ + protected FlushOperation(final Flushable flushable) { + this.flushable = flushable; + } + + @Override + protected void done() throws IOException { + flushable.flush(); + } + } + + /** + * 表示任何类型的数组的对象列表,让我们可以很容易地遍历 + * + * @param array + * of elements + * @return list with the same elements + */ + private static List arrayToList(final Object array) { + if (array instanceof Object[]) + return Arrays.asList((Object[]) array); + + List result = new ArrayList(); + // Arrays of the primitive types can't be cast to array of Object, so + // this: + if (array instanceof int[]) + for (int value : (int[]) array) + result.add(value); + else if (array instanceof boolean[]) + for (boolean value : (boolean[]) array) + result.add(value); + else if (array instanceof long[]) + for (long value : (long[]) array) + result.add(value); + else if (array instanceof float[]) + for (float value : (float[]) array) + result.add(value); + else if (array instanceof double[]) + for (double value : (double[]) array) + result.add(value); + else if (array instanceof short[]) + for (short value : (short[]) array) + result.add(value); + else if (array instanceof byte[]) + for (byte value : (byte[]) array) + result.add(value); + else if (array instanceof char[]) + for (char value : (char[]) array) + result.add(value); + return result; + } + + /** + * Encode the given URL as an ASCII {@link String} + *

+ * This method ensures the path and query segments of the URL are properly + * encoded such as ' ' characters being encoded to '%20' or any UTF-8 + * characters that are non-ASCII. No encoding of URLs is done by default by + * the {@link HttpRequest} constructors and so if URL encoding is needed + * this method should be called before calling the {@link HttpRequest} + * constructor. + * + * @param url + * @return encoded URL + * @throws HttpRequestException + */ + public static String encode(final String url) throws HttpRequestException { + URL parsed; + try { + parsed = new URL(url.toString()); + } catch (IOException e) { + throw new HttpRequestException(e); + } + + String host = parsed.getHost(); + int port = parsed.getPort(); + if (port != -1) + host = host + ':' + Integer.toString(port); + + try { + String encoded = new URI(parsed.getProtocol(), host, parsed.getPath(), parsed.getQuery(), null) + .toASCIIString(); + int paramsStart = encoded.indexOf('?'); + if (paramsStart > 0 && paramsStart + 1 < encoded.length()) + encoded = encoded.substring(0, paramsStart + 1) + + encoded.substring(paramsStart + 1).replace("+", "%2B"); + return encoded; + } catch (URISyntaxException e) { + IOException io = new IOException("Parsing URI failed"); + io.initCause(e); + throw new HttpRequestException(io); + } + } + + /** + * Append given map as query parameters to the base URL + *

+ * Each map entry's key will be a parameter name and the value's + * {@link Object#toString()} will be the parameter value. + * + * @param url + * @param params + * @return URL with appended query params + */ + public static String append(final String url, final Map params) { + final String baseUrl = url.toString(); + if (params == null || params.isEmpty()) + return baseUrl; + + final StringBuilder result = new StringBuilder(baseUrl); + + addPathSeparator(baseUrl, result); + addParamPrefix(baseUrl, result); + + Entry entry; + Iterator iterator = params.entrySet().iterator(); + entry = (Entry) iterator.next(); + addParam(entry.getKey().toString(), entry.getValue(), result); + + while (iterator.hasNext()) { + result.append('&'); + entry = (Entry) iterator.next(); + addParam(entry.getKey().toString(), entry.getValue(), result); + } + + return result.toString(); + } + + /** + * Append given name/value pairs as query parameters to the base URL + *

+ * The params argument is interpreted as a sequence of name/value pairs so + * the given number of params must be divisible by 2. + * + * @param url + * @param params + * name/value pairs + * @return URL with appended query params + */ + public static String append(final String url, final Object... params) { + final String baseUrl = url.toString(); + if (params == null || params.length == 0) + return baseUrl; + + if (params.length % 2 != 0) + throw new IllegalArgumentException("Must specify an even number of parameter names/values"); + + final StringBuilder result = new StringBuilder(baseUrl); + + addPathSeparator(baseUrl, result); + addParamPrefix(baseUrl, result); + + addParam(params[0], params[1], result); + + for (int i = 2; i < params.length; i += 2) { + result.append('&'); + addParam(params[i], params[i + 1], result); + } + + return result.toString(); + } + + /** + * Start a 'GET' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest get(final String url) throws HttpRequestException { + return new HttpRequest(url, MethodType.GET); + } + + /** + * Start a 'GET' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest get(final URL url) throws HttpRequestException { + return new HttpRequest(url, MethodType.GET); + } + + /** + * Start a 'GET' request to the given URL along with the query params + * + * @param baseUrl + * @param params + * The query parameters to include as part of the baseUrl + * @param encode + * true to encode the full URL + * + * @see #append(CharSequence, Map) + * @see #encode(CharSequence) + * + * @return request + */ + public static HttpRequest get(final String baseUrl, final Map params, final boolean encode) { + String url = append(baseUrl, params); + return get(encode ? encode(url) : url); + } + + /** + * Start a 'GET' request to the given URL along with the query params + * + * @param baseUrl + * @param encode + * true to encode the full URL + * @param params + * the name/value query parameter pairs to include as part of the + * baseUrl + * + * @see #append(CharSequence, Object...) + * @see #encode(CharSequence) + * + * @return request + */ + public static HttpRequest get(final String baseUrl, final boolean encode, final Object... params) { + String url = append(baseUrl, params); + return get(encode ? encode(url) : url); + } + + /** + * Start a 'POST' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest post(final String url) throws HttpRequestException { + return new HttpRequest(url, MethodType.POST); + } + + /** + * Start a 'POST' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest post(final URL url) throws HttpRequestException { + return new HttpRequest(url, MethodType.POST); + } + + /** + * Start a 'POST' request to the given URL along with the query params + * + * @param baseUrl + * @param params + * the query parameters to include as part of the baseUrl + * @param encode + * true to encode the full URL + * + * @see #append(String, Map) + * @see #encode(String) + * + * @return request + */ + public static HttpRequest post(final String baseUrl, final Map params, final boolean encode) { + String url = append(baseUrl, params); + return post(encode ? encode(url) : url); + } + + /** + * Start a 'POST' request to the given URL along with the query params + * + * @param baseUrl + * @param encode + * true to encode the full URL + * @param params + * the name/value query parameter pairs to include as part of the + * baseUrl + * + * @see #append(String, Object...) + * @see #encode(String) + * + * @return request + */ + public static HttpRequest post(final String baseUrl, final boolean encode, final Object... params) { + String url = append(baseUrl, params); + return post(encode ? encode(url) : url); + } + + /** + * Start a 'PUT' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest put(final String url) throws HttpRequestException { + return new HttpRequest(url, MethodType.PUT); + } + + /** + * Start a 'PUT' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest put(final URL url) throws HttpRequestException { + return new HttpRequest(url, MethodType.PUT); + } + + /** + * Start a 'PUT' request to the given URL along with the query params + * + * @param baseUrl + * @param params + * the query parameters to include as part of the baseUrl + * @param encode + * true to encode the full URL + * + * @see #append(String, Map) + * @see #encode(String) + * + * @return request + */ + public static HttpRequest put(final String baseUrl, final Map params, final boolean encode) { + String url = append(baseUrl, params); + return put(encode ? encode(url) : url); + } + + /** + * Start a 'PUT' request to the given URL along with the query params + * + * @param baseUrl + * @param encode + * true to encode the full URL + * @param params + * the name/value query parameter pairs to include as part of the + * baseUrl + * + * @see #append(String, Object...) + * @see #encode(String) + * + * @return request + */ + public static HttpRequest put(final String baseUrl, final boolean encode, final Object... params) { + String url = append(baseUrl, params); + return put(encode ? encode(url) : url); + } + + /** + * Start a 'DELETE' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest delete(final String url) throws HttpRequestException { + return new HttpRequest(url, MethodType.DELETE); + } + + /** + * Start a 'DELETE' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest delete(final URL url) throws HttpRequestException { + return new HttpRequest(url, MethodType.DELETE); + } + + /** + * Start a 'DELETE' request to the given URL along with the query params + * + * @param baseUrl + * @param params + * The query parameters to include as part of the baseUrl + * @param encode + * true to encode the full URL + * + * @see #append(String, Map) + * @see #encode(String) + * + * @return request + */ + public static HttpRequest delete(final String baseUrl, final Map params, final boolean encode) { + String url = append(baseUrl, params); + return delete(encode ? encode(url) : url); + } + + /** + * Start a 'DELETE' request to the given URL along with the query params + * + * @param baseUrl + * @param encode + * true to encode the full URL + * @param params + * the name/value query parameter pairs to include as part of the + * baseUrl + * + * @see #append(String, Object...) + * @see #encode(String) + * + * @return request + */ + public static HttpRequest delete(final String baseUrl, final boolean encode, final Object... params) { + String url = append(baseUrl, params); + return delete(encode ? encode(url) : url); + } + + /** + * Start a 'HEAD' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest head(final String url) throws HttpRequestException { + return new HttpRequest(url, MethodType.HEAD); + } + + /** + * Start a 'HEAD' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest head(final URL url) throws HttpRequestException { + return new HttpRequest(url, MethodType.HEAD); + } + + /** + * Start a 'HEAD' request to the given URL along with the query params + * + * @param baseUrl + * @param params + * The query parameters to include as part of the baseUrl + * @param encode + * true to encode the full URL + * + * @see #append(String, Map) + * @see #encode(String) + * + * @return request + */ + public static HttpRequest head(final String baseUrl, final Map params, final boolean encode) { + String url = append(baseUrl, params); + return head(encode ? encode(url) : url); + } + + /** + * Start a 'GET' request to the given URL along with the query params + * + * @param baseUrl + * @param encode + * true to encode the full URL + * @param params + * the name/value query parameter pairs to include as part of the + * baseUrl + * + * @see #append(String, Object...) + * @see #encode(String) + * + * @return request + */ + public static HttpRequest head(final String baseUrl, final boolean encode, final Object... params) { + String url = append(baseUrl, params); + return head(encode ? encode(url) : url); + } + + /** + * Start an 'OPTIONS' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest options(final String url) throws HttpRequestException { + return new HttpRequest(url, MethodType.OPTIONS); + } + + /** + * Start an 'OPTIONS' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest options(final URL url) throws HttpRequestException { + return new HttpRequest(url, MethodType.OPTIONS); + } + + /** + * Start a 'TRACE' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest trace(final String url) throws HttpRequestException { + return new HttpRequest(url, MethodType.TRACE); + } + + /** + * Start a 'TRACE' request to the given URL + * + * @param url + * @return request + * @throws HttpRequestException + */ + public static HttpRequest trace(final URL url) throws HttpRequestException { + return new HttpRequest(url, MethodType.TRACE); + } + + /** + * Set the 'http.keepAlive' property to the given value. + *

+ * This setting will apply to all requests. + * + * @param keepAlive + */ + public static void keepAlive(final boolean keepAlive) { + setProperty("http.keepAlive", Boolean.toString(keepAlive)); + } + + /** + * Set the 'http.maxConnections' property to the given value. + *

+ * This setting will apply to all requests. + * + * @param maxConnections + */ + public static void maxConnections(final int maxConnections) { + setProperty("http.maxConnections", Integer.toString(maxConnections)); + } + + /** + * Set the 'http.proxyHost' and 'https.proxyHost' properties to the given + * host value. + *

+ * This setting will apply to all requests. + * + * @param host + */ + public static void proxyHost(final String host) { + setProperty("http.proxyHost", host); + setProperty("https.proxyHost", host); + } + + /** + * Set the 'http.proxyPort' and 'https.proxyPort' properties to the given + * port number. + *

+ * This setting will apply to all requests. + * + * @param port + */ + public static void proxyPort(final int port) { + final String portValue = Integer.toString(port); + setProperty("http.proxyPort", portValue); + setProperty("https.proxyPort", portValue); + } + + /** + * Set the 'http.nonProxyHosts' property to the given host values. + *

+ * Hosts will be separated by a '|' character. + *

+ * This setting will apply to all requests. + * + * @param hosts + */ + public static void nonProxyHosts(final String... hosts) { + if (hosts != null && hosts.length > 0) { + StringBuilder separated = new StringBuilder(); + int last = hosts.length - 1; + for (int i = 0; i < last; i++) + separated.append(hosts[i]).append('|'); + separated.append(hosts[last]); + setProperty("http.nonProxyHosts", separated.toString()); + } else + setProperty("http.nonProxyHosts", null); + } + + /** + * Set property to given value. + *

+ * Specifying a null value will cause the property to be cleared + * + * @param name + * @param value + * @return previous value + */ + private static String setProperty(final String name, final String value) { + final PrivilegedAction action; + if (value != null) + action = new PrivilegedAction() { + + public String run() { + return System.setProperty(name, value); + } + }; + else + action = new PrivilegedAction() { + + public String run() { + return System.clearProperty(name); + } + }; + return AccessController.doPrivileged(action); + } + + private HttpURLConnection connection = null; + + private final URL url; + + private final String requestMethod; + + private RequestOutputStream output; + + private boolean multipart; + + private boolean form; + + private boolean ignoreCloseExceptions = true; + + private boolean uncompress = false; + + private int bufferSize = 8192; + + private long totalSize = -1; + + private long totalWritten = 0; + + private String httpProxyHost; + + private int httpProxyPort; + + private UploadProgress progress = UploadProgress.DEFAULT; + + private Map cookies; + + /** + * Create HTTP connection wrapper + * + * @param url + * Remote resource URL. + * @param method + * HTTP request method (e.g., "GET", "POST"). + * @throws HttpRequestException + */ + public HttpRequest(final String url, final String method) throws HttpRequestException { + try { + this.url = new URL(url.toString()); + } catch (MalformedURLException e) { + throw new HttpRequestException(e); + } + this.requestMethod = method; + this.cookies = new LinkedHashMap(); + } + + /** + * Create HTTP connection wrapper + * + * @param url + * Remote resource URL. + * @param method + * HTTP request method (e.g., "GET", "POST"). + * @throws HttpRequestException + */ + public HttpRequest(final URL url, final String method) throws HttpRequestException { + this.url = url; + this.requestMethod = method; + this.cookies = new LinkedHashMap(); + } + + private Proxy createProxy() { + return new Proxy(HTTP, new InetSocketAddress(httpProxyHost, httpProxyPort)); + } + + private HttpURLConnection createConnection() { + try { + final HttpURLConnection connection; + if (httpProxyHost != null) + connection = CONNECTION_FACTORY.create(url, createProxy()); + else + connection = CONNECTION_FACTORY.create(url); + connection.setRequestMethod(requestMethod); + return connection; + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + @Override + public String toString() { + return method() + ' ' + url(); + } + + /** + * Get underlying connection + * + * @return connection + */ + public HttpURLConnection getConnection() { + if (connection == null) + connection = createConnection(); + return connection; + } + + /** + * Set whether or not to ignore exceptions that occur from calling + * {@link Closeable#close()} + *

+ * The default value of this setting is true + * + * @param ignore + * @return this request + */ + public HttpRequest ignoreCloseExceptions(final boolean ignore) { + ignoreCloseExceptions = ignore; + return this; + } + + /** + * Get whether or not exceptions thrown by {@link Closeable#close()} are + * ignored + * + * @return true if ignoring, false if throwing + */ + public boolean ignoreCloseExceptions() { + return ignoreCloseExceptions; + } + + /** + * Get the status code of the response + * + * @return the response code + * @throws HttpRequestException + */ + public int code() throws HttpRequestException { + try { + closeOutput(); + return getConnection().getResponseCode(); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Set the value of the given {@link AtomicInteger} to the status code of + * the response + * + * @param output + * @return this request + * @throws HttpRequestException + */ + public HttpRequest code(final AtomicInteger output) throws HttpRequestException { + output.set(code()); + return this; + } + + /** + * Is the response code a 200 OK? + * + * @return true if 200, false otherwise + * @throws HttpRequestException + */ + public boolean ok() throws HttpRequestException { + return HTTP_OK == code(); + } + + /** + * Is the response code a 201 Created? + * + * @return true if 201, false otherwise + * @throws HttpRequestException + */ + public boolean created() throws HttpRequestException { + return HTTP_CREATED == code(); + } + + /** + * Is the response code a 204 No Content? + * + * @return true if 204, false otherwise + * @throws HttpRequestException + */ + public boolean noContent() throws HttpRequestException { + return HTTP_NO_CONTENT == code(); + } + + /** + * Is the response code a 500 Internal Server Error? + * + * @return true if 500, false otherwise + * @throws HttpRequestException + */ + public boolean serverError() throws HttpRequestException { + return HTTP_INTERNAL_ERROR == code(); + } + + /** + * Is the response code a 400 Bad Request? + * + * @return true if 400, false otherwise + * @throws HttpRequestException + */ + public boolean badRequest() throws HttpRequestException { + return HTTP_BAD_REQUEST == code(); + } + + /** + * Is the response code a 404 Not Found? + * + * @return true if 404, false otherwise + * @throws HttpRequestException + */ + public boolean notFound() throws HttpRequestException { + return HTTP_NOT_FOUND == code(); + } + + /** + * Is the response code a 304 Not Modified? + * + * @return true if 304, false otherwise + * @throws HttpRequestException + */ + public boolean notModified() throws HttpRequestException { + return HTTP_NOT_MODIFIED == code(); + } + + /** + * Get status message of the response + * + * @return message + * @throws HttpRequestException + */ + public String message() throws HttpRequestException { + try { + closeOutput(); + return getConnection().getResponseMessage(); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Disconnect the connection + * + * @return this request + */ + public HttpRequest disconnect() { + getConnection().disconnect(); + return this; + } + + /** + * Set chunked streaming mode to the given size + * + * @param size + * @return this request + */ + public HttpRequest chunk(final int size) { + getConnection().setChunkedStreamingMode(size); + return this; + } + + /** + * Set the size used when buffering and copying between streams + *

+ * This size is also used for send and receive buffers created for both char + * and byte arrays + *

+ * The default buffer size is 8,192 bytes + * + * @param size + * @return this request + */ + public HttpRequest bufferSize(final int size) { + if (size < 1) + throw new IllegalArgumentException("Size must be greater than zero"); + bufferSize = size; + return this; + } + + /** + * Get the configured buffer size + *

+ * The default buffer size is 8,192 bytes + * + * @return buffer size + */ + public int bufferSize() { + return bufferSize; + } + + /** + * Set whether or not the response body should be automatically uncompressed + * when read from. + *

+ * This will only affect requests that have the 'Content-Encoding' response + * header set to 'gzip'. + *

+ * This causes all receive methods to use a {@link GZIPInputStream} when + * applicable so that higher level streams and readers can read the data + * uncompressed. + *

+ * Setting this option does not cause any request headers to be set + * automatically so {@link #acceptGzipEncoding()} should be used in + * conjunction with this setting to tell the server to gzip the response. + * + * @param uncompress + * @return this request + */ + public HttpRequest uncompress(final boolean uncompress) { + this.uncompress = uncompress; + return this; + } + + /** + * Create byte array output stream + * + * @return stream + */ + protected ByteArrayOutputStream byteStream() { + final int size = contentLength(); + if (size > 0) + return new ByteArrayOutputStream(size); + else + return new ByteArrayOutputStream(); + } + + /** + * Get response as {@link String} in given character set + *

+ * This will fall back to using the UTF-8 character set if the given charset + * is null + * + * @param charset + * @return string + * @throws HttpRequestException + */ + public String body(final String charset) throws HttpRequestException { + final ByteArrayOutputStream output = byteStream(); + try { + copy(buffer(), output); + return output.toString(getValidCharset(charset)); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Get response as {@link String} using character set returned from + * {@link #charset()} + * + * @return string + * @throws HttpRequestException + */ + public String body() throws HttpRequestException { + return body(charset()); + } + + /** + * Get the response body as a {@link String} and set it as the value of the + * given reference. + * + * @param output + * @return this request + * @throws HttpRequestException + */ + public HttpRequest body(final AtomicReference output) throws HttpRequestException { + output.set(body()); + return this; + } + + /** + * Get the response body as a {@link String} and set it as the value of the + * given reference. + * + * @param output + * @param charset + * @return this request + * @throws HttpRequestException + */ + public HttpRequest body(final AtomicReference output, final String charset) throws HttpRequestException { + output.set(body(charset)); + return this; + } + + /** + * Is the response body empty? + * + * @return true if the Content-Length response header is 0, false otherwise + * @throws HttpRequestException + */ + public boolean isBodyEmpty() throws HttpRequestException { + return contentLength() == 0; + } + + /** + * Get response as byte array + * + * @return byte array + * @throws HttpRequestException + */ + public byte[] bytes() throws HttpRequestException { + final ByteArrayOutputStream output = byteStream(); + try { + copy(buffer(), output); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return output.toByteArray(); + } + + /** + * Get response in a buffered stream + * + * @see #bufferSize(int) + * @return stream + * @throws HttpRequestException + */ + public BufferedInputStream buffer() throws HttpRequestException { + return new BufferedInputStream(stream(), bufferSize); + } + + /** + * Get stream to response body + * + * @return stream + * @throws HttpRequestException + */ + public InputStream stream() throws HttpRequestException { + InputStream stream; + if (code() < HTTP_BAD_REQUEST) + try { + stream = getConnection().getInputStream(); + } catch (IOException e) { + throw new HttpRequestException(e); + } + else { + stream = getConnection().getErrorStream(); + if (stream == null) + try { + stream = getConnection().getInputStream(); + } catch (IOException e) { + if (contentLength() > 0) + throw new HttpRequestException(e); + else + stream = new ByteArrayInputStream(new byte[0]); + } + } + + if (!uncompress || !Header.ENCODING_GZIP.equals(contentEncoding())) + return stream; + else + try { + return new GZIPInputStream(stream); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Get reader to response body using given character set. + *

+ * This will fall back to using the UTF-8 character set if the given charset + * is null + * + * @param charset + * @return reader + * @throws HttpRequestException + */ + public InputStreamReader reader(final String charset) throws HttpRequestException { + try { + return new InputStreamReader(stream(), getValidCharset(charset)); + } catch (UnsupportedEncodingException e) { + throw new HttpRequestException(e); + } + } + + /** + * Get reader to response body using the character set returned from + * {@link #charset()} + * + * @return reader + * @throws HttpRequestException + */ + public InputStreamReader reader() throws HttpRequestException { + return reader(charset()); + } + + /** + * Get buffered reader to response body using the given character set r and + * the configured buffer size + * + * + * @see #bufferSize(int) + * @param charset + * @return reader + * @throws HttpRequestException + */ + public BufferedReader bufferedReader(final String charset) throws HttpRequestException { + return new BufferedReader(reader(charset), bufferSize); + } + + /** + * Get buffered reader to response body using the character set returned + * from {@link #charset()} and the configured buffer size + * + * @see #bufferSize(int) + * @return reader + * @throws HttpRequestException + */ + public BufferedReader bufferedReader() throws HttpRequestException { + return bufferedReader(charset()); + } + + /** + * Stream response body to file + * + * @param file + * @return this request + * @throws HttpRequestException + */ + public HttpRequest receive(final File file) throws HttpRequestException { + final OutputStream output; + try { + output = new BufferedOutputStream(new FileOutputStream(file), bufferSize); + } catch (FileNotFoundException e) { + throw new HttpRequestException(e); + } + return new CloseOperation(output, ignoreCloseExceptions) { + + @Override + protected HttpRequest run() throws HttpRequestException, IOException { + return receive(output); + } + }.call(); + } + + /** + * Stream response to given output stream + * + * @param output + * @return this request + * @throws HttpRequestException + */ + public HttpRequest receive(final OutputStream output) throws HttpRequestException { + try { + return copy(buffer(), output); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Stream response to given print stream + * + * @param output + * @return this request + * @throws HttpRequestException + */ + public HttpRequest receive(final PrintStream output) throws HttpRequestException { + return receive((OutputStream) output); + } + + /** + * Receive response into the given appendable + * + * @param appendable + * @return this request + * @throws HttpRequestException + */ + public HttpRequest receive(final Appendable appendable) throws HttpRequestException { + final BufferedReader reader = bufferedReader(); + return new CloseOperation(reader, ignoreCloseExceptions) { + + @Override + public HttpRequest run() throws IOException { + final CharBuffer buffer = CharBuffer.allocate(bufferSize); + int read; + while ((read = reader.read(buffer)) != -1) { + buffer.rewind(); + appendable.append(buffer, 0, read); + buffer.rewind(); + } + return HttpRequest.this; + } + }.call(); + } + + /** + * Receive response into the given writer + * + * @param writer + * @return this request + * @throws HttpRequestException + */ + public HttpRequest receive(final Writer writer) throws HttpRequestException { + final BufferedReader reader = bufferedReader(); + return new CloseOperation(reader, ignoreCloseExceptions) { + + @Override + public HttpRequest run() throws IOException { + return copy(reader, writer); + } + }.call(); + } + + /** + * Set read timeout on connection to given value + * + * @param timeout + * @return this request + */ + public HttpRequest readTimeout(final int timeout) { + getConnection().setReadTimeout(timeout); + return this; + } + + /** + * Set connect timeout on connection to given value + * + * @param timeout + * @return this request + */ + public HttpRequest connectTimeout(final int timeout) { + getConnection().setConnectTimeout(timeout); + return this; + } + + /** + * Set header name to given value + * + * @param name + * @param value + * @return this request + */ + public HttpRequest header(final String name, final String value) { + getConnection().setRequestProperty(name, value); + return this; + } + + /** + * Set cookie. e.g: key1=val1; key2=val2; + * + * @param key + * @param val + * @return + */ + public HttpRequest cookie(final String name, final String value) { + Assert.notEmpty(name, "Cookie name must not be empty"); + Assert.notNull(value); + + this.cookies.put(name, value); + this.executeCookie(getConnection()); + return this; + } + + public HttpRequest cookies(final Map cookies) { + if(null != cookies){ + this.cookies.putAll(cookies); + this.executeCookie(getConnection()); + } + return this; + } + + private void executeCookie(HttpURLConnection connection){ + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Map.Entry cookie : this.cookies.entrySet()) { + if (!first) + sb.append("; "); + else + first = false; + sb.append(cookie.getKey()).append('=').append(cookie.getValue()); + // todo: spec says only ascii, no escaping / encoding defined. validate on set? or escape somehow here? + } + String cookies = sb.toString(); + connection.setRequestProperty(Header.HEADER_COOKIE, cookies); + } + + + /** + * Set header name to given value + * + * @param name + * @param value + * @return this request + */ + public HttpRequest header(final String name, final Number value) { + return header(name, value != null ? value.toString() : null); + } + + /** + * Set all headers found in given map where the keys are the header names + * and the values are the header values + * + * @param headers + * @return this request + */ + public HttpRequest headers(final Map headers) { + if (!headers.isEmpty()) + for (Entry header : headers.entrySet()) + header(header); + return this; + } + + /** + * Set header to have given entry's key as the name and value as the value + * + * @param header + * @return this request + */ + public HttpRequest header(final Entry header) { + return header(header.getKey(), header.getValue()); + } + + /** + * Get a response header + * + * @param name + * @return response header + * @throws HttpRequestException + */ + public String header(final String name) throws HttpRequestException { + closeOutputQuietly(); + return getConnection().getHeaderField(name); + } + + /** + * Get all the response headers + * + * @return map of response header names to their value(s) + * @throws HttpRequestException + */ + public Map> headers() throws HttpRequestException { + closeOutputQuietly(); + return getConnection().getHeaderFields(); + } + + /** + * Get response cookie + * + * @param name cookie name + * @return cookie value + */ + public String cookie(final String name) { + Assert.notEmpty(name, "Cookie name must not be empty"); + return this.cookies().get(name); + } + + /** + * Get response cookies + * + * @return cookies map + */ + public Map cookies() { + closeOutputQuietly(); + this.cookies.clear(); + Map> resHeaders = getConnection().getHeaderFields(); + processResponseHeaders(resHeaders); + return this.cookies; + } + + void processResponseHeaders(Map> resHeaders) { + for (Map.Entry> entry : resHeaders.entrySet()) { + String name = entry.getKey(); + if (name == null) + continue; // http/1.1 line + List values = entry.getValue(); + if (name.equalsIgnoreCase(Header.HEADER_SET_COOKIE)) { + for (String value : values) { + if (value == null) + continue; + TokenQueue cd = new TokenQueue(value); + String cookieName = cd.chompTo("=").trim(); + String cookieVal = cd.consumeTo(";").trim(); + // ignores path, date, domain, validateTLSCertificates et al. req'd? + // name not blank, value not null + if (cookieName.length() > 0) + cookie(cookieName, cookieVal); + } + } + } + } + + /** + * Get a date header from the response falling back to returning -1 if the + * header is missing or parsing fails + * + * @param name + * @return date, -1 on failures + * @throws HttpRequestException + */ + public long dateHeader(final String name) throws HttpRequestException { + return dateHeader(name, -1L); + } + + /** + * Get a date header from the response falling back to returning the given + * default value if the header is missing or parsing fails + * + * @param name + * @param defaultValue + * @return date, default value on failures + * @throws HttpRequestException + */ + public long dateHeader(final String name, final long defaultValue) throws HttpRequestException { + closeOutputQuietly(); + return getConnection().getHeaderFieldDate(name, defaultValue); + } + + /** + * Get an integer header from the response falling back to returning -1 if + * the header is missing or parsing fails + * + * @param name + * @return header value as an integer, -1 when missing or parsing fails + * @throws HttpRequestException + */ + public int intHeader(final String name) throws HttpRequestException { + return intHeader(name, -1); + } + + /** + * Get an integer header value from the response falling back to the given + * default value if the header is missing or if parsing fails + * + * @param name + * @param defaultValue + * @return header value as an integer, default value when missing or parsing + * fails + * @throws HttpRequestException + */ + public int intHeader(final String name, final int defaultValue) throws HttpRequestException { + closeOutputQuietly(); + return getConnection().getHeaderFieldInt(name, defaultValue); + } + + /** + * Get all values of the given header from the response + * + * @param name + * @return non-null but possibly empty array of {@link String} header values + */ + public String[] headers(final String name) { + final Map> headers = headers(); + if (headers == null || headers.isEmpty()) + return EMPTY_STRINGS; + + final List values = headers.get(name); + if (values != null && !values.isEmpty()) + return values.toArray(new String[values.size()]); + else + return EMPTY_STRINGS; + } + + /** + * Get parameter with given name from header value in response + * + * @param headerName + * @param paramName + * @return parameter value or null if missing + */ + public String parameter(final String headerName, final String paramName) { + return getParam(header(headerName), paramName); + } + + /** + * Get all parameters from header value in response + *

+ * This will be all key=value pairs after the first ';' that are separated + * by a ';' + * + * @param headerName + * @return non-null but possibly empty map of parameter headers + */ + public Map parameters(final String headerName) { + return getParams(header(headerName)); + } + + /** + * Get parameter values from header value + * + * @param header + * @return parameter value or null if none + */ + protected Map getParams(final String header) { + if (header == null || header.length() == 0) + return Collections.emptyMap(); + + final int headerLength = header.length(); + int start = header.indexOf(';') + 1; + if (start == 0 || start == headerLength) + return Collections.emptyMap(); + + int end = header.indexOf(';', start); + if (end == -1) + end = headerLength; + + Map params = new LinkedHashMap(); + while (start < end) { + int nameEnd = header.indexOf('=', start); + if (nameEnd != -1 && nameEnd < end) { + String name = header.substring(start, nameEnd).trim(); + if (name.length() > 0) { + String value = header.substring(nameEnd + 1, end).trim(); + int length = value.length(); + if (length != 0) + if (length > 2 && '"' == value.charAt(0) && '"' == value.charAt(length - 1)) + params.put(name, value.substring(1, length - 1)); + else + params.put(name, value); + } + } + + start = end + 1; + end = header.indexOf(';', start); + if (end == -1) + end = headerLength; + } + + return params; + } + + /** + * Get parameter value from header value + * + * @param value + * @param paramName + * @return parameter value or null if none + */ + protected String getParam(final String value, final String paramName) { + if (value == null || value.length() == 0) + return null; + + final int length = value.length(); + int start = value.indexOf(';') + 1; + if (start == 0 || start == length) + return null; + + int end = value.indexOf(';', start); + if (end == -1) + end = length; + + while (start < end) { + int nameEnd = value.indexOf('=', start); + if (nameEnd != -1 && nameEnd < end && paramName.equals(value.substring(start, nameEnd).trim())) { + String paramValue = value.substring(nameEnd + 1, end).trim(); + int valueLength = paramValue.length(); + if (valueLength != 0) + if (valueLength > 2 && '"' == paramValue.charAt(0) && '"' == paramValue.charAt(valueLength - 1)) + return paramValue.substring(1, valueLength - 1); + else + return paramValue; + } + + start = end + 1; + end = value.indexOf(';', start); + if (end == -1) + end = length; + } + + return null; + } + + /** + * Get 'charset' parameter from 'Content-Type' response header + * + * @return charset or null if none + */ + public String charset() { + return parameter(Header.HEADER_CONTENT_TYPE, Header.PARAM_CHARSET); + } + + /** + * Set the 'User-Agent' header to given value + * + * @param userAgent + * @return this request + */ + public HttpRequest userAgent(final String userAgent) { + return header(Header.HEADER_USER_AGENT, userAgent); + } + + /** + * Set the 'Referer' header to given value + * + * @param referer + * @return this request + */ + public HttpRequest referer(final String referer) { + return header(Header.HEADER_REFERER, referer); + } + + /** + * Set value of {@link HttpURLConnection#setUseCaches(boolean)} + * + * @param useCaches + * @return this request + */ + public HttpRequest useCaches(final boolean useCaches) { + getConnection().setUseCaches(useCaches); + return this; + } + + /** + * Set the 'Accept-Encoding' header to given value + * + * @param acceptEncoding + * @return this request + */ + public HttpRequest acceptEncoding(final String acceptEncoding) { + return header(Header.HEADER_ACCEPT_ENCODING, acceptEncoding); + } + + /** + * Set the 'Accept-Encoding' header to 'gzip' + * + * @see #uncompress(boolean) + * @return this request + */ + public HttpRequest acceptGzipEncoding() { + return acceptEncoding(Header.ENCODING_GZIP); + } + + /** + * Set the 'Accept-Charset' header to given value + * + * @param acceptCharset + * @return this request + */ + public HttpRequest acceptCharset(final String acceptCharset) { + return header(Header.HEADER_ACCEPT_CHARSET, acceptCharset); + } + + /** + * Get the 'Content-Encoding' header from the response + * + * @return this request + */ + public String contentEncoding() { + return header(Header.HEADER_CONTENT_ENCODING); + } + + /** + * Get the 'Server' header from the response + * + * @return server + */ + public String server() { + return header(Header.HEADER_SERVER); + } + + /** + * Get the 'Date' header from the response + * + * @return date value, -1 on failures + */ + public long date() { + return dateHeader(Header.HEADER_DATE); + } + + /** + * Get the 'Cache-Control' header from the response + * + * @return cache control + */ + public String cacheControl() { + return header(Header.HEADER_CACHE_CONTROL); + } + + /** + * Get the 'ETag' header from the response + * + * @return entity tag + */ + public String eTag() { + return header(Header.HEADER_ETAG); + } + + /** + * Get the 'Expires' header from the response + * + * @return expires value, -1 on failures + */ + public long expires() { + return dateHeader(Header.HEADER_EXPIRES); + } + + /** + * Get the 'Last-Modified' header from the response + * + * @return last modified value, -1 on failures + */ + public long lastModified() { + return dateHeader(Header.HEADER_LAST_MODIFIED); + } + + /** + * Get the 'Location' header from the response + * + * @return location + */ + public String location() { + return header(Header.HEADER_LOCATION); + } + + /** + * Set the 'Authorization' header to given value + * + * @param authorization + * @return this request + */ + public HttpRequest authorization(final String authorization) { + return header(Header.HEADER_AUTHORIZATION, authorization); + } + + /** + * Set the 'Proxy-Authorization' header to given value + * + * @param proxyAuthorization + * @return this request + */ + public HttpRequest proxyAuthorization(final String proxyAuthorization) { + return header(Header.HEADER_PROXY_AUTHORIZATION, proxyAuthorization); + } + + /** + * Set the 'Authorization' header to given values in Basic authentication + * format + * + * @param name + * @param password + * @return this request + */ + public HttpRequest basic(final String name, final String password) { + return authorization("Basic " + Base64.encode(name + ':' + password)); + } + + /** + * Set the 'Proxy-Authorization' header to given values in Basic + * authentication format + * + * @param name + * @param password + * @return this request + */ + public HttpRequest proxyBasic(final String name, final String password) { + return proxyAuthorization("Basic " + Base64.encode(name + ':' + password)); + } + + /** + * Set the 'If-Modified-Since' request header to the given value + * + * @param ifModifiedSince + * @return this request + */ + public HttpRequest ifModifiedSince(final long ifModifiedSince) { + getConnection().setIfModifiedSince(ifModifiedSince); + return this; + } + + /** + * Set the 'If-None-Match' request header to the given value + * + * @param ifNoneMatch + * @return this request + */ + public HttpRequest ifNoneMatch(final String ifNoneMatch) { + return header(Header.HEADER_IF_NONE_MATCH, ifNoneMatch); + } + + /** + * Set the 'Content-Type' request header to the given value + * + * @param contentType + * @return this request + */ + public HttpRequest contentType(final String contentType) { + return contentType(contentType, null); + } + + /** + * Set the 'Content-Type' request header to the given value and charset + * + * @param contentType + * @param charset + * @return this request + */ + public HttpRequest contentType(final String contentType, final String charset) { + if (charset != null && charset.length() > 0) { + final String separator = "; " + Header.PARAM_CHARSET + '='; + return header(Header.HEADER_CONTENT_TYPE, contentType + separator + charset); + } else + return header(Header.HEADER_CONTENT_TYPE, contentType); + } + + /** + * Get the 'Content-Type' header from the response + * + * @return response header value + */ + public String contentType() { + return header(Header.HEADER_CONTENT_TYPE); + } + + /** + * Get the 'Content-Length' header from the response + * + * @return response header value + */ + public int contentLength() { + return intHeader(Header.HEADER_CONTENT_LENGTH); + } + + /** + * Set the 'Content-Length' request header to the given value + * + * @param contentLength + * @return this request + */ + public HttpRequest contentLength(final String contentLength) { + return contentLength(Integer.parseInt(contentLength)); + } + + /** + * Set the 'Content-Length' request header to the given value + * + * @param contentLength + * @return this request + */ + public HttpRequest contentLength(final int contentLength) { + getConnection().setFixedLengthStreamingMode(contentLength); + return this; + } + + /** + * Set the 'Accept' header to given value + * + * @param accept + * @return this request + */ + public HttpRequest accept(final String accept) { + return header(Header.HEADER_ACCEPT, accept); + } + + /** + * Set the 'Accept' header to 'application/json' + * + * @return this request + */ + public HttpRequest acceptJson() { + return accept(Header.CONTENT_TYPE_JSON); + } + + /** + * Copy from input stream to output stream + * + * @param input + * @param output + * @return this request + * @throws IOException + */ + protected HttpRequest copy(final InputStream input, final OutputStream output) throws IOException { + return new CloseOperation(input, ignoreCloseExceptions) { + + @Override + public HttpRequest run() throws IOException { + final byte[] buffer = new byte[bufferSize]; + int read; + while ((read = input.read(buffer)) != -1) { + output.write(buffer, 0, read); + totalWritten += read; + progress.onUpload(totalWritten, totalSize); + } + return HttpRequest.this; + } + }.call(); + } + + /** + * Copy from reader to writer + * + * @param input + * @param output + * @return this request + * @throws IOException + */ + protected HttpRequest copy(final Reader input, final Writer output) throws IOException { + return new CloseOperation(input, ignoreCloseExceptions) { + + @Override + public HttpRequest run() throws IOException { + final char[] buffer = new char[bufferSize]; + int read; + while ((read = input.read(buffer)) != -1) { + output.write(buffer, 0, read); + totalWritten += read; + progress.onUpload(totalWritten, -1); + } + return HttpRequest.this; + } + }.call(); + } + + /** + * Set the UploadProgress callback for this request + * + * @param callback + * @return this request + */ + public HttpRequest progress(final UploadProgress callback) { + if (callback == null) + progress = UploadProgress.DEFAULT; + else + progress = callback; + return this; + } + + private HttpRequest incrementTotalSize(final long size) { + if (totalSize == -1) + totalSize = 0; + totalSize += size; + return this; + } + + /** + * Close output stream + * + * @return this request + * @throws HttpRequestException + * @throws IOException + */ + protected HttpRequest closeOutput() throws IOException { + progress(null); + if (output == null) + return this; + if (multipart) + output.write(CRLF + "--" + BOUNDARY + "--" + CRLF); + if (ignoreCloseExceptions) + try { + output.close(); + } catch (IOException ignored) { + // Ignored + } + else + output.close(); + output = null; + return this; + } + + /** + * Call {@link #closeOutput()} and re-throw a caught {@link IOException}s as + * an {@link HttpRequestException} + * + * @return this request + * @throws HttpRequestException + */ + protected HttpRequest closeOutputQuietly() throws HttpRequestException { + try { + return closeOutput(); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Open output stream + * + * @return this request + * @throws IOException + */ + protected HttpRequest openOutput() throws IOException { + if (output != null) + return this; + getConnection().setDoOutput(true); + final String charset = getParam(getConnection().getRequestProperty(Header.HEADER_CONTENT_TYPE), + Header.PARAM_CHARSET); + output = new RequestOutputStream(getConnection().getOutputStream(), charset, bufferSize); + return this; + } + + /** + * Start part of a multipart + * + * @return this request + * @throws IOException + */ + protected HttpRequest startPart() throws IOException { + if (!multipart) { + multipart = true; + contentType(CONTENT_TYPE_MULTIPART).openOutput(); + output.write("--" + BOUNDARY + CRLF); + } else + output.write(CRLF + "--" + BOUNDARY + CRLF); + return this; + } + + /** + * Write part header + * + * @param name + * @param filename + * @return this request + * @throws IOException + */ + protected HttpRequest writePartHeader(final String name, final String filename) throws IOException { + return writePartHeader(name, filename, null); + } + + /** + * Write part header + * + * @param name + * @param filename + * @param contentType + * @return this request + * @throws IOException + */ + protected HttpRequest writePartHeader(final String name, final String filename, final String contentType) + throws IOException { + final StringBuilder partBuffer = new StringBuilder(); + partBuffer.append("form-data; name=\"").append(name); + if (filename != null) + partBuffer.append("\"; filename=\"").append(filename); + partBuffer.append('"'); + partHeader("Content-Disposition", partBuffer.toString()); + if (contentType != null) + partHeader(Header.HEADER_CONTENT_TYPE, contentType); + return send(CRLF); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param part + * @return this request + */ + public HttpRequest part(final String name, final String part) { + return part(name, null, part); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param filename + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final String filename, final String part) throws HttpRequestException { + return part(name, filename, null, part); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param filename + * @param contentType + * value of the Content-Type part header + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final String filename, final String contentType, final String part) + throws HttpRequestException { + try { + startPart(); + writePartHeader(name, filename, contentType); + output.write(part); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return this; + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final Number part) throws HttpRequestException { + return part(name, null, part); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param filename + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final String filename, final Number part) throws HttpRequestException { + return part(name, filename, part != null ? part.toString() : null); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final File part) throws HttpRequestException { + return part(name, null, part); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param filename + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final String filename, final File part) throws HttpRequestException { + return part(name, filename, null, part); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param filename + * @param contentType + * value of the Content-Type part header + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final String filename, final String contentType, final File part) + throws HttpRequestException { + final InputStream stream; + try { + stream = new BufferedInputStream(new FileInputStream(part)); + incrementTotalSize(part.length()); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return part(name, filename, contentType, stream); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final InputStream part) throws HttpRequestException { + return part(name, null, null, part); + } + + /** + * Write part of a multipart request to the request body + * + * @param name + * @param filename + * @param contentType + * value of the Content-Type part header + * @param part + * @return this request + * @throws HttpRequestException + */ + public HttpRequest part(final String name, final String filename, final String contentType, final InputStream part) + throws HttpRequestException { + try { + startPart(); + writePartHeader(name, filename, contentType); + copy(part, output); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return this; + } + + /** + * Write a multipart header to the response body + * + * @param name + * @param value + * @return this request + * @throws HttpRequestException + */ + public HttpRequest partHeader(final String name, final String value) throws HttpRequestException { + return send(name).send(": ").send(value).send(CRLF); + } + + /** + * Write contents of file to request body + * + * @param input + * @return this request + * @throws HttpRequestException + */ + public HttpRequest send(final File input) throws HttpRequestException { + final InputStream stream; + try { + stream = new BufferedInputStream(new FileInputStream(input)); + incrementTotalSize(input.length()); + } catch (FileNotFoundException e) { + throw new HttpRequestException(e); + } + return send(stream); + } + + /** + * Write byte array to request body + * + * @param input + * @return this request + * @throws HttpRequestException + */ + public HttpRequest send(final byte[] input) throws HttpRequestException { + if (input != null) + incrementTotalSize(input.length); + return send(new ByteArrayInputStream(input)); + } + + /** + * Write stream to request body + *

+ * The given stream will be closed once sending completes + * + * @param input + * @return this request + * @throws HttpRequestException + */ + public HttpRequest send(final InputStream input) throws HttpRequestException { + try { + openOutput(); + copy(input, output); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return this; + } + + /** + * Write reader to request body + *

+ * The given reader will be closed once sending completes + * + * @param input + * @return this request + * @throws HttpRequestException + */ + public HttpRequest send(final Reader input) throws HttpRequestException { + try { + openOutput(); + } catch (IOException e) { + throw new HttpRequestException(e); + } + final Writer writer = new OutputStreamWriter(output, output.encoder.charset()); + return new FlushOperation(writer) { + + @Override + protected HttpRequest run() throws IOException { + return copy(input, writer); + } + }.call(); + } + + /** + * Write char sequence to request body + *

+ * The charset configured via {@link #contentType(String)} will be used and + * UTF-8 will be used if it is unset. + * + * @param value + * @return this request + * @throws HttpRequestException + */ + public HttpRequest send(final String value) throws HttpRequestException { + try { + openOutput(); + output.write(value.toString()); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return this; + } + + /** + * Create writer to request output stream + * + * @return writer + * @throws HttpRequestException + */ + public OutputStreamWriter writer() throws HttpRequestException { + try { + openOutput(); + return new OutputStreamWriter(output, output.encoder.charset()); + } catch (IOException e) { + throw new HttpRequestException(e); + } + } + + /** + * Write the values in the map as form data to the request body + *

+ * The pairs specified will be URL-encoded in UTF-8 and sent with the + * 'application/x-www-form-urlencoded' content-type + * + * @param values + * @return this request + * @throws HttpRequestException + */ + public HttpRequest form(final Map values) throws HttpRequestException { + return form(values, Header.CHARSET_UTF8); + } + + /** + * Write the key and value in the entry as form data to the request body + *

+ * The pair specified will be URL-encoded in UTF-8 and sent with the + * 'application/x-www-form-urlencoded' content-type + * + * @param entry + * @return this request + * @throws HttpRequestException + */ + public HttpRequest form(final Entry entry) throws HttpRequestException { + return form(entry, Header.CHARSET_UTF8); + } + + /** + * Write the key and value in the entry as form data to the request body + *

+ * The pair specified will be URL-encoded and sent with the + * 'application/x-www-form-urlencoded' content-type + * + * @param entry + * @param charset + * @return this request + * @throws HttpRequestException + */ + public HttpRequest form(final Entry entry, final String charset) throws HttpRequestException { + return form(entry.getKey(), entry.getValue(), charset); + } + + /** + * Write the name/value pair as form data to the request body + *

+ * The pair specified will be URL-encoded in UTF-8 and sent with the + * 'application/x-www-form-urlencoded' content-type + * + * @param name + * @param value + * @return this request + * @throws HttpRequestException + */ + public HttpRequest form(final Object name, final Object value) throws HttpRequestException { + return form(name, value, Header.CHARSET_UTF8); + } + + /** + * Write the name/value pair as form data to the request body + *

+ * The values specified will be URL-encoded and sent with the + * 'application/x-www-form-urlencoded' content-type + * + * @param name + * @param value + * @param charset + * @return this request + * @throws HttpRequestException + */ + public HttpRequest form(final Object name, final Object value, String charset) throws HttpRequestException { + final boolean first = !form; + if (first) { + contentType(Header.CONTENT_TYPE_FORM, charset); + form = true; + } + charset = getValidCharset(charset); + try { + openOutput(); + if (!first) + output.write('&'); + output.write(URLEncoder.encode(name.toString(), charset)); + output.write('='); + if (value != null) + output.write(URLEncoder.encode(value.toString(), charset)); + } catch (IOException e) { + throw new HttpRequestException(e); + } + return this; + } + + /** + * Write the values in the map as encoded form data to the request body + * + * @param values + * @param charset + * @return this request + * @throws HttpRequestException + */ + public HttpRequest form(final Map values, final String charset) throws HttpRequestException { + if (!values.isEmpty()) + for (Entry entry : values.entrySet()) + form(entry, charset); + return this; + } + + /** + * Configure HTTPS connection to trust all certificates + *

+ * This method does nothing if the current request is not a HTTPS request + * + * @return this request + * @throws HttpRequestException + */ + public HttpRequest trustAllCerts() throws HttpRequestException { + final HttpURLConnection connection = getConnection(); + if (connection instanceof HttpsURLConnection) + ((HttpsURLConnection) connection).setSSLSocketFactory(getTrustedFactory()); + return this; + } + + /** + * Configure HTTPS connection to trust all hosts using a custom + * {@link HostnameVerifier} that always returns true for each + * host verified + *

+ * This method does nothing if the current request is not a HTTPS request + * + * @return this request + */ + public HttpRequest trustAllHosts() { + final HttpURLConnection connection = getConnection(); + if (connection instanceof HttpsURLConnection) + ((HttpsURLConnection) connection).setHostnameVerifier(getTrustedVerifier()); + return this; + } + + /** + * Get the {@link URL} of this request's connection + * + * @return request URL + */ + public URL url() { + return getConnection().getURL(); + } + + /** + * Get the HTTP method of this request + * + * @return method + */ + public String method() { + return getConnection().getRequestMethod(); + } + + /** + * Configure an HTTP proxy on this connection. Use { + * {@link #proxyBasic(String, String)} if this proxy requires basic + * authentication. + * + * @param proxyHost + * @param proxyPort + * @return this request + */ + public HttpRequest useProxy(final String proxyHost, final int proxyPort) { + if (connection != null) + throw new IllegalStateException( + "The connection has already been created. This method must be called before reading or writing to the request."); + + this.httpProxyHost = proxyHost; + this.httpProxyPort = proxyPort; + return this; + } + + /** + * Set whether or not the underlying connection should follow redirects in + * the response. + * + * @param followRedirects + * - true fo follow redirects, false to not. + * @return this request + */ + public HttpRequest followRedirects(final boolean followRedirects) { + getConnection().setInstanceFollowRedirects(followRedirects); + return this; + } +} \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/http/HttpRequestException.java b/blade-kit/src/main/java/com/blade/kit/http/HttpRequestException.java new file mode 100644 index 000000000..7a335ca6e --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/http/HttpRequestException.java @@ -0,0 +1,23 @@ +package com.blade.kit.http; + +import java.io.IOException; + +/** + * HTTP请求异常 + * + * @author biezhi + * @since 1.0 + */ +public class HttpRequestException extends RuntimeException { + + private static final long serialVersionUID = -1170466989781746231L; + + public HttpRequestException(final IOException cause) { + super(cause); + } + + @Override + public IOException getCause() { + return (IOException) super.getCause(); + } +} \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/http/MethodType.java b/blade-kit/src/main/java/com/blade/kit/http/MethodType.java new file mode 100644 index 000000000..e647169e9 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/http/MethodType.java @@ -0,0 +1,51 @@ +package com.blade.kit.http; + +/** + * 请求方法类型 + * + * @author biezhi + * @since 1.0 + */ +public final class MethodType { + + /** + * 'DELETE' request method + */ + public static final String DELETE = "DELETE"; + + /** + * 'GET' request method + */ + public static final String GET = "GET"; + + /** + * 'HEAD' request method + */ + public static final String HEAD = "HEAD"; + + /** + * 'OPTIONS' options method + */ + public static final String OPTIONS = "OPTIONS"; + + /** + * 'POST' request method + */ + public static final String POST = "POST"; + + /** + * 'PUT' request method + */ + public static final String PUT = "PUT"; + + /** + * 'TRACE' request method + */ + public static final String TRACE = "TRACE"; + + /** + * 'CONNECT' request method + */ + public static final String CONNECT = "CONNECT"; + +} diff --git a/blade-kit/src/main/java/com/blade/kit/http/RequestOutputStream.java b/blade-kit/src/main/java/com/blade/kit/http/RequestOutputStream.java new file mode 100644 index 000000000..7ac4ec9b0 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/http/RequestOutputStream.java @@ -0,0 +1,48 @@ +package com.blade.kit.http; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; + +/** + * 请求输出流包装 + * + * @author biezhi + * @since 1.0 + */ +public class RequestOutputStream extends BufferedOutputStream { + + final CharsetEncoder encoder; + + /** + * Create request output stream + * + * @param stream + * @param charset + * @param bufferSize + */ + public RequestOutputStream(final OutputStream stream, final String charset, final int bufferSize) { + super(stream, bufferSize); + + encoder = Charset.forName(HttpRequest.getValidCharset(charset)).newEncoder(); + } + + /** + * Write string to stream + * + * @param value + * @return this stream + * @throws IOException + */ + public RequestOutputStream write(final String value) throws IOException { + final ByteBuffer bytes = encoder.encode(CharBuffer.wrap(value)); + + super.write(bytes.array(), 0, bytes.limit()); + + return this; + } +} \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/http/TokenQueue.java b/blade-kit/src/main/java/com/blade/kit/http/TokenQueue.java new file mode 100644 index 000000000..bd2694408 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/http/TokenQueue.java @@ -0,0 +1,394 @@ +package com.blade.kit.http; + +import com.blade.kit.Assert; + +/** + * A character queue with parsing helpers. + * + * @author Jonathan Hedley + */ +public class TokenQueue { + private String queue; + private int pos = 0; + + private static final char ESC = '\\'; // escape char for chomp balanced. + + /** + Create a new TokenQueue. + @param data string of data to back queue. + */ + public TokenQueue(String data) { + Assert.notNull(data); + queue = data; + } + + /** + * Is the queue empty? + * @return true if no data left in queue. + */ + public boolean isEmpty() { + return remainingLength() == 0; + } + + private int remainingLength() { + return queue.length() - pos; + } + + /** + * Retrieves but does not remove the first character from the queue. + * @return First character, or 0 if empty. + */ + public char peek() { + return isEmpty() ? 0 : queue.charAt(pos); + } + + /** + Add a character to the start of the queue (will be the next character retrieved). + @param c character to add + */ + public void addFirst(Character c) { + addFirst(c.toString()); + } + + /** + Add a string to the start of the queue. + @param seq string to add. + */ + public void addFirst(String seq) { + // not very performant, but an edge case + queue = seq + queue.substring(pos); + pos = 0; + } + + /** + * Tests if the next characters on the queue match the sequence. Case insensitive. + * @param seq String to check queue for. + * @return true if the next characters match. + */ + public boolean matches(String seq) { + return queue.regionMatches(true, pos, seq, 0, seq.length()); + } + + /** + * Case sensitive match test. + * @param seq string to case sensitively check for + * @return true if matched, false if not + */ + public boolean matchesCS(String seq) { + return queue.startsWith(seq, pos); + } + + + /** + Tests if the next characters match any of the sequences. Case insensitive. + @param seq list of strings to case insensitively check for + @return true of any matched, false if none did + */ + public boolean matchesAny(String... seq) { + for (String s : seq) { + if (matches(s)) + return true; + } + return false; + } + + public boolean matchesAny(char... seq) { + if (isEmpty()) + return false; + + for (char c: seq) { + if (queue.charAt(pos) == c) + return true; + } + return false; + } + + public boolean matchesStartTag() { + // micro opt for matching "= 2 && queue.charAt(pos) == '<' && Character.isLetter(queue.charAt(pos+1))); + } + + /** + * Tests if the queue matches the sequence (as with match), and if they do, removes the matched string from the + * queue. + * @param seq String to search for, and if found, remove from queue. + * @return true if found and removed, false if not found. + */ + public boolean matchChomp(String seq) { + if (matches(seq)) { + pos += seq.length(); + return true; + } else { + return false; + } + } + + /** + Tests if queue starts with a whitespace character. + @return if starts with whitespace + */ + public boolean matchesWhitespace() { + return !isEmpty() && Character.isWhitespace(queue.charAt(pos)); + } + + /** + Test if the queue matches a word character (letter or digit). + @return if matches a word character + */ + public boolean matchesWord() { + return !isEmpty() && Character.isLetterOrDigit(queue.charAt(pos)); + } + + /** + * Drops the next character off the queue. + */ + public void advance() { + if (!isEmpty()) pos++; + } + + /** + * Consume one character off queue. + * @return first character on queue. + */ + public char consume() { + return queue.charAt(pos++); + } + + /** + * Consumes the supplied sequence of the queue. If the queue does not start with the supplied sequence, will + * throw an illegal state exception -- but you should be running match() against that condition. +

+ Case insensitive. + * @param seq sequence to remove from head of queue. + */ + public void consume(String seq) { + if (!matches(seq)) + throw new IllegalStateException("Queue did not match expected sequence"); + int len = seq.length(); + if (len > remainingLength()) + throw new IllegalStateException("Queue not long enough to consume sequence"); + + pos += len; + } + + /** + * Pulls a string off the queue, up to but exclusive of the match sequence, or to the queue running out. + * @param seq String to end on (and not include in return, but leave on queue). Case sensitive. + * @return The matched data consumed from queue. + */ + public String consumeTo(String seq) { + int offset = queue.indexOf(seq, pos); + if (offset != -1) { + String consumed = queue.substring(pos, offset); + pos += consumed.length(); + return consumed; + } else { + return remainder(); + } + } + + public String consumeToIgnoreCase(String seq) { + int start = pos; + String first = seq.substring(0, 1); + boolean canScan = first.toLowerCase().equals(first.toUpperCase()); // if first is not cased, use index of + while (!isEmpty()) { + if (matches(seq)) + break; + + if (canScan) { + int skip = queue.indexOf(first, pos) - pos; + if (skip == 0) // this char is the skip char, but not match, so force advance of pos + pos++; + else if (skip < 0) // no chance of finding, grab to end + pos = queue.length(); + else + pos += skip; + } + else + pos++; + } + + return queue.substring(start, pos); + } + + /** + Consumes to the first sequence provided, or to the end of the queue. Leaves the terminator on the queue. + @param seq any number of terminators to consume to. Case insensitive. + @return consumed string + */ + // todo: method name. not good that consumeTo cares for case, and consume to any doesn't. And the only use for this + // is is a case sensitive time... + public String consumeToAny(String... seq) { + int start = pos; + while (!isEmpty() && !matchesAny(seq)) { + pos++; + } + + return queue.substring(start, pos); + } + + /** + * Pulls a string off the queue (like consumeTo), and then pulls off the matched string (but does not return it). + *

+ * If the queue runs out of characters before finding the seq, will return as much as it can (and queue will go + * isEmpty() == true). + * @param seq String to match up to, and not include in return, and to pull off queue. Case sensitive. + * @return Data matched from queue. + */ + public String chompTo(String seq) { + String data = consumeTo(seq); + matchChomp(seq); + return data; + } + + public String chompToIgnoreCase(String seq) { + String data = consumeToIgnoreCase(seq); // case insensitive scan + matchChomp(seq); + return data; + } + + /** + * Pulls a balanced string off the queue. E.g. if queue is "(one (two) three) four", (,) will return "one (two) three", + * and leave " four" on the queue. Unbalanced openers and closers can be escaped (with \). Those escapes will be left + * in the returned string, which is suitable for regexes (where we need to preserve the escape), but unsuitable for + * contains text strings; use unescape for that. + * @param open opener + * @param close closer + * @return data matched from the queue + */ + public String chompBalanced(char open, char close) { + int start = -1; + int end = -1; + int depth = 0; + char last = 0; + + do { + if (isEmpty()) break; + Character c = consume(); + if (last == 0 || last != ESC) { + if (c.equals(open)) { + depth++; + if (start == -1) + start = pos; + } + else if (c.equals(close)) + depth--; + } + + if (depth > 0 && last != 0) + end = pos; // don't include the outer match pair in the return + last = c; + } while (depth > 0); + return (end >= 0) ? queue.substring(start, end) : ""; + } + + /** + * Unescaped a \ escaped string. + * @param in backslash escaped string + * @return unescaped string + */ + public static String unescape(String in) { + StringBuilder out = new StringBuilder(); + char last = 0; + for (char c : in.toCharArray()) { + if (c == ESC) { + if (last != 0 && last == ESC) + out.append(c); + } + else + out.append(c); + last = c; + } + return out.toString(); + } + + /** + * Pulls the next run of whitespace characters of the queue. + * @return Whether consuming whitespace or not + */ + public boolean consumeWhitespace() { + boolean seen = false; + while (matchesWhitespace()) { + pos++; + seen = true; + } + return seen; + } + + /** + * Retrieves the next run of word type (letter or digit) off the queue. + * @return String of word characters from queue, or empty string if none. + */ + public String consumeWord() { + int start = pos; + while (matchesWord()) + pos++; + return queue.substring(start, pos); + } + + /** + * Consume an tag name off the queue (word or :, _, -) + * + * @return tag name + */ + public String consumeTagName() { + int start = pos; + while (!isEmpty() && (matchesWord() || matchesAny(':', '_', '-'))) + pos++; + + return queue.substring(start, pos); + } + + /** + * Consume a CSS element selector (tag name, but | instead of : for namespaces, to not conflict with :pseudo selects). + * + * @return tag name + */ + public String consumeElementSelector() { + int start = pos; + while (!isEmpty() && (matchesWord() || matchesAny('|', '_', '-'))) + pos++; + + return queue.substring(start, pos); + } + + /** + Consume a CSS identifier (ID or class) off the queue (letter, digit, -, _) + http://www.w3.org/TR/CSS2/syndata.html#value-def-identifier + @return identifier + */ + public String consumeCssIdentifier() { + int start = pos; + while (!isEmpty() && (matchesWord() || matchesAny('-', '_'))) + pos++; + + return queue.substring(start, pos); + } + + /** + Consume an attribute key off the queue (letter, digit, -, _, :") + @return attribute key + */ + public String consumeAttributeKey() { + int start = pos; + while (!isEmpty() && (matchesWord() || matchesAny('-', '_', ':'))) + pos++; + + return queue.substring(start, pos); + } + + /** + Consume and return whatever is left on the queue. + @return remained of queue. + */ + public String remainder() { + final String remainder = queue.substring(pos, queue.length()); + pos = queue.length(); + return remainder; + } + + @Override + public String toString() { + return queue.substring(pos); + } +} \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/http/UploadProgress.java b/blade-kit/src/main/java/com/blade/kit/http/UploadProgress.java new file mode 100644 index 000000000..da9c9a270 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/http/UploadProgress.java @@ -0,0 +1,22 @@ +package com.blade.kit.http; + +/** + * 上传进度回调接口 + * + * @author biezhi + * @since 1.0 + */ +public interface UploadProgress { + /** + * 上传数据的回调函数调用 + * + * @param uploaded 已经上传的字节数 + * @param total 字节总数 + */ + void onUpload(long uploaded, long total); + + UploadProgress DEFAULT = new UploadProgress() { + public void onUpload(long uploaded, long total) { + } + }; +} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/http/package-info.java b/blade-kit/src/main/java/com/blade/kit/http/package-info.java similarity index 50% rename from blade-kit/src/main/java/blade/kit/http/package-info.java rename to blade-kit/src/main/java/com/blade/kit/http/package-info.java index e2311401b..9c5032b49 100644 --- a/blade-kit/src/main/java/blade/kit/http/package-info.java +++ b/blade-kit/src/main/java/com/blade/kit/http/package-info.java @@ -1,4 +1,4 @@ -/** - * http网络请求包 - */ -package blade.kit.http; \ No newline at end of file +/** + * http网络请求包 + */ +package com.blade.kit.http; \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/io/AppendableWriter.java b/blade-kit/src/main/java/com/blade/kit/io/AppendableWriter.java new file mode 100644 index 000000000..253deeaab --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/io/AppendableWriter.java @@ -0,0 +1,103 @@ +package com.blade.kit.io; + +import static com.blade.kit.Assert.checkNotNull; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; +import java.io.Writer; + +/** + * Writer that places all output on an {@link Appendable} target. If the target + * is {@link Flushable} or {@link Closeable}, flush()es and close()s will also + * be delegated to the target. + * + * @author Alan Green + * @author Sebastian Kanthak + * @since 1.0 + */ +class AppendableWriter extends Writer { + private final Appendable target; + private boolean closed; + + /** + * Creates a new writer that appends everything it writes to {@code target}. + * + * @param target target to which to append output + */ + AppendableWriter(Appendable target) { + this.target = checkNotNull(target); + } + + /* + * Abstract methods from Writer + */ + + @Override public void write(char cbuf[], int off, int len) + throws IOException { + checkNotClosed(); + // It turns out that creating a new String is usually as fast, or faster + // than wrapping cbuf in a light-weight CharSequence. + target.append(new String(cbuf, off, len)); + } + + @Override public void flush() throws IOException { + checkNotClosed(); + if (target instanceof Flushable) { + ((Flushable) target).flush(); + } + } + + @Override public void close() throws IOException { + this.closed = true; + if (target instanceof Closeable) { + ((Closeable) target).close(); + } + } + + /* + * Override a few functions for performance reasons to avoid creating + * unnecessary strings. + */ + + @Override public void write(int c) throws IOException { + checkNotClosed(); + target.append((char) c); + } + + @Override public void write(String str) throws IOException { + checkNotClosed(); + target.append(str); + } + + @Override public void write(String str, int off, int len) throws IOException { + checkNotClosed(); + // tricky: append takes start, end pair... + target.append(str, off, off + len); + } + + @Override public Writer append(char c) throws IOException { + checkNotClosed(); + target.append(c); + return this; + } + + @Override public Writer append(CharSequence charSeq) throws IOException { + checkNotClosed(); + target.append(charSeq); + return this; + } + + @Override public Writer append(CharSequence charSeq, int start, int end) + throws IOException { + checkNotClosed(); + target.append(charSeq, start, end); + return this; + } + + private void checkNotClosed() throws IOException { + if (closed) { + throw new IOException("Cannot write to a closed writer."); + } + } +} diff --git a/blade-kit/src/main/java/blade/kit/io/ByteArray.java b/blade-kit/src/main/java/com/blade/kit/io/ByteArray.java similarity index 92% rename from blade-kit/src/main/java/blade/kit/io/ByteArray.java rename to blade-kit/src/main/java/com/blade/kit/io/ByteArray.java index 5fcc68d3b..86262d73f 100644 --- a/blade-kit/src/main/java/blade/kit/io/ByteArray.java +++ b/blade-kit/src/main/java/com/blade/kit/io/ByteArray.java @@ -1,11 +1,11 @@ -package blade.kit.io; +package com.blade.kit.io; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import blade.kit.Assert; +import com.blade.kit.Assert; /** * 代表一个byte数组。 diff --git a/blade-kit/src/main/java/blade/kit/io/ByteArrayOutputStream.java b/blade-kit/src/main/java/com/blade/kit/io/ByteArrayOutputStream.java similarity index 94% rename from blade-kit/src/main/java/blade/kit/io/ByteArrayOutputStream.java rename to blade-kit/src/main/java/com/blade/kit/io/ByteArrayOutputStream.java index 3feccac35..bf81dff73 100644 --- a/blade-kit/src/main/java/blade/kit/io/ByteArrayOutputStream.java +++ b/blade-kit/src/main/java/com/blade/kit/io/ByteArrayOutputStream.java @@ -1,11 +1,11 @@ -package blade.kit.io; +package com.blade.kit.io; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import blade.kit.IOKit; +import com.blade.kit.IOKit; /** * 非同步的ByteArrayOutputStream替换方案, 执行toByteArray() 方法时返回的是只读的内部字节数组, 避免了没有必要的字节复制. 本代码移植自IBM diff --git a/blade-kit/src/main/java/com/blade/kit/io/CharStreams.java b/blade-kit/src/main/java/com/blade/kit/io/CharStreams.java new file mode 100644 index 000000000..9e7373da1 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/io/CharStreams.java @@ -0,0 +1,273 @@ +package com.blade.kit.io; + + +import static com.blade.kit.Assert.checkNotNull; +import static com.blade.kit.Assert.checkPositionIndexes; + +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.CharBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.blade.kit.Assert; + +/** + * Provides utility methods for working with character streams. + * + *

All method parameters must be non-null unless documented otherwise. + * + *

Some of the methods in this class take arguments with a generic type of + * {@code Readable & Closeable}. A {@link java.io.Reader} implements both of + * those interfaces. Similarly for {@code Appendable & Closeable} and + * {@link java.io.Writer}. + * + * @author Chris Nokleberg + * @author Bin Zhu + * @author Colin Decker + * @since 1.0 + */ +public final class CharStreams { + private static final int BUF_SIZE = 0x800; // 2K chars (4K bytes) + + private CharStreams() {} + + /** + * Copies all characters between the {@link Readable} and {@link Appendable} + * objects. Does not close or flush either object. + * + * @param from the object to read from + * @param to the object to write to + * @return the number of characters copied + * @throws IOException if an I/O error occurs + */ + public static long copy(Readable from, Appendable to) throws IOException { + Assert.checkNotNull(from); + checkNotNull(to); + CharBuffer buf = CharBuffer.allocate(BUF_SIZE); + long total = 0; + while (from.read(buf) != -1) { + buf.flip(); + to.append(buf); + total += buf.remaining(); + buf.clear(); + } + return total; + } + + /** + * Reads all characters from a {@link Readable} object into a {@link String}. + * Does not close the {@code Readable}. + * + * @param r the object to read from + * @return a string containing all the characters + * @throws IOException if an I/O error occurs + */ + public static String toString(Readable r) throws IOException { + return toStringBuilder(r).toString(); + } + + /** + * Reads all characters from a {@link Readable} object into a new + * {@link StringBuilder} instance. Does not close the {@code Readable}. + * + * @param r the object to read from + * @return a {@link StringBuilder} containing all the characters + * @throws IOException if an I/O error occurs + */ + private static StringBuilder toStringBuilder(Readable r) throws IOException { + StringBuilder sb = new StringBuilder(); + copy(r, sb); + return sb; + } + + /** + * Reads all of the lines from a {@link Readable} object. The lines do + * not include line-termination characters, but do include other + * leading and trailing whitespace. + * + *

Does not close the {@code Readable}. If reading files or resources you + * should use the {@link Files#readLines} and {@link Resources#readLines} + * methods. + * + * @param r the object to read from + * @return a mutable {@link List} containing all the lines + * @throws IOException if an I/O error occurs + */ + public static List readLines(Readable r) throws IOException { + List result = new ArrayList(); + LineReader lineReader = new LineReader(r); + String line; + while ((line = lineReader.readLine()) != null) { + result.add(line); + } + return result; + } + + /** + * Streams lines from a {@link Readable} object, stopping when the processor + * returns {@code false} or all lines have been read and returning the result + * produced by the processor. Does not close {@code readable}. Note that this + * method may not fully consume the contents of {@code readable} if the + * processor stops processing early. + * + * @throws IOException if an I/O error occurs + * @since 14.0 + */ + public static T readLines( + Readable readable, LineProcessor processor) throws IOException { + checkNotNull(readable); + checkNotNull(processor); + + LineReader lineReader = new LineReader(readable); + String line; + while ((line = lineReader.readLine()) != null) { + if (!processor.processLine(line)) { + break; + } + } + return processor.getResult(); + } + + /** + * Discards {@code n} characters of data from the reader. This method + * will block until the full amount has been skipped. Does not close the + * reader. + * + * @param reader the reader to read from + * @param n the number of characters to skip + * @throws EOFException if this stream reaches the end before skipping all + * the characters + * @throws IOException if an I/O error occurs + */ + public static void skipFully(Reader reader, long n) throws IOException { + checkNotNull(reader); + while (n > 0) { + long amt = reader.skip(n); + if (amt == 0) { + // force a blocking read + if (reader.read() == -1) { + throw new EOFException(); + } + n--; + } else { + n -= amt; + } + } + } + + /** + * Returns a {@link Writer} that simply discards written chars. + * + * @since 15.0 + */ + public static Writer nullWriter() { + return NullWriter.INSTANCE; + } + + private static final class NullWriter extends Writer { + + private static final NullWriter INSTANCE = new NullWriter(); + + @Override + public void write(int c) { + } + + @Override + public void write(char[] cbuf) { + checkNotNull(cbuf); + } + + @Override + public void write(char[] cbuf, int off, int len) { + checkPositionIndexes(off, off + len, cbuf.length); + } + + @Override + public void write(String str) { + checkNotNull(str); + } + + @Override + public void write(String str, int off, int len) { + checkPositionIndexes(off, off + len, str.length()); + } + + @Override + public Writer append(CharSequence csq) { + checkNotNull(csq); + return this; + } + + @Override + public Writer append(CharSequence csq, int start, int end) { + checkPositionIndexes(start, end, csq.length()); + return this; + } + + @Override + public Writer append(char c) { + return this; + } + + @Override + public void flush() { + } + + @Override + public void close() { + } + + @Override + public String toString() { + return "CharStreams.nullWriter()"; + } + } + + /** + * Returns a Writer that sends all output to the given {@link Appendable} + * target. Closing the writer will close the target if it is {@link + * Closeable}, and flushing the writer will flush the target if it is {@link + * java.io.Flushable}. + * + * @param target the object to which output will be sent + * @return a new Writer object, unless target is a Writer, in which case the + * target is returned + */ + public static Writer asWriter(Appendable target) { + if (target instanceof Writer) { + return (Writer) target; + } + return new AppendableWriter(target); + } + + // TODO(user): Remove these once Input/OutputSupplier methods are removed + + static Reader asReader(final Readable readable) { + checkNotNull(readable); + if (readable instanceof Reader) { + return (Reader) readable; + } + return new Reader() { + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + return read(CharBuffer.wrap(cbuf, off, len)); + } + + @Override + public int read(CharBuffer target) throws IOException { + return readable.read(target); + } + + @Override + public void close() throws IOException { + if (readable instanceof Closeable) { + ((Closeable) readable).close(); + } + } + }; + } +} diff --git a/blade-kit/src/main/java/blade/kit/io/FastByteArrayOutputStream.java b/blade-kit/src/main/java/com/blade/kit/io/FastByteArrayOutputStream.java similarity index 94% rename from blade-kit/src/main/java/blade/kit/io/FastByteArrayOutputStream.java rename to blade-kit/src/main/java/com/blade/kit/io/FastByteArrayOutputStream.java index 9a935a00e..4e1f1a9c4 100644 --- a/blade-kit/src/main/java/blade/kit/io/FastByteArrayOutputStream.java +++ b/blade-kit/src/main/java/com/blade/kit/io/FastByteArrayOutputStream.java @@ -1,4 +1,4 @@ -package blade.kit.io; +package com.blade.kit.io; import java.io.IOException; import java.io.OutputStream; diff --git a/blade-kit/src/main/java/blade/kit/io/FastByteBuffer.java b/blade-kit/src/main/java/com/blade/kit/io/FastByteBuffer.java similarity index 95% rename from blade-kit/src/main/java/blade/kit/io/FastByteBuffer.java rename to blade-kit/src/main/java/com/blade/kit/io/FastByteBuffer.java index 1c63c0e6d..e28efe1a8 100644 --- a/blade-kit/src/main/java/blade/kit/io/FastByteBuffer.java +++ b/blade-kit/src/main/java/com/blade/kit/io/FastByteBuffer.java @@ -1,6 +1,6 @@ -package blade.kit.io; +package com.blade.kit.io; -import blade.kit.Assert; +import com.blade.kit.Assert; /** * 快速缓冲,将数据存放在缓冲集中,取代以往的单一数组 diff --git a/blade-kit/src/main/java/com/blade/kit/io/LineBuffer.java b/blade-kit/src/main/java/com/blade/kit/io/LineBuffer.java new file mode 100644 index 000000000..c539f4c00 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/io/LineBuffer.java @@ -0,0 +1,104 @@ +package com.blade.kit.io; + +import java.io.IOException; + +/** + * Package-protected abstract class that implements the line reading + * algorithm used by {@link LineReader}. Line separators are per {@link + * java.io.BufferedReader}: line feed, carriage return, or carriage + * return followed immediately by a linefeed. + * + *

Subclasses must implement {@link #handleLine}, call {@link #add} + * to pass character data, and call {@link #finish} at the end of stream. + * + * @author Chris Nokleberg + * @since 1.0 + */ +abstract class LineBuffer { + /** Holds partial line contents. */ + private StringBuilder line = new StringBuilder(); + /** Whether a line ending with a CR is pending processing. */ + private boolean sawReturn; + + /** + * Process additional characters from the stream. When a line separator + * is found the contents of the line and the line separator itself + * are passed to the abstract {@link #handleLine} method. + * + * @param cbuf the character buffer to process + * @param off the offset into the buffer + * @param len the number of characters to process + * @throws IOException if an I/O error occurs + * @see #finish + */ + protected void add(char[] cbuf, int off, int len) throws IOException { + int pos = off; + if (sawReturn && len > 0) { + // Last call to add ended with a CR; we can handle the line now. + if (finishLine(cbuf[pos] == '\n')) { + pos++; + } + } + + int start = pos; + for (int end = off + len; pos < end; pos++) { + switch (cbuf[pos]) { + case '\r': + line.append(cbuf, start, pos - start); + sawReturn = true; + if (pos + 1 < end) { + if (finishLine(cbuf[pos + 1] == '\n')) { + pos++; + } + } + start = pos + 1; + break; + + case '\n': + line.append(cbuf, start, pos - start); + finishLine(true); + start = pos + 1; + break; + + default: + // do nothing + } + } + line.append(cbuf, start, off + len - start); + } + + /** Called when a line is complete. */ + private boolean finishLine(boolean sawNewline) throws IOException { + handleLine(line.toString(), sawReturn + ? (sawNewline ? "\r\n" : "\r") + : (sawNewline ? "\n" : "")); + line = new StringBuilder(); + sawReturn = false; + return sawNewline; + } + + /** + * Subclasses must call this method after finishing character processing, + * in order to ensure that any unterminated line in the buffer is + * passed to {@link #handleLine}. + * + * @throws IOException if an I/O error occurs + */ + protected void finish() throws IOException { + if (sawReturn || line.length() > 0) { + finishLine(false); + } + } + + /** + * Called for each line found in the character data passed to + * {@link #add}. + * + * @param line a line of text (possibly empty), without any line separators + * @param end the line separator; one of {@code "\r"}, {@code "\n"}, + * {@code "\r\n"}, or {@code ""} + * @throws IOException if an I/O error occurs + */ + protected abstract void handleLine(String line, String end) + throws IOException; +} diff --git a/blade-kit/src/main/java/com/blade/kit/io/LineProcessor.java b/blade-kit/src/main/java/com/blade/kit/io/LineProcessor.java new file mode 100644 index 000000000..4c87d9a49 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/io/LineProcessor.java @@ -0,0 +1,26 @@ +package com.blade.kit.io; + +import java.io.IOException; + +/** + * A callback to be used with the streaming {@code readLines} methods. + * + *

{@link #processLine} will be called for each line that is read, and + * should return {@code false} when you want to stop processing. + * + * @author Miles Barr + * @since 1.0 + */ +public interface LineProcessor { + + /** + * This method will be called once for each line. + * + * @param line the line read from the input, without delimiter + * @return true to continue processing, false to stop + */ + boolean processLine(String line) throws IOException; + + /** Return the result of processing all the lines. */ + T getResult(); +} diff --git a/blade-kit/src/main/java/com/blade/kit/io/LineReader.java b/blade-kit/src/main/java/com/blade/kit/io/LineReader.java new file mode 100644 index 000000000..b29f68d14 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/io/LineReader.java @@ -0,0 +1,67 @@ +package com.blade.kit.io; + +import static com.blade.kit.Assert.checkNotNull; + +import java.io.IOException; +import java.io.Reader; +import java.nio.CharBuffer; +import java.util.LinkedList; +import java.util.Queue; +/** + * A class for reading lines of text. Provides the same functionality + * as {@link java.io.BufferedReader#readLine()} but for all {@link Readable} + * objects, not just instances of {@link Reader}. + * + * @author Chris Nokleberg + * @since 1.0 + */ +public final class LineReader { + private final Readable readable; + private final Reader reader; + private final char[] buf = new char[0x1000]; // 4K + private final CharBuffer cbuf = CharBuffer.wrap(buf); + + private final Queue lines = new LinkedList(); + private final LineBuffer lineBuf = new LineBuffer() { + @Override protected void handleLine(String line, String end) { + lines.add(line); + } + }; + + /** + * Creates a new instance that will read lines from the given + * {@code Readable} object. + */ + public LineReader(Readable readable) { + this.readable = checkNotNull(readable); + this.reader = (readable instanceof Reader) ? (Reader) readable : null; + } + + /** + * Reads a line of text. A line is considered to be terminated by any + * one of a line feed ({@code '\n'}), a carriage return + * ({@code '\r'}), or a carriage return followed immediately by a linefeed + * ({@code "\r\n"}). + * + * @return a {@code String} containing the contents of the line, not + * including any line-termination characters, or {@code null} if the + * end of the stream has been reached. + * @throws IOException if an I/O error occurs + */ + public String readLine() throws IOException { + while (lines.peek() == null) { + cbuf.clear(); + // The default implementation of Reader#read(CharBuffer) allocates a + // temporary char[], so we call Reader#read(char[], int, int) instead. + int read = (reader != null) + ? reader.read(buf, 0, buf.length) + : readable.read(cbuf); + if (read == -1) { + lineBuf.finish(); + break; + } + lineBuf.add(buf, 0, read); + } + return lines.poll(); + } +} diff --git a/blade-kit/src/main/java/blade/kit/io/StringBuilderWriter.java b/blade-kit/src/main/java/com/blade/kit/io/StringBuilderWriter.java similarity index 96% rename from blade-kit/src/main/java/blade/kit/io/StringBuilderWriter.java rename to blade-kit/src/main/java/com/blade/kit/io/StringBuilderWriter.java index 666002885..03b58a044 100644 --- a/blade-kit/src/main/java/blade/kit/io/StringBuilderWriter.java +++ b/blade-kit/src/main/java/com/blade/kit/io/StringBuilderWriter.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.kit.io; +package com.blade.kit.io; import java.io.Serializable; import java.io.Writer; diff --git a/blade-kit/src/main/java/com/blade/kit/io/package-info.java b/blade-kit/src/main/java/com/blade/kit/io/package-info.java new file mode 100644 index 000000000..b1a309d49 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/io/package-info.java @@ -0,0 +1,4 @@ +/** + * IO操作包 + */ +package com.blade.kit.io; \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/json/JSON.java b/blade-kit/src/main/java/com/blade/kit/json/JSON.java new file mode 100644 index 000000000..bc4412733 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/JSON.java @@ -0,0 +1,409 @@ +/******************************************************************************* + * Copyright (c) 2015, 2016 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + +import java.io.IOException; +import java.io.Reader; + +/** + * This class serves as the entry point to the minimal-json API. + *

+ * To parse a given JSON input, use the parse() + * methods like in this example: + *

+ * + *
+ * JsonObject object = Json.parse(string).asObject();
+ * 
+ *

+ * To create a JSON data structure to be serialized, use the + * methods value(), array(), and object() + * . For example, the following snippet will produce the JSON string + * {"foo": 23, "bar": true}: + *

+ * + *
+ * String string = Json.object().add("foo", 23).add("bar", true).toString();
+ * 
+ *

+ * To create a JSON array from a given Java array, you can use one of the + * array() methods with varargs parameters: + *

+ * + *
+ * String[] names = ...
+ * JsonArray array = Json.array(names);
+ * 
+ */ +public final class JSON { + + private JSON() { + // not meant to be instantiated + } + + /** + * Represents the JSON literal null. + */ + public static final JSONValue NULL = new JSONLiteral("null"); + + /** + * Represents the JSON literal true. + */ + public static final JSONValue TRUE = new JSONLiteral("true"); + + /** + * Represents the JSON literal false. + */ + public static final JSONValue FALSE = new JSONLiteral("false"); + + /** + * Returns a JsonValue instance that represents the given int + * value. + * + * @param value + * the value to get a JSON representation for + * @return a JSON value that represents the given value + */ + public static JSONValue value(Integer value) { + return new JSONNumber(Integer.toString(value, 10)); + } + + public static JSONValue value(Object value) { + return JSONHelper.toJSONValue(value); + } + + /** + * Returns a JsonValue instance that represents the given long + * value. + * + * @param value + * the value to get a JSON representation for + * @return a JSON value that represents the given value + */ + public static JSONValue value(Long value) { + return new JSONNumber(Long.toString(value, 10)); + } + + public static JSONValue value(Byte value) { + return new JSONNumber(Byte.toString(value)); + } + + /** + * Returns a JsonValue instance that represents the given float + * value. + * + * @param value + * the value to get a JSON representation for + * @return a JSON value that represents the given value + */ + public static JSONValue value(Float value) { + if (Float.isInfinite(value) || Float.isNaN(value)) { + throw new IllegalArgumentException("Infinite and NaN values not permitted in JSON"); + } + return new JSONNumber(cutOffPointZero(Float.toString(value))); + } + + /** + * Returns a JsonValue instance that represents the given + * double value. + * + * @param value + * the value to get a JSON representation for + * @return a JSON value that represents the given value + */ + public static JSONValue value(Double value) { + if (Double.isInfinite(value) || Double.isNaN(value)) { + throw new IllegalArgumentException("Infinite and NaN values not permitted in JSON"); + } + return new JSONNumber(cutOffPointZero(Double.toString(value))); + } + + /** + * Returns a JsonValue instance that represents the given string. + * + * @param string + * the string to get a JSON representation for + * @return a JSON value that represents the given string + */ + public static JSONValue value(String string) { + return string == null ? NULL : new JSONString(string); + } + + /** + * Returns a JsonValue instance that represents the given + * boolean value. + * + * @param value + * the value to get a JSON representation for + * @return a JSON value that represents the given value + */ + public static JSONValue value(Boolean value) { + return value ? TRUE : FALSE; + } + + /** + * Creates a new empty JsonArray. This is equivalent to creating a new + * JsonArray using the constructor. + * + * @return a new empty JSON array + */ + public static JSONValue array() { + return new JSONArray(); + } + + /** + * Creates a new JsonArray that contains the JSON representations of the + * given int values. + * + * @param values + * the values to be included in the new JSON array + * @return a new JSON array that contains the given values + */ + public static JSONArray array(Integer... values) { + if (values == null) { + throw new NullPointerException("values is null"); + } + JSONArray array = new JSONArray(); + for (int value : values) { + array.add(value); + } + return array; + } + + /** + * Creates a new JsonArray that contains the JSON representations of the + * given long values. + * + * @param values + * the values to be included in the new JSON array + * @return a new JSON array that contains the given values + */ + public static JSONArray array(Long... values) { + if (values == null) { + throw new NullPointerException("values is null"); + } + JSONArray array = new JSONArray(); + for (long value : values) { + array.add(value); + } + return array; + } + + /** + * Creates a new JsonArray that contains the JSON representations of the + * given float values. + * + * @param values + * the values to be included in the new JSON array + * @return a new JSON array that contains the given values + */ + public static JSONArray array(Float... values) { + if (values == null) { + throw new NullPointerException("values is null"); + } + JSONArray array = new JSONArray(); + for (float value : values) { + array.add(value); + } + return array; + } + + /** + * Creates a new JsonArray that contains the JSON representations of the + * given double values. + * + * @param values + * the values to be included in the new JSON array + * @return a new JSON array that contains the given values + */ + public static JSONArray array(Double... values) { + if (values == null) { + throw new NullPointerException("values is null"); + } + JSONArray array = new JSONArray(); + for (double value : values) { + array.add(value); + } + return array; + } + + /** + * Creates a new JsonArray that contains the JSON representations of the + * given boolean values. + * + * @param values + * the values to be included in the new JSON array + * @return a new JSON array that contains the given values + */ + public static JSONArray array(Boolean... values) { + if (values == null) { + throw new NullPointerException("values is null"); + } + JSONArray array = new JSONArray(); + for (boolean value : values) { + array.add(value); + } + return array; + } + + /** + * Creates a new JsonArray that contains the JSON representations of the + * given strings. + * + * @param strings + * the strings to be included in the new JSON array + * @return a new JSON array that contains the given strings + */ + public static JSONArray array(String... strings) { + if (strings == null) { + throw new NullPointerException("values is null"); + } + JSONArray array = new JSONArray(); + for (String value : strings) { + array.add(value); + } + return array; + } + + /** + * Creates a new empty JsonObject. This is equivalent to creating a new + * JsonObject using the constructor. + * + * @return a new empty JSON object + */ + public static JSONObject create() { + return new JSONObject(); + } + + /** + * Parses the given input string as JSON. The input must contain a valid + * JSON value, optionally padded with whitespace. + * + * @param string + * the input string, must be valid JSON + * @return a value that represents the parsed JSON + * @throws ParseException + * if the input is not valid JSON + */ + public static JSONValue parse(String string) { + if (string == null) { + throw new NullPointerException("string is null"); + } + DefaultHandler handler = new DefaultHandler(); + new JSONParser(handler).parse(string); + return handler.getValue(); + } + + /** + * Reads the entire input from the given reader and parses it as JSON. The + * input must contain a valid JSON value, optionally padded with whitespace. + *

+ * Characters are read in chunks into an input buffer. Hence, wrapping a + * reader in an additional BufferedReader likely won't improve + * reading performance. + *

+ * + * @param reader + * the reader to read the JSON value from + * @return a value that represents the parsed JSON + * @throws IOException + * if an I/O error occurs in the reader + * @throws ParseException + * if the input is not valid JSON + */ + public static JSONValue parse(Reader reader) throws IOException { + if (reader == null) { + throw new NullPointerException("reader is null"); + } + DefaultHandler handler = new DefaultHandler(); + new JSONParser(handler).parse(reader); + return handler.getValue(); + } + + private static String cutOffPointZero(String string) { + if (string.endsWith(".0")) { + return string.substring(0, string.length() - 2); + } + return string; + } + + static class DefaultHandler extends JSONHandler { + + protected JSONValue value; + + @Override + public JSONArray startArray() { + return new JSONArray(); + } + + @Override + public JSONObject startObject() { + return new JSONObject(); + } + + @Override + public void endNull() { + value = NULL; + } + + @Override + public void endBoolean(boolean bool) { + value = bool ? TRUE : FALSE; + } + + @Override + public void endString(String string) { + value = new JSONString(string); + } + + @Override + public void endNumber(String string) { + value = new JSONNumber(string); + } + + @Override + public void endArray(JSONArray array) { + value = array; + } + + @Override + public void endObject(JSONObject object) { + value = object; + } + + @Override + public void endArrayValue(JSONArray array) { + array.add(value); + } + + @Override + public void endObjectValue(JSONObject object, String name) { + object.put(name, value); + } + + JSONValue getValue() { + return value; + } + + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/json/JSONArray.java b/blade-kit/src/main/java/com/blade/kit/json/JSONArray.java new file mode 100644 index 000000000..f4b1217ab --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/JSONArray.java @@ -0,0 +1,472 @@ +/******************************************************************************* + * Copyright (c) 2013, 2015 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Represents a JSON array, an ordered collection of JSON values. + *

+ * Elements can be added using the add(...) methods which accept + * instances of {@link JSONValue}, strings, primitive numbers, and boolean + * values. To replace an element of an array, use the set(int, ...) + * methods. + *

+ *

+ * Elements can be accessed by their index using {@link #get(int)}. This class + * also supports iterating over the elements in document order using an + * {@link #iterator()} or an enhanced for loop: + *

+ * + *
+ * for (JsonValue value : jsonArray) {
+ *   ...
+ * }
+ * 
+ *

+ * An equivalent {@link List} can be obtained from the method {@link #values()}. + *

+ *

+ * Note that this class is not thread-safe. If multiple threads + * access a JsonArray instance concurrently, while at least one of + * these threads modifies the contents of this array, access to the instance + * must be synchronized externally. Failure to do so may lead to an inconsistent + * state. + *

+ *

+ * This class is not supposed to be extended by clients. + *

+ */ +@SuppressWarnings("serial") // use default serial UID +public class JSONArray extends JSONValue implements Iterable { + + private final List values; + + /** + * Creates a new empty JsonArray. + */ + public JSONArray() { + values = new ArrayList(); + } + + /** + * Creates a new JsonArray with the contents of the specified JSON array. + * + * @param array + * the JsonArray to get the initial contents from, must not be + * null + */ + public JSONArray(JSONArray array) { + this(array, false); + } + + private JSONArray(JSONArray array, boolean unmodifiable) { + if (array == null) { + throw new NullPointerException("array is null"); + } + if (unmodifiable) { + values = Collections.unmodifiableList(array.values); + } else { + values = new ArrayList(array.values); + } + } + + /** + * Returns an unmodifiable wrapper for the specified JsonArray. This method + * allows to provide read-only access to a JsonArray. + *

+ * The returned JsonArray is backed by the given array and reflects + * subsequent changes. Attempts to modify the returned JsonArray result in + * an UnsupportedOperationException. + *

+ * + * @param array + * the JsonArray for which an unmodifiable JsonArray is to be + * returned + * @return an unmodifiable view of the specified JsonArray + */ + public static JSONArray unmodifiableArray(JSONArray array) { + return new JSONArray(array, true); + } + + /** + * Appends the JSON representation of the specified int value + * to the end of this array. + * + * @param value + * the value to add to the array + * @return the array itself, to enable method chaining + */ + public JSONArray add(int value) { + values.add(JSON.value(value)); + return this; + } + + /** + * Appends the JSON representation of the specified long value + * to the end of this array. + * + * @param value + * the value to add to the array + * @return the array itself, to enable method chaining + */ + public JSONArray add(long value) { + values.add(JSON.value(value)); + return this; + } + + /** + * Appends the JSON representation of the specified float value + * to the end of this array. + * + * @param value + * the value to add to the array + * @return the array itself, to enable method chaining + */ + public JSONArray add(float value) { + values.add(JSON.value(value)); + return this; + } + + /** + * Appends the JSON representation of the specified double + * value to the end of this array. + * + * @param value + * the value to add to the array + * @return the array itself, to enable method chaining + */ + public JSONArray add(double value) { + values.add(JSON.value(value)); + return this; + } + + /** + * Appends the JSON representation of the specified boolean + * value to the end of this array. + * + * @param value + * the value to add to the array + * @return the array itself, to enable method chaining + */ + public JSONArray add(boolean value) { + values.add(JSON.value(value)); + return this; + } + + /** + * Appends the JSON representation of the specified string to the end of + * this array. + * + * @param value + * the string to add to the array + * @return the array itself, to enable method chaining + */ + public JSONArray add(String value) { + values.add(JSON.value(value)); + return this; + } + + public JSONArray add(Object value) { + values.add(JSON.value(value)); + return this; + } + + /** + * Appends the specified JSON value to the end of this array. + * + * @param value + * the JsonValue to add to the array, must not be + * null + * @return the array itself, to enable method chaining + */ + public JSONArray add(JSONValue value) { + if (value == null) { + throw new NullPointerException("value is null"); + } + values.add(value); + return this; + } + + /** + * Replaces the element at the specified position in this array with the + * JSON representation of the specified int value. + * + * @param index + * the index of the array element to replace + * @param value + * the value to be stored at the specified array position + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 + * or index >= size + */ + public JSONArray set(int index, int value) { + values.set(index, JSON.value(value)); + return this; + } + + /** + * Replaces the element at the specified position in this array with the + * JSON representation of the specified long value. + * + * @param index + * the index of the array element to replace + * @param value + * the value to be stored at the specified array position + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 + * or index >= size + */ + public JSONArray set(int index, long value) { + values.set(index, JSON.value(value)); + return this; + } + + /** + * Replaces the element at the specified position in this array with the + * JSON representation of the specified float value. + * + * @param index + * the index of the array element to replace + * @param value + * the value to be stored at the specified array position + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 + * or index >= size + */ + public JSONArray set(int index, float value) { + values.set(index, JSON.value(value)); + return this; + } + + /** + * Replaces the element at the specified position in this array with the + * JSON representation of the specified double value. + * + * @param index + * the index of the array element to replace + * @param value + * the value to be stored at the specified array position + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 + * or index >= size + */ + public JSONArray set(int index, double value) { + values.set(index, JSON.value(value)); + return this; + } + + /** + * Replaces the element at the specified position in this array with the + * JSON representation of the specified boolean value. + * + * @param index + * the index of the array element to replace + * @param value + * the value to be stored at the specified array position + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 + * or index >= size + */ + public JSONArray set(int index, boolean value) { + values.set(index, JSON.value(value)); + return this; + } + + /** + * Replaces the element at the specified position in this array with the + * JSON representation of the specified string. + * + * @param index + * the index of the array element to replace + * @param value + * the string to be stored at the specified array position + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 + * or index >= size + */ + public JSONArray set(int index, String value) { + values.set(index, JSON.value(value)); + return this; + } + + /** + * Replaces the element at the specified position in this array with the + * specified JSON value. + * + * @param index + * the index of the array element to replace + * @param value + * the value to be stored at the specified array position, must + * not be null + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 + * or index >= size + */ + public JSONArray set(int index, JSONValue value) { + if (value == null) { + throw new NullPointerException("value is null"); + } + values.set(index, value); + return this; + } + + /** + * Removes the element at the specified index from this array. + * + * @param index + * the index of the element to remove + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 + * or index >= size + */ + public JSONArray remove(int index) { + values.remove(index); + return this; + } + + /** + * Returns the number of elements in this array. + * + * @return the number of elements in this array + */ + public int size() { + return values.size(); + } + + /** + * Returns true if this array contains no elements. + * + * @return true if this array contains no elements + */ + public boolean isEmpty() { + return values.isEmpty(); + } + + /** + * Returns the value of the element at the specified position in this array. + * + * @param index + * the index of the array element to return + * @return the value of the element at the specified position + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 + * or index >= size + */ + public JSONValue get(int index) { + return values.get(index); + } + + /** + * Returns a list of the values in this array in document order. The + * returned list is backed by this array and will reflect subsequent + * changes. It cannot be used to modify this array. Attempts to modify the + * returned list will result in an exception. + * + * @return a list of the values in this array + */ + public List values() { + return Collections.unmodifiableList(values); + } + + /** + * Returns an iterator over the values of this array in document order. The + * returned iterator cannot be used to modify this array. + * + * @return an iterator over the values of this array + */ + public Iterator iterator() { + final Iterator iterator = values.iterator(); + return new Iterator() { + + public boolean hasNext() { + return iterator.hasNext(); + } + + public JSONValue next() { + return iterator.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + void write(JSONWriter writer) throws IOException { + writer.writeArrayOpen(); + Iterator iterator = iterator(); + boolean first = true; + while (iterator.hasNext()) { + if (!first) { + writer.writeArraySeparator(); + } + iterator.next().write(writer); + first = false; + } + writer.writeArrayClose(); + } + + @Override + public boolean isArray() { + return true; + } + + @Override + public JSONArray asArray() { + return this; + } + + @Override + public int hashCode() { + return values.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + JSONArray other = (JSONArray) object; + return values.equals(other.values); + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/json/JSONHandler.java b/blade-kit/src/main/java/com/blade/kit/json/JSONHandler.java new file mode 100644 index 000000000..9a735ac2b --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/JSONHandler.java @@ -0,0 +1,263 @@ +/******************************************************************************* + * Copyright (c) 2016 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + + +/** + * A handler for parser events. Instances of this class can be given to a {@link JSONParser}. The + * parser will then call the methods of the given handler while reading the input. + *

+ * The default implementations of these methods do nothing. Subclasses may override only those + * methods they are interested in. They can use getLocation() to access the current + * character position of the parser at any point. The start* methods will be called + * while the location points to the first character of the parsed element. The end* + * methods will be called while the location points to the character position that directly follows + * the last character of the parsed element. Example: + *

+ * + *
+ * ["lorem ipsum"]
+ *  ^            ^
+ *  startString  endString
+ * 
+ *

+ * Subclasses that build an object representation of the parsed JSON can return arbitrary handler + * objects for JSON arrays and JSON objects in {@link #startArray()} and {@link #startObject()}. + * These handler objects will then be provided in all subsequent parser events for this particular + * array or object. They can be used to keep track the elements of a JSON array or object. + *

+ * + * @param + * The type of handlers used for JSON arrays + * @param + * The type of handlers used for JSON objects + * @see JSONParser + */ +public abstract class JSONHandler { + + JSONParser parser; + + /** + * Returns the current parser location. + * + * @return the current parser location + */ + protected Location getLocation() { + return parser.getLocation(); + } + + /** + * Indicates the beginning of a null literal in the JSON input. This method will be + * called when reading the first character of the literal. + */ + public void startNull() { + } + + /** + * Indicates the end of a null literal in the JSON input. This method will be called + * after reading the last character of the literal. + */ + public void endNull() { + } + + /** + * Indicates the beginning of a boolean literal (true or false) in the + * JSON input. This method will be called when reading the first character of the literal. + */ + public void startBoolean() { + } + + /** + * Indicates the end of a boolean literal (true or false) in the JSON + * input. This method will be called after reading the last character of the literal. + * + * @param value + * the parsed boolean value + */ + public void endBoolean(boolean value) { + } + + /** + * Indicates the beginning of a string in the JSON input. This method will be called when reading + * the opening double quote character ('"'). + */ + public void startString() { + } + + /** + * Indicates the end of a string in the JSON input. This method will be called after reading the + * closing double quote character ('"'). + * + * @param string + * the parsed string + */ + public void endString(String string) { + } + + /** + * Indicates the beginning of a number in the JSON input. This method will be called when reading + * the first character of the number. + */ + public void startNumber() { + } + + /** + * Indicates the end of a number in the JSON input. This method will be called after reading the + * last character of the number. + * + * @param string + * the parsed number string + */ + public void endNumber(String string) { + } + + /** + * Indicates the beginning of an array in the JSON input. This method will be called when reading + * the opening square bracket character ('['). + *

+ * This method may return an object to handle subsequent parser events for this array. This array + * handler will then be provided in all calls to {@link #startArrayValue(Object) + * startArrayValue()}, {@link #endArrayValue(Object) endArrayValue()}, and + * {@link #endArray(Object) endArray()} for this array. + *

+ * + * @return a handler for this array, or null if not needed + */ + public A startArray() { + return null; + } + + /** + * Indicates the end of an array in the JSON input. This method will be called after reading the + * closing square bracket character (']'). + * + * @param array + * the array handler returned from {@link #startArray()}, or null if not + * provided + */ + public void endArray(A array) { + } + + /** + * Indicates the beginning of an array element in the JSON input. This method will be called when + * reading the first character of the element, just before the call to the start + * method for the specific element type ({@link #startString()}, {@link #startNumber()}, etc.). + * + * @param array + * the array handler returned from {@link #startArray()}, or null if not + * provided + */ + public void startArrayValue(A array) { + } + + /** + * Indicates the end of an array element in the JSON input. This method will be called after + * reading the last character of the element value, just after the end method for the + * specific element type (like {@link #endString(String) endString()}, {@link #endNumber(String) + * endNumber()}, etc.). + * + * @param array + * the array handler returned from {@link #startArray()}, or null if not + * provided + */ + public void endArrayValue(A array) { + } + + /** + * Indicates the beginning of an object in the JSON input. This method will be called when reading + * the opening curly bracket character ('{'). + *

+ * This method may return an object to handle subsequent parser events for this object. This + * object handler will be provided in all calls to {@link #startObjectName(Object) + * startObjectName()}, {@link #endObjectName(Object, String) endObjectName()}, + * {@link #startObjectValue(Object, String) startObjectValue()}, + * {@link #endObjectValue(Object, String) endObjectValue()}, and {@link #endObject(Object) + * endObject()} for this object. + *

+ * + * @return a handler for this object, or null if not needed + */ + public O startObject() { + return null; + } + + /** + * Indicates the end of an object in the JSON input. This method will be called after reading the + * closing curly bracket character ('}'). + * + * @param object + * the object handler returned from {@link #startObject()}, or null if not provided + */ + public void endObject(O object) { + } + + /** + * Indicates the beginning of the name of an object member in the JSON input. This method will be + * called when reading the opening quote character ('"') of the member name. + * + * @param object + * the object handler returned from {@link #startObject()}, or null if not + * provided + */ + public void startObjectName(O object) { + } + + /** + * Indicates the end of an object member name in the JSON input. This method will be called after + * reading the closing quote character ('"') of the member name. + * + * @param object + * the object handler returned from {@link #startObject()}, or null if not provided + * @param name + * the parsed member name + */ + public void endObjectName(O object, String name) { + } + + /** + * Indicates the beginning of the name of an object member in the JSON input. This method will be + * called when reading the opening quote character ('"') of the member name. + * + * @param object + * the object handler returned from {@link #startObject()}, or null if not + * provided + * @param name + * the member name + */ + public void startObjectValue(O object, String name) { + } + + /** + * Indicates the end of an object member value in the JSON input. This method will be called after + * reading the last character of the member value, just after the end method for the + * specific member type (like {@link #endString(String) endString()}, {@link #endNumber(String) + * endNumber()}, etc.). + * + * @param object + * the object handler returned from {@link #startObject()}, or null if not provided + * @param name + * the parsed member name + */ + public void endObjectValue(O object, String name) { + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/json/JSONHelper.java b/blade-kit/src/main/java/com/blade/kit/json/JSONHelper.java new file mode 100644 index 000000000..883b233a3 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/JSONHelper.java @@ -0,0 +1,256 @@ +package com.blade.kit.json; + +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.blade.kit.StringKit; + +public class JSONHelper { + + private static final Logger LOGGER = LoggerFactory.getLogger(JSONHelper.class); + + public static Object toJSONObject(JSONValue value) { + if(value.isBoolean()) + return value.asBoolean(); + else if(value.isNumber()) + return value.asInt(); + else if(value.isString()) + return value.asString(); + else if(value.isArray()) + return jsonArrayAsList(value.asArray()); + else if(value.isObject()) + return jsonObjectAsMap(value.asJSONObject()); + else if(value.isBean()) + return jsonObjectAsMap(value.asJSONObject()); + else return null; + } + + public static T toBean(String json, Class clazz) { + JSONObject jsonObject = JSON.parse(json).asJSONObject(); + if(null == jsonObject){ + return null; + } + + T object = null; + try { + object = clazz.newInstance(); + + /*Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + if(field.getModifiers() == 2){ + String key = field.getName(); + if (jsonObject.contains(key)) { + JSONValue value = jsonObject.get(key); + if(null != value){ + field.setAccessible(true); + field.set(object, value.asString()); + } + } + } + }*/ + + BeanInfo beanInfo = Introspector.getBeanInfo(clazz); + PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); + for (PropertyDescriptor property : propertyDescriptors) { + String name = property.getName(); + if (jsonObject.contains(name)) { + Object value = getValue(property.getPropertyType(), jsonObject, name); + if(null != value){ + // 得到property对应的setter方法 + Method setter = property.getWriteMethod(); + setter.invoke(object, value); + } + } + } + } catch (Exception e) { + LOGGER.error("jsonobject covert to bean error", e); + } + return object; + } + + private static Object getValue(Class type, JSONObject jsonObject, String name){ + if(null != type && null != jsonObject && StringKit.isNotBlank(name)){ + if(type.equals(String.class)){ + return jsonObject.getString(name); + } + if(type.equals(Long.class) || type.equals(long.class)){ + return jsonObject.getLong(name); + } + if(type.equals(Double.class) || type.equals(double.class)){ + return jsonObject.getDouble(name); + } + if(type.equals(Boolean.class) || type.equals(boolean.class)){ + return jsonObject.getBoolean(name); + } + } + return null; + } + + public static Set jsonArrayAsSet(JSONArray array) { + Set set = new HashSet(); + for(JSONValue value:array) + set.add(toJSONObject(value)); + return set; + } + + public static List jsonArrayAsList(JSONArray array) { + List list = new ArrayList(array.size()); + for(JSONValue element:array) + list.add(toJSONObject(element)); + return list; + } + + public static Map jsonObjectAsMap(JSONObject object) { + Map map = new HashMap(object.size(), 1.f); + for(JSONObject.Member member:object) + map.put(member.getName(), toJSONObject(member.getValue())); + return map; + } + + public static JSONValue toJSONValue(Object object) { + if (object == null) + return JSON.NULL; + else if (object instanceof Boolean) + return JSON.value((Boolean) object); + else if (object instanceof Integer) + return JSON.value((Integer) object); + else if (object instanceof Long) + return JSON.value((Long) object); + else if (object instanceof Float) + return JSON.value((Float) object); + else if (object instanceof Double) + return JSON.value((Double) object); + else if (object instanceof String) + return JSON.value((String) object); + else if (object instanceof Byte) + return JSON.value((Byte) object); + else if (object instanceof Collection) + return toJSONArray((Collection) object); + else if (object instanceof Map) + return mapAsJsonObject((Map) object); + else if (object instanceof JSONObject) + return ((JSONObject)object); + else if (object instanceof JSONArray) + return ((JSONArray)object); + else + return toJSONObject(object); + } + + public static JSONArray toJSONArray(Collection collection) { + JSONArray array = new JSONArray(); + for(Object element:collection) + array.add(toJSONValue(element)); + return array; + } + + public static JSONObject mapAsJsonObject(Map map) { + JSONObject object = new JSONObject(); + for(Entry entry:map.entrySet()) { + object.put(String.valueOf(entry.getKey()), toJSONValue(entry.getValue())); + } + return object; + } + + public static JSONObject toJSONObject(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(); + + if(null == methods || methods.length == 0){ + return null; + } + + Map map = new HashMap(); + + for (int i = 0, len = methods.length; i < len; i ++) { + try { + Method method = methods[i]; + if (Modifier.isPublic(method.getModifiers())) { + String name = method.getName(); + String key = ""; + if (name.startsWith("get")) { + if ("getClass".equals(name) || "getDeclaringClass".equals(name)) { + key = ""; + } else { + key = name.substring(3); + } + } else if (name.startsWith("is")) { + key = name.substring(2); + } + if (key.length() > 0 && Character.isUpperCase(key.charAt(0)) && method.getParameterTypes().length == 0) { + if (key.length() == 1) { + key = key.toLowerCase(); + } else if (!Character.isUpperCase(key.charAt(1))) { + key = key.substring(0, 1).toLowerCase() + key.substring(1); + } + Object result = method.invoke(bean, (Object[]) null); + if(null != result){ + map.put(key, wrap(result)); + } + } + } + } catch (Exception ignore) { + } + } + return mapAsJsonObject(map); + } + + public static Object wrap(Object object) { + try { + if (object == null) { + return null; + } + if (object instanceof JSONObject || object instanceof JSONArray + || JSON.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) { + return object; + } + + if (object instanceof Collection) { + Collection coll = (Collection) object; + return toJSONArray(coll); + } + + if (object instanceof Map) { + Map map = (Map) object; + return mapAsJsonObject(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 toJSONObject(object); + } catch (Exception exception) { + return null; + } + } + +} \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/json/JSONKit.java b/blade-kit/src/main/java/com/blade/kit/json/JSONKit.java new file mode 100644 index 000000000..fd529dc13 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/JSONKit.java @@ -0,0 +1,38 @@ +package com.blade.kit.json; + +import com.blade.kit.StringKit; + +public final class JSONKit { + + private JSONKit() {} + + public static String toJSONString(Object object){ + if(null == object){ + return null; + } + return JSONHelper.toJSONValue(object).toString(); + } + + public static String toJSONString(Object object, boolean flag){ + if(!flag){ + return toJSONString(object); + } + if(null == object){ + return null; + } + return JSONHelper.toJSONValue(object).toString(WriterConfig.PRETTY_PRINT); + } + + public static T parse(String json, Class clazz) { + if(StringKit.isBlank(json) || null == clazz){ + return null; + } + return JSONHelper.toBean(json, clazz); + } + + public static JSONObject parseObject(String json) { + return JSON.parse(json).asJSONObject(); + } + + +} \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/json/JSONLiteral.java b/blade-kit/src/main/java/com/blade/kit/json/JSONLiteral.java new file mode 100644 index 000000000..d00acdd70 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/JSONLiteral.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2013, 2015 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + +import java.io.IOException; + + +@SuppressWarnings("serial") // use default serial UID +class JSONLiteral extends JSONValue { + + private final String value; + private final boolean isNull; + private final boolean isTrue; + private final boolean isFalse; + + JSONLiteral(String value) { + this.value = value; + isNull = "null".equals(value); + isTrue = "true".equals(value); + isFalse = "false".equals(value); + } + + @Override + void write(JSONWriter writer) throws IOException { + writer.writeLiteral(value); + } + + @Override + public String toString() { + return value; + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public boolean isNull() { + return isNull; + } + + @Override + public boolean isTrue() { + return isTrue; + } + + @Override + public boolean isFalse() { + return isFalse; + } + + @Override + public boolean isBoolean() { + return isTrue || isFalse; + } + + @Override + public Boolean asBoolean() { + return isNull ? super.asBoolean() : isTrue; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + JSONLiteral other = (JSONLiteral)object; + return value.equals(other.value); + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/json/JSONNumber.java b/blade-kit/src/main/java/com/blade/kit/json/JSONNumber.java new file mode 100644 index 000000000..f5c74823f --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/JSONNumber.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2013, 2015 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + +import java.io.IOException; + +@SuppressWarnings("serial") // use default serial UID +class JSONNumber extends JSONValue { + + private final String string; + + JSONNumber(String string) { + if (string == null) { + throw new NullPointerException("string is null"); + } + this.string = string; + } + + @Override + public String toString() { + return string; + } + + @Override + void write(JSONWriter writer) throws IOException { + writer.writeNumber(string); + } + + @Override + public boolean isNumber() { + return true; + } + + @Override + public Integer asInt() { + return Integer.parseInt(string, 10); + } + + @Override + public Long asLong() { + return Long.parseLong(string, 10); + } + + @Override + public Float asFloat() { + return Float.parseFloat(string); + } + + @Override + public Double asDouble() { + return Double.parseDouble(string); + } + + public Byte asByte() { + return Byte.parseByte(string); + } + + @Override + public int hashCode() { + return string.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + JSONNumber other = (JSONNumber) object; + return string.equals(other.string); + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/json/JSONObject.java b/blade-kit/src/main/java/com/blade/kit/json/JSONObject.java new file mode 100644 index 000000000..1b8142c30 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/JSONObject.java @@ -0,0 +1,947 @@ +/******************************************************************************* + * Copyright (c) 2013, 2015 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import com.blade.kit.json.JSONObject.Member; + +/** + * Represents a JSON object, a set of name/value pairs, where the names are + * strings and the values are JSON values. + *

+ * Members can be added using the add(String, ...) methods which + * accept instances of {@link JSONValue}, strings, primitive numbers, and + * boolean values. To modify certain values of an object, use the + * set(String, ...) methods. Please note that the add + * methods are faster than set as they do not search for existing + * members. On the other hand, the add methods do not prevent + * adding multiple members with the same name. Duplicate names are discouraged + * but not prohibited by JSON. + *

+ *

+ * Members can be accessed by their name using {@link #get(String)}. A list of + * all names can be obtained from the method {@link #names()}. This class also + * supports iterating over the members in document order using an + * {@link #iterator()} or an enhanced for loop: + *

+ * + *
+ * for (Member member : jsonObject) {
+ *   String name = member.getName();
+ *   JsonValue value = member.getValue();
+ *   ...
+ * }
+ * 
+ *

+ * Even though JSON objects are unordered by definition, instances of this class + * preserve the order of members to allow processing in document order and to + * guarantee a predictable output. + *

+ *

+ * Note that this class is not thread-safe. If multiple threads + * access a JsonObject instance concurrently, while at least one of + * these threads modifies the contents of this object, access to the instance + * must be synchronized externally. Failure to do so may lead to an inconsistent + * state. + *

+ *

+ * This class is not supposed to be extended by clients. + *

+ */ +@SuppressWarnings("serial") // use default serial UID +public class JSONObject extends JSONValue implements Iterable { + + private final List names; + private final List values; + private transient HashIndexTable table; + + /** + * Creates a new empty JsonObject. + */ + public JSONObject() { + names = new ArrayList(); + values = new ArrayList(); + table = new HashIndexTable(); + } + + /** + * Creates a new JsonObject, initialized with the contents of the specified + * JSON object. + * + * @param object + * the JSON object to get the initial contents from, must not be + * null + */ + public JSONObject(JSONObject object) { + this(object, false); + } + + private JSONObject(JSONObject object, boolean unmodifiable) { + if (object == null) { + throw new NullPointerException("object is null"); + } + if (unmodifiable) { + names = Collections.unmodifiableList(object.names); + values = Collections.unmodifiableList(object.values); + } else { + names = new ArrayList(object.names); + values = new ArrayList(object.values); + } + table = new HashIndexTable(); + updateHashIndex(); + } + + /** + * Returns an unmodifiable JsonObject for the specified one. This method + * allows to provide read-only access to a JsonObject. + *

+ * The returned JsonObject is backed by the given object and reflect changes + * that happen to it. Attempts to modify the returned JsonObject result in + * an UnsupportedOperationException. + *

+ * + * @param object + * the JsonObject for which an unmodifiable JsonObject is to be + * returned + * @return an unmodifiable view of the specified JsonObject + */ + public static JSONObject unmodifiableObject(JSONObject object) { + return new JSONObject(object, true); + } + + /** + * Appends a new member to the end of this object, with the specified name + * and the JSON representation of the specified int value. + *

+ * This method does not prevent duplicate names. Calling + * this method with a name that already exists in the object will append + * another member with the same name. In order to replace existing members, + * use the method set(name, value) instead. However, + * add is much faster than set (because it does + * not need to search for existing members). Therefore add should + * be preferred when constructing new objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JSONObject put(String name, int value) { + put(name, JSON.value(value)); + return this; + } + + public JSONObject put(String name, Object value) { + put(name, JSON.value(value)); + return this; + } + + /** + * Appends a new member to the end of this object, with the specified name + * and the JSON representation of the specified long value. + *

+ * This method does not prevent duplicate names. Calling + * this method with a name that already exists in the object will append + * another member with the same name. In order to replace existing members, + * use the method set(name, value) instead. However, + * add is much faster than set (because it does + * not need to search for existing members). Therefore add should + * be preferred when constructing new objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JSONObject put(String name, long value) { + put(name, JSON.value(value)); + return this; + } + + /** + * Appends a new member to the end of this object, with the specified name + * and the JSON representation of the specified float value. + *

+ * This method does not prevent duplicate names. Calling + * this method with a name that already exists in the object will append + * another member with the same name. In order to replace existing members, + * use the method set(name, value) instead. However, + * add is much faster than set (because it does + * not need to search for existing members). Therefore add should + * be preferred when constructing new objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JSONObject put(String name, float value) { + put(name, JSON.value(value)); + return this; + } + + /** + * Appends a new member to the end of this object, with the specified name + * and the JSON representation of the specified double value. + *

+ * This method does not prevent duplicate names. Calling + * this method with a name that already exists in the object will append + * another member with the same name. In order to replace existing members, + * use the method set(name, value) instead. However, + * add is much faster than set (because it does + * not need to search for existing members). Therefore add should + * be preferred when constructing new objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JSONObject put(String name, double value) { + put(name, JSON.value(value)); + return this; + } + + /** + * Appends a new member to the end of this object, with the specified name + * and the JSON representation of the specified boolean value. + *

+ * This method does not prevent duplicate names. Calling + * this method with a name that already exists in the object will append + * another member with the same name. In order to replace existing members, + * use the method set(name, value) instead. However, + * add is much faster than set (because it does + * not need to search for existing members). Therefore add should + * be preferred when constructing new objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JSONObject put(String name, boolean value) { + put(name, JSON.value(value)); + return this; + } + + /** + * Appends a new member to the end of this object, with the specified name + * and the JSON representation of the specified string. + *

+ * This method does not prevent duplicate names. Calling + * this method with a name that already exists in the object will append + * another member with the same name. In order to replace existing members, + * use the method set(name, value) instead. However, + * add is much faster than set (because it does + * not need to search for existing members). Therefore add should + * be preferred when constructing new objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JSONObject put(String name, String value) { + put(name, JSON.value(value)); + return this; + } + + /** + * Appends a new member to the end of this object, with the specified name + * and the specified JSON value. + *

+ * This method does not prevent duplicate names. Calling + * this method with a name that already exists in the object will append + * another member with the same name. In order to replace existing members, + * use the method set(name, value) instead. However, + * add is much faster than set (because it does + * not need to search for existing members). Therefore add should + * be preferred when constructing new objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add, must not be null + * @return the object itself, to enable method chaining + */ + public JSONObject put(String name, JSONValue value) { + if (name == null) { + throw new NullPointerException("name is null"); + } + if (value == null) { + throw new NullPointerException("value is null"); + } + table.add(name, names.size()); + names.add(name); + values.add(value); + return this; + } + + /** + * Sets the value of the member with the specified name to the JSON + * representation of the specified int value. If this object + * does not contain a member with this name, a new member is added at the + * end of the object. If this object contains multiple members with this + * name, only the last one is changed. + *

+ * This method should only be used to modify existing + * objects. To fill a new object with members, the method + * add(name, value) should be preferred which is much faster + * (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to replace + * @param value + * the value to set to the member + * @return the object itself, to enable method chaining + */ + public JSONObject set(String name, int value) { + set(name, JSON.value(value)); + return this; + } + + /** + * Sets the value of the member with the specified name to the JSON + * representation of the specified long value. If this object + * does not contain a member with this name, a new member is added at the + * end of the object. If this object contains multiple members with this + * name, only the last one is changed. + *

+ * This method should only be used to modify existing + * objects. To fill a new object with members, the method + * add(name, value) should be preferred which is much faster + * (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to replace + * @param value + * the value to set to the member + * @return the object itself, to enable method chaining + */ + public JSONObject set(String name, long value) { + set(name, JSON.value(value)); + return this; + } + + /** + * Sets the value of the member with the specified name to the JSON + * representation of the specified float value. If this object + * does not contain a member with this name, a new member is added at the + * end of the object. If this object contains multiple members with this + * name, only the last one is changed. + *

+ * This method should only be used to modify existing + * objects. To fill a new object with members, the method + * add(name, value) should be preferred which is much faster + * (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JSONObject set(String name, float value) { + set(name, JSON.value(value)); + return this; + } + + /** + * Sets the value of the member with the specified name to the JSON + * representation of the specified double value. If this object + * does not contain a member with this name, a new member is added at the + * end of the object. If this object contains multiple members with this + * name, only the last one is changed. + *

+ * This method should only be used to modify existing + * objects. To fill a new object with members, the method + * add(name, value) should be preferred which is much faster + * (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JSONObject set(String name, double value) { + set(name, JSON.value(value)); + return this; + } + + /** + * Sets the value of the member with the specified name to the JSON + * representation of the specified boolean value. If this + * object does not contain a member with this name, a new member is added at + * the end of the object. If this object contains multiple members with this + * name, only the last one is changed. + *

+ * This method should only be used to modify existing + * objects. To fill a new object with members, the method + * add(name, value) should be preferred which is much faster + * (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JSONObject set(String name, boolean value) { + set(name, JSON.value(value)); + return this; + } + + /** + * Sets the value of the member with the specified name to the JSON + * representation of the specified string. If this object does not contain a + * member with this name, a new member is added at the end of the object. If + * this object contains multiple members with this name, only the last one + * is changed. + *

+ * This method should only be used to modify existing + * objects. To fill a new object with members, the method + * add(name, value) should be preferred which is much faster + * (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JSONObject set(String name, String value) { + set(name, JSON.value(value)); + return this; + } + + /** + * Sets the value of the member with the specified name to the specified + * JSON value. If this object does not contain a member with this name, a + * new member is added at the end of the object. If this object contains + * multiple members with this name, only the last one is changed. + *

+ * This method should only be used to modify existing + * objects. To fill a new object with members, the method + * add(name, value) should be preferred which is much faster + * (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add, must not be null + * @return the object itself, to enable method chaining + */ + public JSONObject set(String name, JSONValue value) { + if (name == null) { + throw new NullPointerException("name is null"); + } + if (value == null) { + throw new NullPointerException("value is null"); + } + int index = indexOf(name); + if (index != -1) { + values.set(index, value); + } else { + table.add(name, names.size()); + names.add(name); + values.add(value); + } + return this; + } + + /** + * Removes a member with the specified name from this object. If this object + * contains multiple members with the given name, only the last one is + * removed. If this object does not contain a member with the specified + * name, the object is not modified. + * + * @param name + * the name of the member to remove + * @return the object itself, to enable method chaining + */ + public JSONObject remove(String name) { + if (name == null) { + throw new NullPointerException("name is null"); + } + int index = indexOf(name); + if (index != -1) { + table.remove(index); + names.remove(index); + values.remove(index); + } + return this; + } + + /** + * Copies all members of the specified object into this object. When the + * specified object contains members with names that also exist in this + * object, the existing values in this object will be replaced by the + * corresponding values in the specified object. + * + * @param object + * the object to merge + * @return the object itself, to enable method chaining + */ + public JSONObject merge(JSONObject object) { + if (object == null) { + throw new NullPointerException("object is null"); + } + for (Member member : object) { + this.set(member.name, member.value); + } + return this; + } + + /** + * Returns the value of the member with the specified name in this object. + * If this object contains multiple members with the given name, this method + * will return the last one. + * + * @param name + * the name of the member whose value is to be returned + * @return the value of the last member with the specified name, or + * null if this object does not contain a member with + * that name + */ + public JSONValue get(String name) { + if (name == null) { + throw new NullPointerException("name is null"); + } + int index = indexOf(name); + return index != -1 ? values.get(index) : null; + } + + public boolean contains(String name) { + if (name == null) { + throw new NullPointerException("name is null"); + } + int index = indexOf(name); + return index != -1; + } + + + /** + * Returns the int value of the member with the specified name + * in this object. If this object does not contain a member with this name, + * the given default value is returned. If this object contains multiple + * members with the given name, the last one will be picked. If this + * member's value does not represent a JSON number or if it cannot be + * interpreted as Java int, an exception is thrown. + * + * @param name + * the name of the member whose value is to be returned + * @param defaultValue + * the value to be returned if the requested member is missing + * @return the value of the last member with the specified name, or the + * given default value if this object does not contain a member with + * that name + */ + public Integer getInt(String name, Integer defaultValue) { + JSONValue value = get(name); + return value != null ? value.asInt() : defaultValue; + } + + public Integer getInt(String name) { + return getInt(name, null); + } + + /** + * Returns the long value of the member with the specified name + * in this object. If this object does not contain a member with this name, + * the given default value is returned. If this object contains multiple + * members with the given name, the last one will be picked. If this + * member's value does not represent a JSON number or if it cannot be + * interpreted as Java long, an exception is thrown. + * + * @param name + * the name of the member whose value is to be returned + * @param defaultValue + * the value to be returned if the requested member is missing + * @return the value of the last member with the specified name, or the + * given default value if this object does not contain a member with + * that name + */ + public Long getLong(String name, Long defaultValue) { + JSONValue value = get(name); + return value != null ? value.asLong() : defaultValue; + } + + public Long getLong(String name) { + return getLong(name, null); + } + + /** + * Returns the float value of the member with the specified + * name in this object. If this object does not contain a member with this + * name, the given default value is returned. If this object contains + * multiple members with the given name, the last one will be picked. If + * this member's value does not represent a JSON number or if it cannot be + * interpreted as Java float, an exception is thrown. + * + * @param name + * the name of the member whose value is to be returned + * @param defaultValue + * the value to be returned if the requested member is missing + * @return the value of the last member with the specified name, or the + * given default value if this object does not contain a member with + * that name + */ + public Float getFloat(String name, Float defaultValue) { + JSONValue value = get(name); + return value != null ? value.asFloat() : defaultValue; + } + + public Float getFloat(String name) { + return getFloat(name, null); + } + + /** + * Returns the double value of the member with the specified + * name in this object. If this object does not contain a member with this + * name, the given default value is returned. If this object contains + * multiple members with the given name, the last one will be picked. If + * this member's value does not represent a JSON number or if it cannot be + * interpreted as Java double, an exception is thrown. + * + * @param name + * the name of the member whose value is to be returned + * @param defaultValue + * the value to be returned if the requested member is missing + * @return the value of the last member with the specified name, or the + * given default value if this object does not contain a member with + * that name + */ + public Double getDouble(String name, Double defaultValue) { + JSONValue value = get(name); + return value != null ? value.asDouble() : defaultValue; + } + + public Double getDouble(String name) { + return getDouble(name, null); + } + + /** + * Returns the boolean value of the member with the specified + * name in this object. If this object does not contain a member with this + * name, the given default value is returned. If this object contains + * multiple members with the given name, the last one will be picked. If + * this member's value does not represent a JSON true or + * false value, an exception is thrown. + * + * @param name + * the name of the member whose value is to be returned + * @param defaultValue + * the value to be returned if the requested member is missing + * @return the value of the last member with the specified name, or the + * given default value if this object does not contain a member with + * that name + */ + public Boolean getBoolean(String name, Boolean defaultValue) { + JSONValue value = get(name); + return value != null ? value.asBoolean() : defaultValue; + } + + public boolean getBoolean(String name) { + return getBoolean(name, null); + } + + /** + * Returns the String value of the member with the specified + * name in this object. If this object does not contain a member with this + * name, the given default value is returned. If this object contains + * multiple members with the given name, the last one is picked. If this + * member's value does not represent a JSON string, an exception is thrown. + * + * @param name + * the name of the member whose value is to be returned + * @param defaultValue + * the value to be returned if the requested member is missing + * @return the value of the last member with the specified name, or the + * given default value if this object does not contain a member with + * that name + */ + public String getString(String name, String defaultValue) { + JSONValue value = get(name); + return value != null ? value.asString() : defaultValue; + } + + public String getString(String name) { + return getString(name, null); + } + + /** + * Returns the number of members (name/value pairs) in this object. + * + * @return the number of members in this object + */ + public int size() { + return names.size(); + } + + /** + * Returns true if this object contains no members. + * + * @return true if this object contains no members + */ + public boolean isEmpty() { + return names.isEmpty(); + } + + /** + * Returns a list of the names in this object in document order. The + * returned list is backed by this object and will reflect subsequent + * changes. It cannot be used to modify this object. Attempts to modify the + * returned list will result in an exception. + * + * @return a list of the names in this object + */ + public List names() { + return Collections.unmodifiableList(names); + } + + /** + * Returns an iterator over the members of this object in document order. + * The returned iterator cannot be used to modify this object. + * + * @return an iterator over the members of this object + */ + public Iterator iterator() { + final Iterator namesIterator = names.iterator(); + final Iterator valuesIterator = values.iterator(); + return new Iterator() { + + public boolean hasNext() { + return namesIterator.hasNext(); + } + + public Member next() { + String name = namesIterator.next(); + JSONValue value = valuesIterator.next(); + return new Member(name, value); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } + + @Override + void write(JSONWriter writer) throws IOException { + writer.writeObjectOpen(); + Iterator namesIterator = names.iterator(); + Iterator valuesIterator = values.iterator(); + boolean first = true; + while (namesIterator.hasNext()) { + if (!first) { + writer.writeObjectSeparator(); + } + writer.writeMemberName(namesIterator.next()); + writer.writeMemberSeparator(); + valuesIterator.next().write(writer); + first = false; + } + writer.writeObjectClose(); + } + + @Override + public boolean isObject() { + return true; + } + + @Override + public JSONObject asJSONObject() { + return this; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + names.hashCode(); + result = 31 * result + values.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + JSONObject other = (JSONObject) obj; + return names.equals(other.names) && values.equals(other.values); + } + + int indexOf(String name) { + int index = table.get(name); + if (index != -1 && name.equals(names.get(index))) { + return index; + } + return names.lastIndexOf(name); + } + + private synchronized void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { + inputStream.defaultReadObject(); + table = new HashIndexTable(); + updateHashIndex(); + } + + private void updateHashIndex() { + int size = names.size(); + for (int i = 0; i < size; i++) { + table.add(names.get(i), i); + } + } + + /** + * Represents a member of a JSON object, a pair of a name and a value. + */ + public static class Member { + + private final String name; + private final JSONValue value; + + Member(String name, JSONValue value) { + this.name = name; + this.value = value; + } + + /** + * Returns the name of this member. + * + * @return the name of this member, never null + */ + public String getName() { + return name; + } + + /** + * Returns the value of this member. + * + * @return the value of this member, never null + */ + public JSONValue getValue() { + return value; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + name.hashCode(); + result = 31 * result + value.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Member other = (Member) obj; + return name.equals(other.name) && value.equals(other.value); + } + + } + + static class HashIndexTable { + + private final byte[] hashTable = new byte[32]; // must be a power of two + + public HashIndexTable() { + } + + public HashIndexTable(HashIndexTable original) { + System.arraycopy(original.hashTable, 0, hashTable, 0, hashTable.length); + } + + void add(String name, int index) { + int slot = hashSlotFor(name); + if (index < 0xff) { + // increment by 1, 0 stands for empty + hashTable[slot] = (byte) (index + 1); + } else { + hashTable[slot] = 0; + } + } + + void remove(int index) { + for (int i = 0; i < hashTable.length; i++) { + if (hashTable[i] == index + 1) { + hashTable[i] = 0; + } else if (hashTable[i] > index + 1) { + hashTable[i]--; + } + } + } + + int get(Object name) { + int slot = hashSlotFor(name); + // subtract 1, 0 stands for empty + return (hashTable[slot] & 0xff) - 1; + } + + private int hashSlotFor(Object element) { + return element.hashCode() & hashTable.length - 1; + } + + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/json/JSONParser.java b/blade-kit/src/main/java/com/blade/kit/json/JSONParser.java new file mode 100644 index 000000000..c118bf5de --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/JSONParser.java @@ -0,0 +1,511 @@ +/******************************************************************************* + * Copyright (c) 2013, 2016 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + + +/** + * A streaming parser for JSON text. The parser reports all events to a given handler. + */ +public class JSONParser { + + private static final int MAX_NESTING_LEVEL = 1000; + private static final int MIN_BUFFER_SIZE = 10; + private static final int DEFAULT_BUFFER_SIZE = 1024; + + private final JSONHandler handler; + private Reader reader; + private char[] buffer; + private int bufferOffset; + private int index; + private int fill; + private int line; + private int lineOffset; + private int current; + private StringBuilder captureBuffer; + private int captureStart; + private int nestingLevel; + + /* + * | bufferOffset + * v + * [a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t] < input + * [l|m|n|o|p|q|r|s|t|?|?] < buffer + * ^ ^ + * | index fill + */ + + /** + * Creates a new JsonParser with the given handler. The parser will report all parser events to + * this handler. + * + * @param handler + * the handler to process parser events + */ + @SuppressWarnings("unchecked") + public JSONParser(JSONHandler handler) { + if (handler == null) { + throw new NullPointerException("handler is null"); + } + this.handler = (JSONHandler)handler; + handler.parser = this; + } + + /** + * Parses the given input string. The input must contain a valid JSON value, optionally padded + * with whitespace. + * + * @param string + * the input string, must be valid JSON + * @throws ParseException + * if the input is not valid JSON + */ + public void parse(String string) { + if (string == null) { + throw new NullPointerException("string is null"); + } + int bufferSize = Math.max(MIN_BUFFER_SIZE, Math.min(DEFAULT_BUFFER_SIZE, string.length())); + try { + parse(new StringReader(string), bufferSize); + } catch (IOException exception) { + // StringReader does not throw IOException + throw new RuntimeException(exception); + } + } + + /** + * Reads the entire input from the given reader and parses it as JSON. The input must contain a + * valid JSON value, optionally padded with whitespace. + *

+ * Characters are read in chunks into a default-sized input buffer. Hence, wrapping a reader in an + * additional BufferedReader likely won't improve reading performance. + *

+ * + * @param reader + * the reader to read the input from + * @throws IOException + * if an I/O error occurs in the reader + * @throws ParseException + * if the input is not valid JSON + */ + public void parse(Reader reader) throws IOException { + parse(reader, DEFAULT_BUFFER_SIZE); + } + + /** + * Reads the entire input from the given reader and parses it as JSON. The input must contain a + * valid JSON value, optionally padded with whitespace. + *

+ * Characters are read in chunks into an input buffer of the given size. Hence, wrapping a reader + * in an additional BufferedReader likely won't improve reading performance. + *

+ * + * @param reader + * the reader to read the input from + * @param buffersize + * the size of the input buffer in chars + * @throws IOException + * if an I/O error occurs in the reader + * @throws ParseException + * if the input is not valid JSON + */ + public void parse(Reader reader, int buffersize) throws IOException { + if (reader == null) { + throw new NullPointerException("reader is null"); + } + if (buffersize <= 0) { + throw new IllegalArgumentException("buffersize is zero or negative"); + } + this.reader = reader; + buffer = new char[buffersize]; + bufferOffset = 0; + index = 0; + fill = 0; + line = 1; + lineOffset = 0; + current = 0; + captureStart = -1; + read(); + skipWhiteSpace(); + readValue(); + skipWhiteSpace(); + if (!isEndOfText()) { + throw error("Unexpected character"); + } + } + + private void readValue() throws IOException { + switch (current) { + case 'n': + readNull(); + break; + case 't': + readTrue(); + break; + case 'f': + readFalse(); + break; + case '"': + readString(); + break; + case '[': + readArray(); + break; + case '{': + readObject(); + break; + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + readNumber(); + break; + default: + throw expected("value"); + } + } + + private void readArray() throws IOException { + Object array = handler.startArray(); + read(); + if (++nestingLevel > MAX_NESTING_LEVEL) { + throw error("Nesting too deep"); + } + skipWhiteSpace(); + if (readChar(']')) { + nestingLevel--; + handler.endArray(array); + return; + } + do { + skipWhiteSpace(); + handler.startArrayValue(array); + readValue(); + handler.endArrayValue(array); + skipWhiteSpace(); + } while (readChar(',')); + if (!readChar(']')) { + throw expected("',' or ']'"); + } + nestingLevel--; + handler.endArray(array); + } + + private void readObject() throws IOException { + Object object = handler.startObject(); + read(); + if (++nestingLevel > MAX_NESTING_LEVEL) { + throw error("Nesting too deep"); + } + skipWhiteSpace(); + if (readChar('}')) { + nestingLevel--; + handler.endObject(object); + return; + } + do { + skipWhiteSpace(); + handler.startObjectName(object); + String name = readName(); + handler.endObjectName(object, name); + skipWhiteSpace(); + if (!readChar(':')) { + throw expected("':'"); + } + skipWhiteSpace(); + handler.startObjectValue(object, name); + readValue(); + handler.endObjectValue(object, name); + skipWhiteSpace(); + } while (readChar(',')); + if (!readChar('}')) { + throw expected("',' or '}'"); + } + nestingLevel--; + handler.endObject(object); + } + + private String readName() throws IOException { + if (current != '"') { + throw expected("name"); + } + return readStringInternal(); + } + + private void readNull() throws IOException { + handler.startNull(); + read(); + readRequiredChar('u'); + readRequiredChar('l'); + readRequiredChar('l'); + handler.endNull(); + } + + private void readTrue() throws IOException { + handler.startBoolean(); + read(); + readRequiredChar('r'); + readRequiredChar('u'); + readRequiredChar('e'); + handler.endBoolean(true); + } + + private void readFalse() throws IOException { + handler.startBoolean(); + read(); + readRequiredChar('a'); + readRequiredChar('l'); + readRequiredChar('s'); + readRequiredChar('e'); + handler.endBoolean(false); + } + + private void readRequiredChar(char ch) throws IOException { + if (!readChar(ch)) { + throw expected("'" + ch + "'"); + } + } + + private void readString() throws IOException { + handler.startString(); + handler.endString(readStringInternal()); + } + + private String readStringInternal() throws IOException { + read(); + startCapture(); + while (current != '"') { + if (current == '\\') { + pauseCapture(); + readEscape(); + startCapture(); + } else if (current < 0x20) { + throw expected("valid string character"); + } else { + read(); + } + } + String string = endCapture(); + read(); + return string; + } + + private void readEscape() throws IOException { + read(); + switch (current) { + case '"': + case '/': + case '\\': + captureBuffer.append((char)current); + break; + case 'b': + captureBuffer.append('\b'); + break; + case 'f': + captureBuffer.append('\f'); + break; + case 'n': + captureBuffer.append('\n'); + break; + case 'r': + captureBuffer.append('\r'); + break; + case 't': + captureBuffer.append('\t'); + break; + case 'u': + char[] hexChars = new char[4]; + for (int i = 0; i < 4; i++) { + read(); + if (!isHexDigit()) { + throw expected("hexadecimal digit"); + } + hexChars[i] = (char)current; + } + captureBuffer.append((char)Integer.parseInt(new String(hexChars), 16)); + break; + default: + throw expected("valid escape sequence"); + } + read(); + } + + private void readNumber() throws IOException { + handler.startNumber(); + startCapture(); + readChar('-'); + int firstDigit = current; + if (!readDigit()) { + throw expected("digit"); + } + if (firstDigit != '0') { + while (readDigit()) { + } + } + readFraction(); + readExponent(); + handler.endNumber(endCapture()); + } + + private boolean readFraction() throws IOException { + if (!readChar('.')) { + return false; + } + if (!readDigit()) { + throw expected("digit"); + } + while (readDigit()) { + } + return true; + } + + private boolean readExponent() throws IOException { + if (!readChar('e') && !readChar('E')) { + return false; + } + if (!readChar('+')) { + readChar('-'); + } + if (!readDigit()) { + throw expected("digit"); + } + while (readDigit()) { + } + return true; + } + + private boolean readChar(char ch) throws IOException { + if (current != ch) { + return false; + } + read(); + return true; + } + + private boolean readDigit() throws IOException { + if (!isDigit()) { + return false; + } + read(); + return true; + } + + private void skipWhiteSpace() throws IOException { + while (isWhiteSpace()) { + read(); + } + } + + private void read() throws IOException { + if (index == fill) { + if (captureStart != -1) { + captureBuffer.append(buffer, captureStart, fill - captureStart); + captureStart = 0; + } + bufferOffset += fill; + fill = reader.read(buffer, 0, buffer.length); + index = 0; + if (fill == -1) { + current = -1; + index++; + return; + } + } + if (current == '\n') { + line++; + lineOffset = bufferOffset + index; + } + current = buffer[index++]; + } + + private void startCapture() { + if (captureBuffer == null) { + captureBuffer = new StringBuilder(); + } + captureStart = index - 1; + } + + private void pauseCapture() { + int end = current == -1 ? index : index - 1; + captureBuffer.append(buffer, captureStart, end - captureStart); + captureStart = -1; + } + + private String endCapture() { + int start = captureStart; + int end = index - 1; + captureStart = -1; + if (captureBuffer.length() > 0) { + captureBuffer.append(buffer, start, end - start); + String captured = captureBuffer.toString(); + captureBuffer.setLength(0); + return captured; + } + return new String(buffer, start, end - start); + } + + Location getLocation() { + int offset = bufferOffset + index - 1; + int column = offset - lineOffset + 1; + return new Location(offset, line, column); + } + + private ParseException expected(String expected) { + if (isEndOfText()) { + return error("Unexpected end of input"); + } + return error("Expected " + expected); + } + + private ParseException error(String message) { + return new ParseException(message, getLocation()); + } + + private boolean isWhiteSpace() { + return current == ' ' || current == '\t' || current == '\n' || current == '\r'; + } + + private boolean isDigit() { + return current >= '0' && current <= '9'; + } + + private boolean isHexDigit() { + return current >= '0' && current <= '9' + || current >= 'a' && current <= 'f' + || current >= 'A' && current <= 'F'; + } + + private boolean isEndOfText() { + return current == -1; + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/json/JSONString.java b/blade-kit/src/main/java/com/blade/kit/json/JSONString.java new file mode 100644 index 000000000..c64ec66ab --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/JSONString.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2013, 2015 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + +import java.io.IOException; + + +@SuppressWarnings("serial") // use default serial UID +class JSONString extends JSONValue { + + private final String string; + + JSONString(String string) { + if (string == null) { + throw new NullPointerException("string is null"); + } + this.string = string; + } + + @Override + void write(JSONWriter writer) throws IOException { + writer.writeString(string); + } + + @Override + public boolean isString() { + return true; + } + + @Override + public String asString() { + return string; + } + + @Override + public int hashCode() { + return string.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + JSONString other = (JSONString)object; + return string.equals(other.string); + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/json/JSONValue.java b/blade-kit/src/main/java/com/blade/kit/json/JSONValue.java new file mode 100644 index 000000000..7742582bc --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/JSONValue.java @@ -0,0 +1,391 @@ +/******************************************************************************* + * Copyright (c) 2013, 2016 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + +import java.io.IOException; +import java.io.Serializable; +import java.io.StringWriter; +import java.io.Writer; + +/** + * Represents a JSON value. This can be a JSON object, an + * array, a number, a string + * , or one of the literals true, false, and + * null. + *

+ * The literals true, false, and + * null are represented by the constants {@link #TRUE}, + * {@link #FALSE}, and {@link #NULL}. + *

+ *

+ * JSON objects and arrays are represented by + * the subtypes {@link JSONObject} and {@link JSONArray}. Instances of these + * types can be created using the public constructors of these classes. + *

+ *

+ * Instances that represent JSON numbers, + * strings and boolean values can be created + * using the static factory methods {@link #valueOf(String)}, + * {@link #valueOf(long)}, {@link #valueOf(double)}, etc. + *

+ *

+ * In order to find out whether an instance of this class is of a certain type, + * the methods {@link #isObject()}, {@link #isArray()}, {@link #isString()}, + * {@link #isNumber()} etc. can be used. + *

+ *

+ * If the type of a JSON value is known, the methods {@link #asObject()}, + * {@link #asArray()}, {@link #asString()}, {@link #asInt()}, etc. can be used + * to get this value directly in the appropriate target type. + *

+ *

+ * This class is not supposed to be extended by clients. + *

+ */ +@SuppressWarnings("serial") // use default serial UID +public abstract class JSONValue implements Serializable { + + JSONValue() { + // prevent subclasses outside of this package + } + + /** + * Detects whether this value represents a JSON object. If this is the case, + * this value is an instance of {@link JSONObject}. + * + * @return true if this value is an instance of JsonObject + */ + public boolean isObject() { + return false; + } + + public boolean isBean() { + return false; + } + + /** + * Detects whether this value represents a JSON array. If this is the case, + * this value is an instance of {@link JSONArray}. + * + * @return true if this value is an instance of JsonArray + */ + public boolean isArray() { + return false; + } + + /** + * Detects whether this value represents a JSON number. + * + * @return true if this value represents a JSON number + */ + public boolean isNumber() { + return false; + } + + /** + * Detects whether this value represents a JSON string. + * + * @return true if this value represents a JSON string + */ + public boolean isString() { + return false; + } + + public boolean isLong(){ + return false; + } + + /** + * Detects whether this value represents a boolean value. + * + * @return true if this value represents either the JSON + * literal true or false + */ + public boolean isBoolean() { + return false; + } + + /** + * Detects whether this value represents the JSON literal true. + * + * @return true if this value represents the JSON literal + * true + */ + public boolean isTrue() { + return false; + } + + /** + * Detects whether this value represents the JSON literal false + * . + * + * @return true if this value represents the JSON literal + * false + */ + public boolean isFalse() { + return false; + } + + /** + * Detects whether this value represents the JSON literal null. + * + * @return true if this value represents the JSON literal + * null + */ + public boolean isNull() { + return false; + } + + /** + * Returns this JSON value as {@link JSONObject}, assuming that this value + * represents a JSON object. If this is not the case, an exception is + * thrown. + * + * @return a JSONObject for this value + * @throws UnsupportedOperationException + * if this value is not a JSON object + */ + public JSONObject asJSONObject() { + throw new UnsupportedOperationException("Not an JSONObject: " + toString()); + } + + /** + * Returns this JSON value as {@link JSONArray}, assuming that this value + * represents a JSON array. If this is not the case, an exception is thrown. + * + * @return a JSONArray for this value + * @throws UnsupportedOperationException + * if this value is not a JSON array + */ + public JSONArray asArray() { + throw new UnsupportedOperationException("Not an array: " + toString()); + } + + /** + * Returns this JSON value as an int value, assuming that this + * value represents a JSON number that can be interpreted as Java + * int. If this is not the case, an exception is thrown. + *

+ * To be interpreted as Java int, the JSON number must neither + * contain an exponent nor a fraction part. Moreover, the number must be in + * the Integer range. + *

+ * + * @return this value as int + * @throws UnsupportedOperationException + * if this value is not a JSON number + * @throws NumberFormatException + * if this JSON number can not be interpreted as + * int value + */ + public Integer asInt() { + throw new UnsupportedOperationException("Not a number: " + toString()); + } + + /** + * Returns this JSON value as a long value, assuming that this + * value represents a JSON number that can be interpreted as Java + * long. If this is not the case, an exception is thrown. + *

+ * To be interpreted as Java long, the JSON number must neither + * contain an exponent nor a fraction part. Moreover, the number must be in + * the Long range. + *

+ * + * @return this value as long + * @throws UnsupportedOperationException + * if this value is not a JSON number + * @throws NumberFormatException + * if this JSON number can not be interpreted as + * long value + */ + public Long asLong() { + throw new UnsupportedOperationException("Not a number: " + toString()); + } + + /** + * Returns this JSON value as a float value, assuming that this + * value represents a JSON number. If this is not the case, an exception is + * thrown. + *

+ * If the JSON number is out of the Float range, + * {@link Float#POSITIVE_INFINITY} or {@link Float#NEGATIVE_INFINITY} is + * returned. + *

+ * + * @return this value as float + * @throws UnsupportedOperationException + * if this value is not a JSON number + */ + public Float asFloat() { + throw new UnsupportedOperationException("Not a number: " + toString()); + } + + /** + * Returns this JSON value as a double value, assuming that + * this value represents a JSON number. If this is not the case, an + * exception is thrown. + *

+ * If the JSON number is out of the Double range, + * {@link Double#POSITIVE_INFINITY} or {@link Double#NEGATIVE_INFINITY} is + * returned. + *

+ * + * @return this value as double + * @throws UnsupportedOperationException + * if this value is not a JSON number + */ + public Double asDouble() { + throw new UnsupportedOperationException("Not a number: " + toString()); + } + + /** + * Returns this JSON value as String, assuming that this value represents a + * JSON string. If this is not the case, an exception is thrown. + * + * @return the string represented by this value + * @throws UnsupportedOperationException + * if this value is not a JSON string + */ + public String asString() { + throw new UnsupportedOperationException("Not a string: " + toString()); + } + + /** + * Returns this JSON value as a boolean value, assuming that + * this value is either true or false. If this is + * not the case, an exception is thrown. + * + * @return this value as boolean + * @throws UnsupportedOperationException + * if this value is neither true or + * false + */ + public Boolean asBoolean() { + throw new UnsupportedOperationException("Not a boolean: " + toString()); + } + + /** + * Writes the JSON representation of this value to the given writer in its + * minimal form, without any additional whitespace. + *

+ * Writing performance can be improved by using a + * {@link java.io.BufferedWriter BufferedWriter}. + *

+ * + * @param writer + * the writer to write this value to + * @throws IOException + * if an I/O error occurs in the writer + */ + public void writeTo(Writer writer) throws IOException { + writeTo(writer, WriterConfig.MINIMAL); + } + + /** + * Writes the JSON representation of this value to the given writer using + * the given formatting. + *

+ * Writing performance can be improved by using a + * {@link java.io.BufferedWriter BufferedWriter}. + *

+ * + * @param writer + * the writer to write this value to + * @param config + * a configuration that controls the formatting or + * null for the minimal form + * @throws IOException + * if an I/O error occurs in the writer + */ + public void writeTo(Writer writer, WriterConfig config) throws IOException { + if (writer == null) { + throw new NullPointerException("writer is null"); + } + if (config == null) { + throw new NullPointerException("config is null"); + } + WritingBuffer buffer = new WritingBuffer(writer, 128); + write(config.createWriter(buffer)); + buffer.flush(); + } + + /** + * Returns the JSON string for this value in its minimal form, without any + * additional whitespace. The result is guaranteed to be a valid input for + * the method {@link #readFrom(String)} and to create a value that is + * equal to this object. + * + * @return a JSON string that represents this value + */ + @Override + public String toString() { + return toString(WriterConfig.MINIMAL); + } + + /** + * Returns the JSON string for this value using the given formatting. + * + * @param config + * a configuration that controls the formatting or + * null for the minimal form + * @return a JSON string that represents this value + */ + public String toString(WriterConfig config) { + StringWriter writer = new StringWriter(); + try { + writeTo(writer, config); + } catch (IOException exception) { + // StringWriter does not throw IOExceptions + throw new RuntimeException(exception); + } + return writer.toString(); + } + + /** + * Indicates whether some other object is "equal to" this one according to + * the contract specified in {@link Object#equals(Object)}. + *

+ * Two JsonValues are considered equal if and only if they represent the + * same JSON text. As a consequence, two given JsonObjects may be different + * even though they contain the same set of names with the same values, but + * in a different order. + *

+ * + * @param object + * the reference object with which to compare + * @return true if this object is the same as the object argument; false + * otherwise + */ + @Override + public boolean equals(Object object) { + return super.equals(object); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + abstract void write(JSONWriter writer) throws IOException; + +} diff --git a/blade-kit/src/main/java/com/blade/kit/json/JSONWriter.java b/blade-kit/src/main/java/com/blade/kit/json/JSONWriter.java new file mode 100644 index 000000000..db8957e81 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/JSONWriter.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2013, 2015 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + +import java.io.IOException; +import java.io.Writer; + + +class JSONWriter { + + private static final int CONTROL_CHARACTERS_END = 0x001f; + + private static final char[] QUOT_CHARS = {'\\', '"'}; + private static final char[] BS_CHARS = {'\\', '\\'}; + private static final char[] LF_CHARS = {'\\', 'n'}; + private static final char[] CR_CHARS = {'\\', 'r'}; + private static final char[] TAB_CHARS = {'\\', 't'}; + // In JavaScript, U+2028 and U+2029 characters count as line endings and must be encoded. + // http://stackoverflow.com/questions/2965293/javascript-parse-error-on-u2028-unicode-character + private static final char[] UNICODE_2028_CHARS = {'\\', 'u', '2', '0', '2', '8'}; + private static final char[] UNICODE_2029_CHARS = {'\\', 'u', '2', '0', '2', '9'}; + private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f'}; + + protected final Writer writer; + + JSONWriter(Writer writer) { + this.writer = writer; + } + + protected void writeLiteral(String value) throws IOException { + writer.write(value); + } + + protected void writeNumber(String string) throws IOException { + writer.write(string); + } + + protected void writeString(String string) throws IOException { + writer.write('"'); + writeJsonString(string); + writer.write('"'); + } + + protected void writeArrayOpen() throws IOException { + writer.write('['); + } + + protected void writeArrayClose() throws IOException { + writer.write(']'); + } + + protected void writeArraySeparator() throws IOException { + writer.write(','); + } + + protected void writeObjectOpen() throws IOException { + writer.write('{'); + } + + protected void writeObjectClose() throws IOException { + writer.write('}'); + } + + protected void writeMemberName(String name) throws IOException { + writer.write('"'); + writeJsonString(name); + writer.write('"'); + } + + protected void writeMemberSeparator() throws IOException { + writer.write(':'); + } + + protected void writeObjectSeparator() throws IOException { + writer.write(','); + } + + protected void writeJsonString(String string) throws IOException { + int length = string.length(); + int start = 0; + for (int index = 0; index < length; index++) { + char[] replacement = getReplacementChars(string.charAt(index)); + if (replacement != null) { + writer.write(string, start, index - start); + writer.write(replacement); + start = index + 1; + } + } + writer.write(string, start, length - start); + } + + private static char[] getReplacementChars(char ch) { + if (ch > '\\') { + if (ch < '\u2028' || ch > '\u2029') { + // The lower range contains 'a' .. 'z'. Only 2 checks required. + return null; + } + return ch == '\u2028' ? UNICODE_2028_CHARS : UNICODE_2029_CHARS; + } + if (ch == '\\') { + return BS_CHARS; + } + if (ch > '"') { + // This range contains '0' .. '9' and 'A' .. 'Z'. Need 3 checks to get here. + return null; + } + if (ch == '"') { + return QUOT_CHARS; + } + if (ch > CONTROL_CHARACTERS_END) { + return null; + } + if (ch == '\n') { + return LF_CHARS; + } + if (ch == '\r') { + return CR_CHARS; + } + if (ch == '\t') { + return TAB_CHARS; + } + return new char[] {'\\', 'u', '0', '0', HEX_DIGITS[ch >> 4 & 0x000f], HEX_DIGITS[ch & 0x000f]}; + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/json/Location.java b/blade-kit/src/main/java/com/blade/kit/json/Location.java new file mode 100644 index 000000000..70d930bc9 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/Location.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2016 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + + +/** + * An immutable object that represents a location in the parsed text. + */ +public class Location { + + /** + * The absolute character index, starting at 0. + */ + public final int offset; + + /** + * The line number, starting at 1. + */ + public final int line; + + /** + * The column number, starting at 1. + */ + public final int column; + + Location(int offset, int line, int column) { + this.offset = offset; + this.column = column; + this.line = line; + } + + @Override + public String toString() { + return line + ":" + column; + } + + @Override + public int hashCode() { + return offset; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Location other = (Location)obj; + return offset == other.offset && column == other.column && line == other.line; + } + +} \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/json/ParseException.java b/blade-kit/src/main/java/com/blade/kit/json/ParseException.java new file mode 100644 index 000000000..72b8e9b89 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/ParseException.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2013, 2016 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + +/** + * An unchecked exception to indicate that an input does not qualify as valid JSON. + */ +@SuppressWarnings("serial") // use default serial UID +public class ParseException extends RuntimeException { + + private final Location location; + + ParseException(String message, Location location) { + super(message + " at " + location); + this.location = location; + } + + /** + * Returns the location at which the error occurred. + * + * @return the error location + */ + public Location getLocation() { + return location; + } + + /** + * Returns the absolute character index at which the error occurred. The offset of the first + * character of a document is 0. + * + * @return the character offset at which the error occurred, will be >= 0 + * @deprecated Use {@link #getLocation()} instead + */ + @Deprecated + public int getOffset() { + return location.offset; + } + + /** + * Returns the line number in which the error occurred. The number of the first line is 1. + * + * @return the line in which the error occurred, will be >= 1 + * @deprecated Use {@link #getLocation()} instead + */ + @Deprecated + public int getLine() { + return location.line; + } + + /** + * Returns the column number at which the error occurred, i.e. the number of the character in its + * line. The number of the first character of a line is 1. + * + * @return the column in which the error occurred, will be >= 1 + * @deprecated Use {@link #getLocation()} instead + */ + @Deprecated + public int getColumn() { + return location.column; + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/json/PrettyPrint.java b/blade-kit/src/main/java/com/blade/kit/json/PrettyPrint.java new file mode 100644 index 000000000..dc2f391e1 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/PrettyPrint.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2015 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + +import java.io.IOException; +import java.io.Writer; +import java.util.Arrays; + + +/** + * Enables human readable JSON output by inserting whitespace between values.after commas and + * colons. Example: + * + *
+ * jsonValue.writeTo(writer, PrettyPrint.singleLine());
+ * 
+ */ +public class PrettyPrint extends WriterConfig { + + private final char[] indentChars; + + protected PrettyPrint(char[] indentChars) { + this.indentChars = indentChars; + } + + /** + * Print every value on a separate line. Use tabs (\t) for indentation. + * + * @return A PrettyPrint instance for wrapped mode with tab indentation + */ + public static PrettyPrint singleLine() { + return new PrettyPrint(null); + } + + /** + * Print every value on a separate line. Use the given number of spaces for indentation. + * + * @param number + * the number of spaces to use + * @return A PrettyPrint instance for wrapped mode with spaces indentation + */ + public static PrettyPrint indentWithSpaces(int number) { + if (number < 0) { + throw new IllegalArgumentException("number is negative"); + } + char[] chars = new char[number]; + Arrays.fill(chars, ' '); + return new PrettyPrint(chars); + } + + /** + * Do not break lines, but still insert whitespace between values. + * + * @return A PrettyPrint instance for single-line mode + */ + public static PrettyPrint indentWithTabs() { + return new PrettyPrint(new char[] {'\t'}); + } + + @Override + protected JSONWriter createWriter(Writer writer) { + return new PrettyPrintWriter(writer, indentChars); + } + + private static class PrettyPrintWriter extends JSONWriter { + + private final char[] indentChars; + private int indent; + + private PrettyPrintWriter(Writer writer, char[] indentChars) { + super(writer); + this.indentChars = indentChars; + } + + @Override + protected void writeArrayOpen() throws IOException { + indent++; + writer.write('['); + writeNewLine(); + } + + @Override + protected void writeArrayClose() throws IOException { + indent--; + writeNewLine(); + writer.write(']'); + } + + @Override + protected void writeArraySeparator() throws IOException { + writer.write(','); + if (!writeNewLine()) { + writer.write(' '); + } + } + + @Override + protected void writeObjectOpen() throws IOException { + indent++; + writer.write('{'); + writeNewLine(); + } + + @Override + protected void writeObjectClose() throws IOException { + indent--; + writeNewLine(); + writer.write('}'); + } + + @Override + protected void writeMemberSeparator() throws IOException { + writer.write(':'); + writer.write(' '); + } + + @Override + protected void writeObjectSeparator() throws IOException { + writer.write(','); + if (!writeNewLine()) { + writer.write(' '); + } + } + + private boolean writeNewLine() throws IOException { + if (indentChars == null) { + return false; + } + writer.write('\n'); + for (int i = 0; i < indent; i++) { + writer.write(indentChars); + } + return true; + } + + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/json/WriterConfig.java b/blade-kit/src/main/java/com/blade/kit/json/WriterConfig.java new file mode 100644 index 000000000..4f3823d29 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/WriterConfig.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2015 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + +import java.io.Writer; + + +/** + * Controls the formatting of the JSON output. Use one of the available constants. + */ +public abstract class WriterConfig { + + /** + * Write JSON in its minimal form, without any additional whitespace. This is the default. + */ + public static WriterConfig MINIMAL = new WriterConfig() { + @Override + JSONWriter createWriter(Writer writer) { + return new JSONWriter(writer); + } + }; + + /** + * Write JSON in pretty-print, with each value on a separate line and an indentation of two + * spaces. + */ + public static WriterConfig PRETTY_PRINT = PrettyPrint.indentWithSpaces(2); + + abstract JSONWriter createWriter(Writer writer); + +} diff --git a/blade-kit/src/main/java/com/blade/kit/json/WritingBuffer.java b/blade-kit/src/main/java/com/blade/kit/json/WritingBuffer.java new file mode 100644 index 000000000..257a394fa --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/json/WritingBuffer.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2015 EclipseSource. + * + * 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 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. + ******************************************************************************/ +package com.blade.kit.json; + +import java.io.IOException; +import java.io.Writer; + + +/** + * A lightweight writing buffer to reduce the amount of write operations to be performed on the + * underlying writer. This implementation is not thread-safe. It deliberately deviates from the + * contract of Writer. In particular, it does not flush or close the wrapped writer nor does it + * ensure that the wrapped writer is open. + */ +class WritingBuffer extends Writer { + + private final Writer writer; + private final char[] buffer; + private int fill = 0; + + WritingBuffer(Writer writer) { + this(writer, 16); + } + + WritingBuffer(Writer writer, int bufferSize) { + this.writer = writer; + buffer = new char[bufferSize]; + } + + @Override + public void write(int c) throws IOException { + if (fill > buffer.length - 1) { + flush(); + } + buffer[fill++] = (char)c; + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + if (fill > buffer.length - len) { + flush(); + if (len > buffer.length) { + writer.write(cbuf, off, len); + return; + } + } + System.arraycopy(cbuf, off, buffer, fill, len); + fill += len; + } + + @Override + public void write(String str, int off, int len) throws IOException { + if (fill > buffer.length - len) { + flush(); + if (len > buffer.length) { + writer.write(str, off, len); + return; + } + } + str.getChars(off, off + len, buffer, fill); + fill += len; + } + + /** + * Flushes the internal buffer but does not flush the wrapped writer. + */ + @Override + public void flush() throws IOException { + writer.write(buffer, 0, fill); + fill = 0; + } + + /** + * Does not close or flush the wrapped writer. + */ + @Override + public void close() throws IOException { + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/package-info.java b/blade-kit/src/main/java/com/blade/kit/package-info.java new file mode 100644 index 000000000..f8e54277d --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/package-info.java @@ -0,0 +1,4 @@ +/** + * 最常用的工具类集合 + */ +package com.blade.kit; \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/reflect/ClassDefine.java b/blade-kit/src/main/java/com/blade/kit/reflect/ClassDefine.java new file mode 100644 index 000000000..fd9d63424 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/reflect/ClassDefine.java @@ -0,0 +1,108 @@ +package com.blade.kit.reflect; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +public final class ClassDefine { + + private static final ConcurrentHashMap, ClassDefine> pool = new ConcurrentHashMap, ClassDefine>(128); + + private final Class clazz; + + private ClassDefine(Class type) { + this.clazz = type; + } + + public static ClassDefine create(Class clazz){ + ClassDefine classDefine = pool.get(clazz); + if (classDefine == null) { + classDefine = new ClassDefine(clazz); + ClassDefine old = pool.putIfAbsent(clazz, classDefine); + if (old != null) { + classDefine = old; + } + } + return classDefine; + } + + @SuppressWarnings("unchecked") + public Class getType() { + return (Class) clazz; + } + + public String getName() { + return clazz.getName(); + } + + public String getSimpleName() { + return clazz.getSimpleName(); + } + + public ClassDefine getSuperKlass() { + Class superKlass = clazz.getSuperclass(); + return (superKlass == null) ? null : ClassDefine.create(superKlass); + } + + public List getInterfaces() { + Class[] interfaces = clazz.getInterfaces(); + if (interfaces.length == 0) { + return Collections.emptyList(); + } + List results = new ArrayList(interfaces.length); + for (Class intf : interfaces) { + results.add(ClassDefine.create(intf)); + } + return results; + } + + // ------------------------------------------------------------------ + public Annotation[] getAnnotations() { + return clazz.getAnnotations(); + } + + public T getAnnotation(Class annotationClass) { + return clazz.getAnnotation(annotationClass); + } + + public boolean isAnnotationPresent(Class annotationClass) { + return clazz.isAnnotationPresent(annotationClass); + } + + public Field[] getDeclaredFields() { + return clazz.getDeclaredFields(); + } + + // ------------------------------------------------------------------ + public int getModifiers() { + return clazz.getModifiers(); + } + + public boolean isInterface() { + return Modifier.isInterface(getModifiers()); + } + + public boolean isAbstract() { + return Modifier.isAbstract(getModifiers()); + } + + public boolean isStatic() { + return Modifier.isStatic(getModifiers()); + } + + public boolean isPrivate() { + return Modifier.isPrivate(getModifiers()); + } + + public boolean isProtected() { + return Modifier.isProtected(getModifiers()); + } + + public boolean isPublic() { + return Modifier.isPublic(getModifiers()); + } +} diff --git a/blade-kit/src/main/java/com/blade/kit/reflect/ConvertKit.java b/blade-kit/src/main/java/com/blade/kit/reflect/ConvertKit.java new file mode 100644 index 000000000..977b637d5 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/reflect/ConvertKit.java @@ -0,0 +1,32 @@ +package com.blade.kit.reflect; + +import java.util.HashSet; +import java.util.Set; + +public final class ConvertKit { + + private static final Set> basicTypes; + + static { + basicTypes = new HashSet>(); + basicTypes.add(Boolean.class); + basicTypes.add(boolean.class); + basicTypes.add(Integer.class); + basicTypes.add(int.class); + basicTypes.add(Short.class); + basicTypes.add(short.class); + basicTypes.add(Long.class); + basicTypes.add(long.class); + basicTypes.add(Float.class); + basicTypes.add(float.class); + basicTypes.add(Double.class); + basicTypes.add(double.class); + basicTypes.add(Character.class); + basicTypes.add(char.class); + } + + public static boolean isBasicType(Class type){ + return basicTypes.contains(type); + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/reflect/ReflectKit.java b/blade-kit/src/main/java/com/blade/kit/reflect/ReflectKit.java new file mode 100644 index 000000000..fb4bd21d9 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/reflect/ReflectKit.java @@ -0,0 +1,571 @@ +/** + * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.kit.reflect; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.JarURLConnection; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.blade.kit.Emptys; +import com.blade.kit.ExceptionKit; +import com.blade.kit.StringKit; +import com.blade.kit.SystemKit; + +/** + * 有关 Reflection处理的工具类。 + * + * 这个类中的每个方法都可以“安全”地处理 null ,而不会抛出 NullPointerException。 + * + * @author biezhi + * @since 1.0 + */ +public abstract class ReflectKit { + + private static final Logger LOGGER = LoggerFactory.getLogger(ReflectKit.class); + + /** 新建对象 + * @throws IllegalAccessException + * @throws InstantiationException + * @throws ClassNotFoundException */ + public static Object newInstance(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException { + Object obj = null; + Class clazz = Class.forName(className); + obj = clazz.newInstance(); + LOGGER.debug("New {}", className); + return obj; + } + + /** + * 创建一个实例对象 + * @param clazz class对象 + * @return + * @throws IllegalAccessException + * @throws InstantiationException + */ + public static Object newInstance(Class clazz) throws InstantiationException, IllegalAccessException{ + return clazz.newInstance(); + } + + /** + * 根据类名获取Class对象 + * + * @param className 类名称 + * @return 返回Class对象 + */ + public static Class newClass(String className){ + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + } + return null; + } + + /** + * 获取包是否存在 + * + * @param packageName 包名称 + * @return 返回包是否存在 + */ + public static boolean isPackage(String packageName){ + if(StringKit.isNotBlank(packageName)){ + Package temp = Package.getPackage(packageName); + return null != temp; + } + return false; + } + + public static boolean isClass(String className){ + if(StringKit.isNotBlank(className)){ + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + } + } + return false; + } + + + /** + * 创建一个实例对象 + * @param clazz class对象 + * @return + * @throws IllegalAccessException + * @throws InstantiationException + */ + public static T newBean(Class clazz) { + try { + Object object = clazz.newInstance(); + return clazz.cast(object); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + /** 用setter设置bean属性 + * @throws InvocationTargetException + * @throws IllegalArgumentException + * @throws IllegalAccessException */ + public static void setProperty(Object bean, String name, Object value) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + String methodName = "set" + StringKit.firstUpperCase(name); + invokeMehodByName(bean, methodName, value); + } + + /** 用getter获取bean属性 + * @throws InvocationTargetException + * @throws IllegalArgumentException + * @throws IllegalAccessException */ + public static Object getProperty(Object bean, String name) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + String methodName = "get" + StringKit.firstUpperCase(name); + return invokeMehodByName(bean, methodName); + } + + /** 类型转换 */ + @SuppressWarnings("unchecked") + public static T cast(Object value, Class type) { + if (value != null && !type.isAssignableFrom(value.getClass())) { + if (is(type, int.class, Integer.class)) { + value = Integer.parseInt(String.valueOf(value)); + } else if (is(type, long.class, Long.class)) { + value = Long.parseLong(String.valueOf(value)); + } else if (is(type, float.class, Float.class)) { + value = Float.parseFloat(String.valueOf(value)); + } else if (is(type, double.class, Double.class)) { + value = Double.parseDouble(String.valueOf(value)); + } else if (is(type, boolean.class, Boolean.class)) { + value = Boolean.parseBoolean(String.valueOf(value)); + } else if (is(type, String.class)) { + value = String.valueOf(value); + } + } + return (T) value; + } + + /** 查找方法 */ + public static Method getMethodByName(Object classOrBean, String methodName) { + Method ret = null; + if (classOrBean != null) { + Class clazz = null; + if (classOrBean instanceof Class) { + clazz = (Class) classOrBean; + } else { + clazz = classOrBean.getClass(); + } + for (Method method : clazz.getMethods()) { + if (method.getName().equals(methodName)) { + ret = method; + break; + } + } + } + return ret; + } + + public static Method getMethodByName(Class clazz, String methodName) { + Method ret = null; + for (Method method : clazz.getMethods()) { + if (method.getName().equals(methodName)) { + ret = method; + break; + } + } + return ret; + } + + /*private static boolean sameType(Type[] types, Class[] clazzes) { + if (types.length != clazzes.length) { + return false; + } + for (int i = 0; i < types.length; i++) { + if (!Type.getType(clazzes[i]).equals(types[i])) { + return false; + } + } + return true; + } + + public static String[] getMethodParamsNames(final Method m) { + try { + final String[] paramNames = new String[m.getParameterTypes().length]; + Class declaringClass = m.getDeclaringClass(); + String className = declaringClass.getName(); + int lastDotIndex = className.lastIndexOf("."); + InputStream is = declaringClass.getResourceAsStream(className.substring(lastDotIndex + 1) + ".class"); + ClassReader cr = new ClassReader(is); + cr.accept(new ClassVisitor(Opcodes.ASM4) { + @Override + public MethodVisitor visitMethod(final int access, final String name, final String desc, + final String signature, final String[] exceptions) { + + final Type[] args = Type.getArgumentTypes(desc); + // 方法名相同并且参数个数相同 + if (!name.equals(m.getName()) || !sameType(args, m.getParameterTypes())) { + return super.visitMethod(access, name, desc, signature, exceptions); + } + MethodVisitor v = super.visitMethod(access, name, desc, signature, exceptions); + return new MethodVisitor(Opcodes.ASM4, v) { + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, + Label end, int index) { + int i = index - 1; + // 如果是静态方法,则第一就是参数 + // 如果不是静态方法,则第一个是"this",然后才是方法的参数 + if (Modifier.isStatic(m.getModifiers())) { + i = index; + } + if (i >= 0 && i < paramNames.length) { + paramNames[i] = name; + } + super.visitLocalVariable(name, desc, signature, start, end, index); + } + }; + } + }, 0); + return paramNames; + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + }*/ + + /** + * + * @param bean 类实例 + * @param methodName 方法名称 + * @param args 方法参数 + * @return + * @throws InvocationTargetException + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + public static Object invokeMehodByName(Object bean, String methodName, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Method method = getMethodByName(bean, methodName); + Class[] types = method.getParameterTypes(); + + int argCount = args == null ? 0 : args.length; + + // 参数个数对不上 + ExceptionKit.makeRunTimeWhen(argCount != types.length, + "%s in %s", methodName, bean); + + // 转参数类型 + for (int i = 0; i < argCount; i++) { + args[i] = cast(args[i], types[i]); + } + return method.invoke(bean, args); + } + + /** + * + * @param bean 类实例 + * @param methodName 方法名称 + * @param args 方法参数 + * @return + * @throws InvocationTargetException + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + public static Object invokeMehod(Object bean, Method method, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Class[] types = method.getParameterTypes(); + + int argCount = args == null ? 0 : args.length; + + // 参数个数对不上 + ExceptionKit.makeRunTimeWhen(argCount != types.length, "%s in %s", method.getName(), bean); + + // 转参数类型 + for (int i = 0; i < argCount; i++) { + args[i] = cast(args[i], types[i]); + } + return method.invoke(bean, args); + } + + // ------------------------------------------------------ + + /** 对象是否其中一个 */ + public static boolean is(Object obj, Object... mybe) { + if (obj != null && mybe != null) { + for (Object mb : mybe) + if (obj.equals(mb)) + return true; + } + return false; + } + + public static boolean isNot(Object obj, Object... mybe) { + return !is(obj, mybe); + } + + // ------------------------------------------------------ + + /** 扫描包下面所有的类 */ + public static List scanPackageClass(String rootPackageName) { + List classNames = new ArrayList(); + try { + ClassLoader loader = ReflectKit.class.getClassLoader(); + URL url = loader.getResource(rootPackageName.replace('.', '/')); + + ExceptionKit.makeRunTimeWhen(url == null, "package[%s] not found!", rootPackageName); + + String protocol = url.getProtocol(); + if ("file".equals(protocol)) { + LOGGER.debug("Scan in file ..."); + File[] files = new File(url.toURI()).listFiles(); + for (File f : files) { + scanPackageClassInFile(rootPackageName, f, classNames); + } + } else if ("jar".equals(protocol)) { + LOGGER.debug("Scan in jar ..."); + JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile(); + scanPackageClassInJar(jar, rootPackageName, classNames); + } + + } catch (URISyntaxException e) { + } catch (IOException e) { + } + return classNames; + } + + /** 扫描文件夹下所有class文件 */ + private static void scanPackageClassInFile(String rootPackageName, + File rootFile, List classNames) { + String absFileName = rootPackageName + "." + rootFile.getName(); + if (rootFile.isFile() && absFileName.endsWith(".class")) { + classNames.add(absFileName.substring(0, absFileName.length() - 6)); + } else if (rootFile.isDirectory()) { + String tmPackageName = rootPackageName + "." + rootFile.getName(); + for (File f : rootFile.listFiles()) { + scanPackageClassInFile(tmPackageName, f, classNames); + } + } + } + + /** + * 扫描jar里面的类 + * @param jar jar包文件 + * @param packageDirName 包目录 + * @param classNames class名称列表 + */ + private static void scanPackageClassInJar(JarFile jar, String packageDirName, List classNames) { + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName().replace('/', '.'); + if (name.startsWith(packageDirName) && name.endsWith(".class")) { + classNames.add(name.substring(0, name.length() - 6)); + } + } + } + + + /** + * 方法调用,如果clazznull,返回null; + *

+ * 如果methodnull,返回null + *

+ * 如果targetnull,则为静态方法 + * + * @param method 调用的方法 + * @param target 目标对象 + * @param args 方法的参数值 + * @return 调用结果 + * @throws InvocationTargetException + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + public static Object invokeMethod(Method method, Object target, Object...args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + if (method == null) { + return null; + } + method.setAccessible(true); + return method.invoke(target, args); + } + + /** + *

+ * 调用一个命名的方法,其参数类型相匹配的对象类型。 + *

+ * + * + * @param object 调用方法作用的对象 + * @param methodName 方法名 + * @param args 参数值 + * @param parameterTypes 参数类型 + * @return 调用的方法的返回值 + * @throws InvocationTargetException + * @throws IllegalArgumentException + * @throws IllegalAccessException + * + */ + public static Object invokeMethod(Object object, String methodName, Object[] args, Class...parameterTypes) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + if (object == null || StringKit.isEmpty(methodName)) { + return null; + } + + if (parameterTypes == null) { + parameterTypes = Emptys.EMPTY_CLASS_ARRAY; + } + if (args == null) { + args = Emptys.EMPTY_OBJECT_ARRAY; + } + Method method; + try { + method = object.getClass().getDeclaredMethod(methodName, parameterTypes); + } catch (Exception ex) { + throw ExceptionKit.toRuntimeException(ex); + } + if (method == null) { + return null; + } + return invokeMethod(method, object, args); + } + + /** + *

+ * 调用一个命名的静态方法,其参数类型相匹配的对象类型。 + *

+ * + * + * @param clazz 调用方法作用的类 + * @param methodName 方法名 + * @param args 参数值 + * @param parameterTypes 参数类型 + * @return 调用的方法的返回值 + * @throws InvocationTargetException + * @throws IllegalArgumentException + * @throws IllegalAccessException + * + */ + public static Object invokeStaticMethod(Class clazz, String methodName, Object[] args, Class...parameterTypes) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + if (parameterTypes == null) { + parameterTypes = Emptys.EMPTY_CLASS_ARRAY; + } + if (args == null) { + args = Emptys.EMPTY_OBJECT_ARRAY; + } + Method method; + try { + method = clazz.getDeclaredMethod(methodName, parameterTypes); + } catch (Exception ex) { + throw ExceptionKit.toRuntimeException(ex); + } + if (method == null) { + return null; + } + return invokeMethod(method, null, args); + } + + // ========================================================================== + // 辅助方法。 + // ========================================================================== + + private static final Method IS_SYNTHETIC; + static { + Method isSynthetic = null; + if (SystemKit.getJavaInfo().isJavaVersionAtLeast(1.5f)) { + // cannot call synthetic methods: + try { + isSynthetic = Member.class.getMethod("isSynthetic", Emptys.EMPTY_CLASS_ARRAY); + } catch (Exception e) { + // ignore + } + } + IS_SYNTHETIC = isSynthetic; + } + + public static boolean isAccessible(Member m) { + return m != null && Modifier.isPublic(m.getModifiers()) && !isSynthetic(m); + } + + static boolean isSynthetic(Member m) { + if (IS_SYNTHETIC != null) { + try { + return ((Boolean) IS_SYNTHETIC.invoke(m, (Object[]) null)).booleanValue(); + } catch (Exception e) { + } + } + return false; + } + + /** + * Check whether the {@link Class} identified by the supplied name is present. + * + * @param className the name of the class to check + * @return true if class is present, false otherwise + */ + public static boolean isPresent(String className) { + try { + // what's wrong with old plain Class.forName + // this code supposed to work everywhere including containers + Class.forName(className); + // getClassLoader().loadClass(className); + return true; + } + catch (Throwable ex) { + return false; + } + } + + public static boolean isPublic(Member m) { + return m != null && Modifier.isPublic(m.getModifiers()); + } + + public static void forceAccess(AccessibleObject object) { + if (object == null || object.isAccessible()) { + return; + } + try { + object.setAccessible(true); + } catch (SecurityException e) { + throw ExceptionKit.toRuntimeException(e); + } + } + + public static boolean hasInterface(Class type, Class interfaceType) { + if(null != type && null != interfaceType){ + Class[] interfaces = type.getInterfaces(); + if(null != interfaces && interfaces.length > 0){ + for(Class inte : interfaces){ + if(inte == interfaceType){ + return true; + } + } + } + } + return false; + } + +} \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/resource/AbstractClassReader.java b/blade-kit/src/main/java/com/blade/kit/resource/AbstractClassReader.java similarity index 58% rename from blade-kit/src/main/java/blade/kit/resource/AbstractClassReader.java rename to blade-kit/src/main/java/com/blade/kit/resource/AbstractClassReader.java index 75c2893f5..6d9ad3c2a 100644 --- a/blade-kit/src/main/java/blade/kit/resource/AbstractClassReader.java +++ b/blade-kit/src/main/java/com/blade/kit/resource/AbstractClassReader.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.kit.resource; +package com.blade.kit.resource; import java.io.File; import java.io.FileFilter; @@ -24,9 +24,12 @@ import java.util.Enumeration; import java.util.Set; -import blade.kit.Assert; -import blade.kit.CollectionKit; -import blade.kit.log.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.blade.kit.Assert; +import com.blade.kit.CollectionKit; +import com.blade.kit.exception.ClassReaderException; /** * 抽象类读取器 @@ -36,10 +39,10 @@ */ public abstract class AbstractClassReader implements ClassReader { - private static final Logger LOGGER = Logger.getLogger(AbstractClassReader.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractClassReader.class); @Override - public Set> getClass(String packageName, boolean recursive) { + public Set getClass(String packageName, boolean recursive) { return this.getClassByAnnotation(packageName, null, null, recursive); } @@ -47,7 +50,7 @@ public Set> getClass(String packageName, boolean recursive) { * 默认实现以文件形式的读取 */ @Override - public Set> getClass(String packageName, Class parent, boolean recursive) { + public Set getClass(String packageName, Class parent, boolean recursive) { return this.getClassByAnnotation(packageName, parent, null, recursive); } @@ -60,12 +63,15 @@ public Set> getClass(String packageName, Class parent, boolean recur * @param recursive * @return */ - private Set> findClassByPackage(final String packageName, final String packagePath, final Class parent, final Class annotation, final boolean recursive, Set> classes) { + private Set findClassByPackage(final String packageName, final String packagePath, + final Class parent, final Class annotation, + final boolean recursive, Set classes) throws ClassNotFoundException { + // 获取此包的目录 建立一个File File dir = new File(packagePath); // 如果不存在或者 也不是目录就直接返回 if ((!dir.exists()) || (!dir.isDirectory())) { - LOGGER.warn("包 " + packageName + " 不存在!"); + LOGGER.warn("The package [{}] not found.", packageName); } // 如果存在 就获取包下的所有文件 包括目录 File[] dirfiles = accept(dir, recursive); @@ -78,35 +84,32 @@ private Set> findClassByPackage(final String packageName, final String } else { // 如果是java类文件 去掉后面的.class 只留下类名 String className = file.getName().substring(0, file.getName().length() - 6); - try { +// Class clazz = classLoader.defineClassByName(packageName + '.' + className); Class clazz = Class.forName(packageName + '.' + className); - if(null != parent && null != annotation){ - if(null != clazz.getSuperclass() && clazz.getSuperclass().equals(parent) && - null != clazz.getAnnotation(annotation)){ - classes.add(clazz); - } - continue; - } - if(null != parent){ - if(null != clazz.getSuperclass() && clazz.getSuperclass().equals(parent)){ - classes.add(clazz); - } else { - if(null != clazz.getInterfaces() && clazz.getInterfaces().length > 0 && clazz.getInterfaces()[0].equals(parent)){ - classes.add(clazz); - } + if(null != parent && null != annotation){ + if(null != clazz.getSuperclass() && clazz.getSuperclass().equals(parent) && + null != clazz.getAnnotation(annotation)){ + classes.add(new ClassInfo(clazz)); + } + continue; + } + if(null != parent){ + if(null != clazz.getSuperclass() && clazz.getSuperclass().equals(parent)){ + classes.add(new ClassInfo(clazz)); + } else { + if(null != clazz.getInterfaces() && clazz.getInterfaces().length > 0 && clazz.getInterfaces()[0].equals(parent)){ + classes.add(new ClassInfo(clazz)); } - continue; - } - if(null != annotation){ - if(null != clazz.getAnnotation(annotation)){ - classes.add(clazz); - } - continue; - } - classes.add(clazz); - } catch (ClassNotFoundException e) { - LOGGER.error("在扫描用户定义视图时从jar包获取文件出错,找不到.class类文件:" + e.getMessage()); - } + } + continue; + } + if(null != annotation){ + if(null != clazz.getAnnotation(annotation)){ + classes.add(new ClassInfo(clazz)); + } + continue; + } + classes.add(new ClassInfo(clazz)); } } } @@ -130,34 +133,37 @@ public boolean accept(File file) { } @Override - public Set> getClassByAnnotation(String packageName, Class annotation, boolean recursive) { + public Set getClassByAnnotation(String packageName, Class annotation, boolean recursive) { return this.getClassByAnnotation(packageName, null, annotation, recursive); } - + @Override - public Set> getClassByAnnotation(String packageName, Class parent, Class annotation, boolean recursive) { + public Set getClassByAnnotation(String packageName, Class parent, Class annotation, boolean recursive) { Assert.notBlank(packageName); - Set> classes = CollectionKit.newHashSet(); + Set classes = CollectionKit.newHashSet(); // 获取包的名字 并进行替换 String packageDirName = packageName.replace('.', '/'); // 定义一个枚举的集合 并进行循环来处理这个目录下的URL Enumeration dirs; try { - dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); + dirs = this.getClass().getClassLoader().getResources(packageDirName); // 循环迭代下去 while (dirs.hasMoreElements()) { // 获取下一个元素 URL url = dirs.nextElement(); // 获取包的物理路径 String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); - Set> subClasses = findClassByPackage(packageName, filePath, parent, annotation, recursive, classes); + Set subClasses = findClassByPackage(packageName, filePath, parent, annotation, recursive, classes); if(subClasses.size() > 0){ classes.addAll(subClasses); } } } catch (IOException e) { - LOGGER.error(e.getMessage()); - } + LOGGER.error(e.getMessage(), e); + } catch (ClassNotFoundException e) { + LOGGER.error("Add user custom view class error Can't find such Class files."); + throw new ClassReaderException(e); + } return classes; } diff --git a/blade-kit/src/main/java/com/blade/kit/resource/BladeClassLoader.java b/blade-kit/src/main/java/com/blade/kit/resource/BladeClassLoader.java new file mode 100644 index 000000000..5168ddbde --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/resource/BladeClassLoader.java @@ -0,0 +1,12 @@ +package com.blade.kit.resource; + +public class BladeClassLoader extends ClassLoader { + + public BladeClassLoader() { + } + + public Class defineClassByName(String name) throws ClassNotFoundException{ + return Class.forName(name); + } + +} diff --git a/blade-kit/src/main/java/com/blade/kit/resource/ClassInfo.java b/blade-kit/src/main/java/com/blade/kit/resource/ClassInfo.java new file mode 100644 index 000000000..0ee29ba52 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/resource/ClassInfo.java @@ -0,0 +1,38 @@ +package com.blade.kit.resource; + +public class ClassInfo { + + private String className; + private Class clazz; + + public ClassInfo(String className) { + this.className = className; + } + + public ClassInfo(Class clazz) { + this.clazz = clazz; + this.className = clazz.getName(); + } + + public ClassInfo(String className, Class clazz) { + this.clazz = clazz; + this.className = className; + } + + public String getClassName() { + return className; + } + + public Class getClazz() { + return clazz; + } + + public Object newInstance() throws InstantiationException, IllegalAccessException{ + return clazz.newInstance(); + } + + @Override + public String toString() { + return clazz.toString(); + } +} diff --git a/blade-kit/src/main/java/blade/kit/resource/ClassPathClassReader.java b/blade-kit/src/main/java/com/blade/kit/resource/ClassPathClassReader.java similarity index 93% rename from blade-kit/src/main/java/blade/kit/resource/ClassPathClassReader.java rename to blade-kit/src/main/java/com/blade/kit/resource/ClassPathClassReader.java index 54cc8ad18..ab86badf5 100644 --- a/blade-kit/src/main/java/blade/kit/resource/ClassPathClassReader.java +++ b/blade-kit/src/main/java/com/blade/kit/resource/ClassPathClassReader.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.kit.resource; +package com.blade.kit.resource; /** * 根据classpath加载类 @@ -22,5 +22,5 @@ * @since 1.0 */ public class ClassPathClassReader extends AbstractClassReader implements ClassReader { - + } \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/resource/ClassReader.java b/blade-kit/src/main/java/com/blade/kit/resource/ClassReader.java similarity index 63% rename from blade-kit/src/main/java/blade/kit/resource/ClassReader.java rename to blade-kit/src/main/java/com/blade/kit/resource/ClassReader.java index b936fb3c5..84e1226d0 100644 --- a/blade-kit/src/main/java/blade/kit/resource/ClassReader.java +++ b/blade-kit/src/main/java/com/blade/kit/resource/ClassReader.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.kit.resource; +package com.blade.kit.resource; import java.lang.annotation.Annotation; import java.util.Set; @@ -26,12 +26,12 @@ */ public interface ClassReader { - public Set> getClass(String packageName, boolean recursive); + Set getClass(String packageName, boolean recursive); - public Set> getClass(String packageName, Class parent, boolean recursive); + Set getClass(String packageName, Class parent, boolean recursive); - public Set> getClassByAnnotation(String packageName, Class annotation, boolean recursive); + Set getClassByAnnotation(String packageName, Class annotation, boolean recursive); - public Set> getClassByAnnotation(String packageName, Class parent, Class annotation, boolean recursive); + Set getClassByAnnotation(String packageName, Class parent, Class annotation, boolean recursive); } \ No newline at end of file diff --git a/blade-kit/src/main/java/blade/kit/resource/JarReaderImpl.java b/blade-kit/src/main/java/com/blade/kit/resource/JarReaderImpl.java similarity index 54% rename from blade-kit/src/main/java/blade/kit/resource/JarReaderImpl.java rename to blade-kit/src/main/java/com/blade/kit/resource/JarReaderImpl.java index 884c96f75..701f3a8bf 100644 --- a/blade-kit/src/main/java/blade/kit/resource/JarReaderImpl.java +++ b/blade-kit/src/main/java/com/blade/kit/resource/JarReaderImpl.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package blade.kit.resource; +package com.blade.kit.resource; import java.io.IOException; import java.lang.annotation.Annotation; @@ -24,10 +24,11 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; -import blade.exception.ClassReaderException; -import blade.kit.Assert; -import blade.kit.CollectionKit; -import blade.kit.log.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.blade.kit.Assert; +import com.blade.kit.CollectionKit; /** * 根据jar文件读取类 @@ -37,60 +38,60 @@ */ public class JarReaderImpl extends AbstractClassReader implements ClassReader { - private static final Logger LOGGER = Logger.getLogger(JarReaderImpl.class); + private static final Logger LOGGER = LoggerFactory.getLogger(JarReaderImpl.class); @Override - public Set> getClass(String packageName, boolean recursive) { + public Set getClass(String packageName, boolean recursive) { return this.getClassByAnnotation(packageName, null, null, recursive); } @Override - public Set> getClass(String packageName, Class parent, boolean recursive) { + public Set getClass(String packageName, Class parent, boolean recursive) { return this.getClassByAnnotation(packageName, parent, null, recursive); } @Override - public Set> getClassByAnnotation(String packageName, Class annotation, boolean recursive) { + public Set getClassByAnnotation(String packageName, Class annotation, boolean recursive) { return this.getClassByAnnotation(packageName, null, annotation, recursive); } @Override - public Set> getClassByAnnotation(String packageName, Class parent, Class annotation, boolean recursive) { + public Set getClassByAnnotation(String packageName, Class parent, Class annotation, boolean recursive) { Assert.notBlank(packageName); - Set> classes = CollectionKit.newHashSet(); + Set classes = CollectionKit.newHashSet(); // 获取包的名字 并进行替换 String packageDirName = packageName.replace('.', '/'); // 定义一个枚举的集合 并进行循环来处理这个目录下的URL Enumeration dirs; try { - dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); + dirs = this.getClass().getClassLoader().getResources(packageDirName); // 循环迭代下去 while (dirs.hasMoreElements()) { // 获取下一个元素 URL url = dirs.nextElement(); - Set> subClasses = this.getClasses(url, packageDirName, packageName, parent, annotation, recursive, classes); + Set subClasses = this.getClasses(url, packageDirName, packageName, parent, annotation, recursive, classes); if(subClasses.size() > 0){ classes.addAll(subClasses); } } } catch (IOException e) { - LOGGER.error(e.getMessage()); + LOGGER.error(e.getMessage(), e); } return classes; } - private Set> getClasses(final URL url, final String packageDirName, String packageName, final Class parent, - final Class annotation, final boolean recursive, Set> classes){ + private Set getClasses(final URL url, final String packageDirName, String packageName, final Class parent, + final Class annotation, final boolean recursive, Set classes){ try { - if( url.toString( ).startsWith( "jar:file:" ) || url.toString( ).startsWith( "wsjar:file:" ) ) { + if( url.toString().startsWith( "jar:file:" ) || url.toString().startsWith( "wsjar:file:" ) ) { // 获取jar - JarFile jarFile = ( (JarURLConnection)url.openConnection() ).getJarFile( ); + JarFile jarFile = ( (JarURLConnection)url.openConnection() ).getJarFile(); // 从此jar包 得到一个枚举类 - Enumeration eje = jarFile.entries( ); + Enumeration eje = jarFile.entries(); - // 同样的进行循环迭代 + // 同样的进行循环迭代 while (eje.hasMoreElements()) { // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件 JarEntry entry = eje.nextElement(); @@ -114,40 +115,38 @@ private Set> getClasses(final URL url, final String packageDirName, Str if (name.endsWith(".class") && !entry.isDirectory()) { // 去掉后面的".class" 获取真正的类名 String className = name.substring(packageName.length() + 1, name.length() - 6); - try { - // 添加到classes + // 添加到classes Class clazz = Class.forName(packageName + '.' + className); - if(null != parent && null != annotation){ - if(null != clazz.getSuperclass() && - clazz.getSuperclass().equals(parent) && null != clazz.getAnnotation(annotation)){ - classes.add(clazz); - } - continue; - } - if(null != parent){ - if(null != clazz.getSuperclass() && clazz.getSuperclass().equals(parent)){ - classes.add(clazz); - } - continue; - } - if(null != annotation){ - if(null != clazz.getAnnotation(annotation)){ - classes.add(clazz); - } - continue; - } - classes.add(clazz); - } catch (ClassNotFoundException e) { - LOGGER.error("添加用户自定义视图类错误 找不到此类的.class文件"); - throw new ClassReaderException(e); + if(null != parent && null != annotation){ + if(null != clazz.getSuperclass() && + clazz.getSuperclass().equals(parent) && null != clazz.getAnnotation(annotation)){ + classes.add(new ClassInfo(clazz)); + } + continue; + } + if(null != parent){ + if(null != clazz.getSuperclass() && clazz.getSuperclass().equals(parent)){ + classes.add(new ClassInfo(clazz)); + } + continue; + } + if(null != annotation){ + if(null != clazz.getAnnotation(annotation)){ + classes.add(new ClassInfo(clazz)); + } + continue; } + classes.add(new ClassInfo(clazz)); } } } } } } catch (IOException e) { - LOGGER.error("在扫描用户定义视图时从jar包获取文件出错:{}", e.getMessage()); + LOGGER.error("The scan error when the user to define the view from a jar package file.", e); + } catch (ClassNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } return classes; } diff --git a/blade-kit/src/main/java/com/blade/kit/resource/package-info.java b/blade-kit/src/main/java/com/blade/kit/resource/package-info.java new file mode 100644 index 000000000..2a268b644 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/resource/package-info.java @@ -0,0 +1,4 @@ +/** + * 资源搜索包 + */ +package com.blade.kit.resource; \ No newline at end of file diff --git a/blade-kit/src/main/java/com/blade/kit/text/HTMLFilter.java b/blade-kit/src/main/java/com/blade/kit/text/HTMLFilter.java new file mode 100644 index 000000000..3cb5dccd4 --- /dev/null +++ b/blade-kit/src/main/java/com/blade/kit/text/HTMLFilter.java @@ -0,0 +1,538 @@ +package com.blade.kit.text; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** +* +* HTML filtering utility for protecting against XSS (Cross Site Scripting). +* +* This code is licensed LGPLv3 +* +* This code is a Java port of the original work in PHP by Cal Hendersen. +* http://code.iamcal.com/php/lib_filter/ +* +* The trickiest part of the translation was handling the differences in regex handling +* between PHP and Java. These resources were helpful in the process: +* +* http://java.sun.com/j2se/1.4.2/docs/api/java/util/regex/Pattern.html +* http://us2.php.net/manual/en/reference.pcre.pattern.modifiers.php +* http://www.regular-expressions.info/modifiers.html +* +* A note on naming conventions: instance variables are prefixed with a "v"; global +* constants are in all caps. +* +* Sample use: +* String input = ... +* String clean = new HTMLFilter().filter( input ); +* +* The class is not thread safe. Create a new instance if in doubt. +* +* If you find bugs or have suggestions on improvement (especially regarding +* performance), please contact us. The latest version of this +* source, and our contact details, can be found at http://xss-html-filter.sf.net +* +* @author Joseph O'Connell +* @author Cal Hendersen +* @author Michael Semb Wever +*/ +public final class HTMLFilter { + + /** regex flag union representing /si modifiers in php **/ + private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; + private static final Pattern P_COMMENTS = Pattern.compile("", Pattern.DOTALL); + private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI); + private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL); + private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI); + private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI); + private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI); + private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI); + private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI); + private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?"); + private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?"); + private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?"); + private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))"); + private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL); + private static final Pattern P_END_ARROW = Pattern.compile("^>"); + private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_AMP = Pattern.compile("&"); + private static final Pattern P_QUOTE = Pattern.compile("\""); + private static final Pattern P_LEFT_ARROW = Pattern.compile("<"); + private static final Pattern P_RIGHT_ARROW = Pattern.compile(">"); + private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>"); + + // @xxx could grow large... maybe use sesat's ReferenceMap + private static final ConcurrentMap P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap(); + + /** set of allowed html elements, along with allowed attributes for each element **/ + private final Map> vAllowed; + /** counts of open tags for each (allowable) html element **/ + private final Map vTagCounts = new HashMap(); + + /** html elements which must always be self-closing (e.g. "") **/ + private final String[] vSelfClosingTags; + /** html elements which must always have separate opening and closing tags (e.g. "") **/ + private final String[] vNeedClosingTags; + /** set of disallowed html elements **/ + private final String[] vDisallowed; + /** attributes which should be checked for valid protocols **/ + private final String[] vProtocolAtts; + /** allowed protocols **/ + private final String[] vAllowedProtocols; + /** tags which should be removed if they contain no content (e.g. "" or "") **/ + private final String[] vRemoveBlanks; + /** entities allowed within html markup **/ + private final String[] vAllowedEntities; + /** flag determining whether comments are allowed in input String. */ + private final boolean stripComment; + private final boolean encodeQuotes; + private boolean vDebug = false; + /** + * flag determining whether to try to make tags when presented with "unbalanced" + * angle brackets (e.g. "" becomes " text "). If set to false, + * unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** Default constructor. + * + */ + public HTMLFilter() { + vAllowed = new HashMap>(); + + final ArrayList a_atts = new ArrayList(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList img_atts = new ArrayList(); + img_atts.add("src"); + img_atts.add("width"); + img_atts.add("height"); + img_atts.add("alt"); + vAllowed.put("img", img_atts); + + final ArrayList no_atts = new ArrayList(); + vAllowed.put("b", no_atts); + vAllowed.put("strong", no_atts); + vAllowed.put("i", no_atts); + vAllowed.put("em", no_atts); + + vSelfClosingTags = new String[]{"img"}; + vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"}; + vDisallowed = new String[]{}; + vAllowedProtocols = new String[]{"http", "mailto", "https"}; // no ftp. + vProtocolAtts = new String[]{"src", "href"}; + vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"}; + vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"}; + stripComment = true; + encodeQuotes = true; + alwaysMakeTags = true; + } + + /** Set debug flag to true. Otherwise use default settings. See the default constructor. + * + * @param debug turn debug on with a true argument + */ + public HTMLFilter(final boolean debug) { + this(); + vDebug = debug; + + } + + /** Map-parameter configurable constructor. + * + * @param conf map containing configuration. keys match field names. + */ + @SuppressWarnings("unchecked") + public HTMLFilter(final Map conf) { + + assert conf.containsKey("vAllowed") : "configuration requires vAllowed"; + assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags"; + assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags"; + assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed"; + assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols"; + assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts"; + assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks"; + assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities"; + + vAllowed = Collections.unmodifiableMap((HashMap>) conf.get("vAllowed")); + vSelfClosingTags = (String[]) conf.get("vSelfClosingTags"); + vNeedClosingTags = (String[]) conf.get("vNeedClosingTags"); + vDisallowed = (String[]) conf.get("vDisallowed"); + vAllowedProtocols = (String[]) conf.get("vAllowedProtocols"); + vProtocolAtts = (String[]) conf.get("vProtocolAtts"); + vRemoveBlanks = (String[]) conf.get("vRemoveBlanks"); + vAllowedEntities = (String[]) conf.get("vAllowedEntities"); + stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true; + encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true; + alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true; + } + + private void reset() { + vTagCounts.clear(); + } + + private void debug(final String msg) { + if (vDebug) { + Logger.getAnonymousLogger().info(msg); + } + } + + //--------------------------------------------------------------- + // my versions of some PHP library functions + public static String chr(final int decimal) { + return String.valueOf((char) decimal); + } + + public static String htmlSpecialChars(final String s) { + if(null == s){ + return s; + } + String result = s; + result = regexReplace(P_AMP, "&", result); + result = regexReplace(P_QUOTE, """, result); + result = regexReplace(P_LEFT_ARROW, "<", result); + result = regexReplace(P_RIGHT_ARROW, ">", result); + return result; + } + + //--------------------------------------------------------------- + /** + * given a user submitted input String, filter out any invalid or restricted + * html. + * + * @param input text (i.e. submitted by a user) than may contain html + * @return "clean" version of input, with only valid, whitelisted html elements allowed + */ + public String filter(final String input) { + reset(); + String s = input; + + debug("************************************************"); + debug(" INPUT: " + input); + + s = escapeComments(s); + debug(" escapeComments: " + s); + + s = balanceHTML(s); + debug(" balanceHTML: " + s); + + s = checkTags(s); + debug(" checkTags: " + s); + + s = processRemoveBlanks(s); + debug("processRemoveBlanks: " + s); + + s = validateEntities(s); + debug(" validateEntites: " + s); + + debug("************************************************\n\n"); + return s; + } + + public boolean isAlwaysMakeTags(){ + return alwaysMakeTags; + } + + public boolean isStripComments(){ + return stripComment; + } + + private String escapeComments(final String s) { + final Matcher m = P_COMMENTS.matcher(s); + final StringBuffer buf = new StringBuffer(); + if (m.find()) { + final String match = m.group(1); //(.*?) + m.appendReplacement(buf, Matcher.quoteReplacement("")); + } + m.appendTail(buf); + + return buf.toString(); + } + + private String balanceHTML(String s) { + if (alwaysMakeTags) { + // + // try and form html + // + s = regexReplace(P_END_ARROW, "", s); + s = regexReplace(P_BODY_TO_END, "<$1>", s); + s = regexReplace(P_XML_CONTENT, "$1<$2", s); + + } else { + // + // escape stray brackets + // + s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s); + s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); + + // + // the last regexp causes '<>' entities to appear + // (we need to do a lookahead assertion so that the last bracket can + // be used in the next pass of the regexp) + // + s = regexReplace(P_BOTH_ARROWS, "", s); + } + + return s; + } + + private String checkTags(String s) { + Matcher m = P_TAGS.matcher(s); + + final StringBuffer buf = new StringBuffer(); + while (m.find()) { + String replaceStr = m.group(1); + replaceStr = processTag(replaceStr); + m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr)); + } + m.appendTail(buf); + + s = buf.toString(); + + // these get tallied in processTag + // (remember to reset before subsequent calls to filter method) + for (String key : vTagCounts.keySet()) { + for (int ii = 0; ii < vTagCounts.get(key); ii++) { + s += ""; + } + } + + return s; + } + + private String processRemoveBlanks(final String s) { + String result = s; + for (String tag : vRemoveBlanks) { + if(!P_REMOVE_PAIR_BLANKS.containsKey(tag)){ + P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?>")); + } + result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result); + if(!P_REMOVE_SELF_BLANKS.containsKey(tag)){ + P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>")); + } + result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result); + } + + return result; + } + + private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) { + Matcher m = regex_pattern.matcher(s); + return m.replaceAll(replacement); + } + + private String processTag(final String s) { + // ending tags + Matcher m = P_END_TAG.matcher(s); + if (m.find()) { + final String name = m.group(1).toLowerCase(); + if (allowed(name)) { + if (!inArray(name, vSelfClosingTags)) { + if (vTagCounts.containsKey(name)) { + vTagCounts.put(name, vTagCounts.get(name) - 1); + return ""; + } + } + } + } + + // starting tags + m = P_START_TAG.matcher(s); + if (m.find()) { + final String name = m.group(1).toLowerCase(); + final String body = m.group(2); + String ending = m.group(3); + + //debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" ); + if (allowed(name)) { + String params = ""; + + final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body); + final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body); + final List paramNames = new ArrayList(); + final List paramValues = new ArrayList(); + while (m2.find()) { + paramNames.add(m2.group(1)); //([a-z0-9]+) + paramValues.add(m2.group(3)); //(.*?) + } + while (m3.find()) { + paramNames.add(m3.group(1)); //([a-z0-9]+) + paramValues.add(m3.group(3)); //([^\"\\s']+) + } + + String paramName, paramValue; + for (int ii = 0; ii < paramNames.size(); ii++) { + paramName = paramNames.get(ii).toLowerCase(); + paramValue = paramValues.get(ii); + +// debug( "paramName='" + paramName + "'" ); +// debug( "paramValue='" + paramValue + "'" ); +// debug( "allowed? " + vAllowed.get( name ).contains( paramName ) ); + + if (allowedAttribute(name, paramName)) { + if (inArray(paramName, vProtocolAtts)) { + paramValue = processParamProtocol(paramValue); + } + params += " " + paramName + "=\"" + paramValue + "\""; + } + } + + if (inArray(name, vSelfClosingTags)) { + ending = " /"; + } + + if (inArray(name, vNeedClosingTags)) { + ending = ""; + } + + if (ending == null || ending.length() < 1) { + if (vTagCounts.containsKey(name)) { + vTagCounts.put(name, vTagCounts.get(name) + 1); + } else { + vTagCounts.put(name, 1); + } + } else { + ending = " /"; + } + return "<" + name + params + ending + ">"; + } else { + return ""; + } + } + + // comments + m = P_COMMENT.matcher(s); + if (!stripComment && m.find()) { + return "<" + m.group() + ">"; + } + + return ""; + } + + private String processParamProtocol(String s) { + s = decodeEntities(s); + final Matcher m = P_PROTOCOL.matcher(s); + if (m.find()) { + final String protocol = m.group(1); + if (!inArray(protocol, vAllowedProtocols)) { + // bad protocol, turn into local anchor link instead + s = "#" + s.substring(protocol.length() + 1, s.length()); + if (s.startsWith("#//")) { + s = "#" + s.substring(3, s.length()); + } + } + } + + return s; + } + + private String decodeEntities(String s) { + StringBuffer buf = new StringBuffer(); + + Matcher m = P_ENTITY.matcher(s); + while (m.find()) { + final String match = m.group(1); + final int decimal = Integer.decode(match).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENTITY_UNICODE.matcher(s); + while (m.find()) { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENCODE.matcher(s); + while (m.find()) { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + s = validateEntities(s); + return s; + } + + private String validateEntities(final String s) { + StringBuffer buf = new StringBuffer(); + + // validate entities throughout the string + Matcher m = P_VALID_ENTITIES.matcher(s); + while (m.find()) { + final String one = m.group(1); //([^&;]*) + final String two = m.group(2); //(?=(;|&|$)) + m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two))); + } + m.appendTail(buf); + + return encodeQuotes(buf.toString()); + } + + private String encodeQuotes(final String s){ + if(encodeQuotes){ + StringBuffer buf = new StringBuffer(); + Matcher m = P_VALID_QUOTES.matcher(s); + while (m.find()) { + final String one = m.group(1); //(>|^) + final String two = m.group(2); //([^<]+?) + final String three = m.group(3); //(<|$) + m.appendReplacement(buf, Matcher.quoteReplacement(one + regexReplace(P_QUOTE, """, two) + three)); + } + m.appendTail(buf); + return buf.toString(); + }else{ + return s; + } + } + + private String checkEntity(final String preamble, final String term) { + + return ";".equals(term) && isValidEntity(preamble) + ? '&' + preamble + : "&" + preamble; + } + + private boolean isValidEntity(final String entity) { + return inArray(entity, vAllowedEntities); + } + + private static boolean inArray(final String s, final String[] array) { + for (String item : array) { + if (item != null && item.equals(s)) { + return true; + } + } + return false; + } + + private boolean allowed(final String name) { + return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed); + } + + private boolean allowedAttribute(final String name, final String paramName) { + return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName)); + } +} \ No newline at end of file diff --git a/blade-kit/src/test/java/blade/test/LogTest.java b/blade-kit/src/test/java/blade/test/LogTest.java deleted file mode 100644 index 58c983b93..000000000 --- a/blade-kit/src/test/java/blade/test/LogTest.java +++ /dev/null @@ -1,14 +0,0 @@ -package blade.test; -import blade.kit.log.Logger; -import blade.kit.log.SysLoggerAdaptor; - - -public class LogTest { - - public static void main(String[] args) { - Logger.setLoggerImpl(SysLoggerAdaptor.class); - Logger logger = Logger.getLogger(LogTest.class); - //[duxue]2015-07-06 11:02:56,606 INFO [localhost-startStop-1] org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions | Loading XML bean definitions - logger.info("hello %s", "aaa"); - } -} diff --git a/blade-kit/src/test/java/blade/test/MailTest.java b/blade-kit/src/test/java/blade/test/MailTest.java deleted file mode 100644 index 1a9ee9612..000000000 --- a/blade-kit/src/test/java/blade/test/MailTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package blade.test; - -import org.junit.Before; -import org.junit.Test; - -import blade.kit.MailKit; - -public class MailTest { - - @Before - public void before(){ - MailKit.config(MailKit.SMTP_QQ, "", ""); - } - - @Test - public void testSendMail(){ - MailKit.send("xxx", "测试发送邮件", "hello"); - } - - @Test - public void testAsynSendMail(){ - MailKit.asynSend("xxx", "测试异步发送邮件", "hello"); - } - -} diff --git a/blade-kit/src/test/java/blade/test/StringTest.java b/blade-kit/src/test/java/blade/test/StringTest.java deleted file mode 100644 index e0eb40f9e..000000000 --- a/blade-kit/src/test/java/blade/test/StringTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package blade.test; - -import java.util.ArrayList; -import java.util.List; - -import blade.kit.StringKit; - -public class StringTest { - - public static void main(String[] args) { - String[] a = {"/aa/", "/aa/cc"}; - - String bString= StringKit.join(a, "|"); - System.out.println(bString); - - - List list = new ArrayList(); - list.add("aa2@qq.com"); - list.add("a3a@qq.com"); - list.add("aa4@qq.com"); - - } -} diff --git a/blade-redis/pom.xml b/blade-redis/pom.xml deleted file mode 100644 index 244bc6a3e..000000000 --- a/blade-redis/pom.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - 4.0.0 - - com.bladejava - blade-root - 1.0 - - - blade-redis - jar - ${blade.version} - blade-redis - https://github.com/biezhi/blade/blade-redis - - - - com.bladejava - blade-core - ${blade.version} - provided - - - redis.clients - jedis - 2.7.2 - - - diff --git a/blade-redis/src/main/java/blade/plugin/redis/JedisCache.java b/blade-redis/src/main/java/blade/plugin/redis/JedisCache.java deleted file mode 100644 index a122cba1c..000000000 --- a/blade-redis/src/main/java/blade/plugin/redis/JedisCache.java +++ /dev/null @@ -1,157 +0,0 @@ -package blade.plugin.redis; - -import java.io.Serializable; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import blade.kit.SerializeKit; - -/** - * jedis tool - * @author:biezhi - * @version:1.0 - */ -public class JedisCache extends RedisExecutor { - - public String set(final String key, final String value) { - return super.setString(key, value); - } - - public String set(final String key, final Serializable value) { - return super.setObject(key, value); - } - - public String set(final String key, final String value, final int timeout) { - return super.setString(key, value, timeout); - } - - public Long hset(final String key, final String field, final String value) { - return super.hashSet(key, field, value); - } - - public Long hset(final String key, final String field, final String value, final int timeout) { - return super.hashSet(key, field, value, timeout); - } - - public String hset(final String key, final Map map) { - return super.hashMultipleSet(key, map); - } - - public String hset(final String key, Map map, final int timeout) { - return super.hashMultipleSet(key, map, timeout); - } - - public Long hset(final String key, final String field, final Serializable value) { - return super.hset(key, field, value); - } - - public Long hset(final String key, final String field, final Serializable value, int timeout) { - return super.hset(key, field, value, timeout); - } - - public boolean exists(final String key, final String field) { - return super.exists(key, field); - } - - public boolean exists(final String key) { - return super.exists(key); - } - - public Long del(final String key) { - return super.delKey(key); - } - - public Long del(final String key, final String field) { - return super.delKey(key, field); - } - - public T get(final String key) { - return super.get(key); - } - - public String get(final String key, final String field) { - return super.hashGet(key, field); - } - - public Map getAllHash(final String key) { - return super.hashGetAll(key); - } - - public Serializable getModel(final String key, final String field) { - return SerializeKit.unserialize(super.hashGet(key, field).getBytes()); - } - - public List getSet(final String key, final Integer start, final Integer end) { - return super.listRange(key, start, end); - } - - public Set getKeys(final String pattern) { - return super.getKeyLike(pattern); - } - - public Long delLike(final String patten) { - return super.delKeysLike(patten); - } - - public String hget(final String key, final String field) { - return super.hashGet(key, field); - } - - public String hget(final String key, final String field, final int timeout) { - return super.hashGet(key, field, timeout); - } - - public String hget(String key, Map map) { - return super.hashMultipleSet(key, map); - } - - public Long hcount(String key) { - return super.hashLen(key); - } - - public Long sadd(String key, String... members) { - return super.sadd(key, members); - } - - public Long srem(String key, String... members) { - return super.srem(key, members); - } - - public Set sunion(String... keys) { - return super.sunion(keys); - } - - public Set sdiff(String... keys) { - return super.sdiff(keys); - } - - public Long scard(String key) { - return super.scard(key); - } - - public Long lpush(String key, String... values) { - return super.listPushHead(key, values); - } - - public Long rpush(String key, String... values) { - return super.listPushTail(key, values); - } - - public Long lpushTrim(String key, String value, long size) { - return super.listPushHeadAndTrim(key, value, size); - } - - public Long ldel(String key, String value) { - return super.listDel(key, value, -1); - } - - public Long llength(String key) { - return super.listLen(key); - } - - public T hgetModel(String key, String field) { - return super.hgetObj(key, field); - } - -} diff --git a/blade-redis/src/main/java/blade/plugin/redis/RedisExecutor.java b/blade-redis/src/main/java/blade/plugin/redis/RedisExecutor.java deleted file mode 100644 index 5787ba6bd..000000000 --- a/blade-redis/src/main/java/blade/plugin/redis/RedisExecutor.java +++ /dev/null @@ -1,1452 +0,0 @@ -package blade.plugin.redis; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPubSub; -import redis.clients.jedis.Pipeline; -import redis.clients.jedis.Response; -import redis.clients.jedis.ShardedJedis; -import redis.clients.jedis.ShardedJedisPipeline; -import redis.clients.jedis.ShardedJedisPool; -import redis.clients.jedis.Transaction; -import redis.clients.util.SafeEncoder; -import blade.kit.SerializeKit; - -/** - * Redis的辅助类,负责对内存数据库的所有操作 - * - * @author biezhi - * @since 1.0 - */ -public class RedisExecutor { - - // 数据源 - private static ShardedJedisPool shardedJedisPool; - - private static RedisPoolConfig redisPoolConfig = RedisPlugin.INSTANCE.redisPoolConfig(); - - static{ - // 根据配置文件,创建shared池实例 - shardedJedisPool = new ShardedJedisPool(redisPoolConfig, RedisPlugin.INSTANCE.shards()); - } - - /** - * 执行器, 它保证在执行操作之后释放数据源returnResource(jedis) - */ - abstract class Executor { - - ShardedJedis jedis; - - ShardedJedisPool shardedJedisPool; - - public Executor(ShardedJedisPool shardedJedisPool) { - this.shardedJedisPool = shardedJedisPool; - jedis = this.shardedJedisPool.getResource(); - } - - /** - * 回调 - * - * @return 执行结果 - */ - abstract T execute(); - - /** - * 调用{@link #execute()}并返回执行结果 它保证在执行{@link #execute()}之后释放数据源returnResource(jedis) - * - * @return 执行结果 - */ - public T getResult() { - T result = null; - try { - result = execute(); - } catch (Throwable e) { - if (null != jedis) { - shardedJedisPool.returnResourceObject(jedis); - } - throw new RuntimeException("Redis execute exception", e); - } finally { - if (null != jedis) { - shardedJedisPool.returnResourceObject(jedis); - } - } - return result; - } - } - - /** - * @return 返回ShardedJedis实例 - */ - public ShardedJedis getShardedJedis(){ - return RedisExecutor.shardedJedisPool.getResource(); - } - - /** - * 删除模糊匹配的key - * - * @param likeKey 模糊匹配的key - * @return 删除成功的条数 - */ - public Set getKeyLike(final String likeKey) { - return new Executor>(shardedJedisPool) { - - @Override - Set execute() { - Collection jedisC = jedis.getAllShards(); - Iterator iter = jedisC.iterator(); - Set keys = null; - while (iter.hasNext()) { - Jedis _jedis = iter.next(); - keys = _jedis.keys(likeKey + "*"); - } - return keys; - } - }.getResult(); - } - - /** - * 删除模糊匹配的key - * - * @param likeKey 模糊匹配的key - * @return 删除成功的条数 - */ - public long delKeysLike(final String likeKey) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - Collection jedisC = jedis.getAllShards(); - Iterator iter = jedisC.iterator(); - long count = 0; - while (iter.hasNext()) { - Jedis _jedis = iter.next(); - Set keys = _jedis.keys(likeKey + "*"); - count += _jedis.del(keys.toArray(new String[keys.size()])); - } - return count; - } - }.getResult(); - } - - /** - * 删除 - * - * @param key 匹配的key - * @return 删除成功的条数 - */ - public Long delKey(final String key) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.del(key); - } - }.getResult(); - } - - /** - * 排序 - * @param key - * @return - */ - public List sort(final String key) { - return new Executor>(shardedJedisPool) { - - @Override - List execute() { - return jedis.sort(key); - } - }.getResult(); - } - - /** - * 删除 - * - * @param key 匹配的key - * @return 删除成功的条数 - */ - public Long delKey(final String key, final String... fileds) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.hdel(key, fileds); - } - }.getResult(); - } - - /** - * 删除 - * - * @param keys 匹配的key的集合 - * @return 删除成功的条数 - */ - public Long delKeys(final String[] keys) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - Collection jedisC = jedis.getAllShards(); - Iterator iter = jedisC.iterator(); - long count = 0; - while (iter.hasNext()) { - Jedis _jedis = iter.next(); - count += _jedis.del(keys); - } - return count; - } - }.getResult(); - } - - /** - * 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。 在 Redis 中,带有生存时间的 key 被称为『可挥发』(volatile)的。 - * - * @param key key - * @param expire 生命周期,单位为秒 - * @return 1: 设置成功 0: 已经超时或key不存在 - */ - public Long expire(final String key, final int expire) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.expire(key, expire); - } - }.getResult(); - } - - /** - * 一个跨jvm的id生成器,利用了redis原子性操作的特点 - * - * @param key id的key - * @return 返回生成的Id - */ - public long makeId(final String key) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - long id = jedis.incr(key); - if ((id + 75807) >= Long.MAX_VALUE) { - // 避免溢出,重置,getSet命令之前允许incr插队,75807就是预留的插队空间 - jedis.getSet(key, "0"); - } - return id; - } - }.getResult(); - } - - public boolean exists(final String key) { - return new Executor(shardedJedisPool) { - - @Override - Boolean execute() { - return jedis.exists(key); - } - }.getResult(); - } - - public boolean exists(final String key, final String field) { - return new Executor(shardedJedisPool) { - - @Override - Boolean execute() { - return jedis.hexists(key, field); - } - }.getResult(); - } - - /* ======================================Strings====================================== */ - - /** - * 将字符串值 value 关联到 key 。 如果 key 已经持有其他值, setString 就覆写旧值,无视类型。 对于某个原本带有生存时间(TTL)的键来说, 当 setString 成功在这个键上执行时, 这个键原有的 TTL 将被清除。 时间复杂度:O(1) - * - * @param key key - * @param value string value - * @return 在设置操作成功完成时,才返回 OK 。 - */ - public String setString(final String key, final String value) { - return new Executor(shardedJedisPool) { - - @Override - String execute() { - return jedis.set(key, value); - } - }.getResult(); - } - - /** - * 将值 value 关联到 key ,并将 key 的生存时间设为 expire (以秒为单位)。 如果 key 已经存在, 将覆写旧值。 类似于以下两个命令: SET key value EXPIRE key expire # 设置生存时间 - * 不同之处是这个方法是一个原子性(atomic)操作,关联值和设置生存时间两个动作会在同一时间内完成,在 Redis 用作缓存时,非常实用。 时间复杂度:O(1) - * - * @param key key - * @param value string value - * @param expire 生命周期 - * @return 设置成功时返回 OK 。当 expire 参数不合法时,返回一个错误。 - */ - public String setString(final String key, final String value, final int expire) { - return new Executor(shardedJedisPool) { - - @Override - String execute() { - return jedis.setex(key, expire, value); - } - }.getResult(); - } - - /** - * 将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 setStringIfNotExists 不做任何动作。 时间复杂度:O(1) - * - * @param key key - * @param value string value - * @return 设置成功,返回 1 。设置失败,返回 0 。 - */ - public Long setStringIfNotExists(final String key, final String value) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.setnx(key, value); - } - }.getResult(); - } - - /** - * //TODO 将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 setStringIfNotExists 不做任何动作。 时间复杂度:O(1) - * - * @param key - * @param value - * @return - */ - public String setObject(final String key, final Serializable value) { - return new Executor(shardedJedisPool) { - - @Override - String execute() { - return jedis.set(SafeEncoder.encode(key), SerializeKit.serialize(value)); - } - }.getResult(); - } - - /** - * //TODO 将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 setStringIfNotExists 不做任何动作。 时间复杂度:O(1) - * - * @param key - * @param value - * @param expire - * @return - */ - public String setObject(final String key, final Serializable value, final int expire) { - return new Executor(shardedJedisPool) { - - @Override - String execute() { - return jedis.setex(key.getBytes(), expire, SerializeKit.serialize(value)); - } - }.getResult(); - } - - /** - * //TODO 添加方法功能描述 - * - * @param key - * @param field - * @param value - * @return - */ - public Long hset(final String key, final String field, final Serializable value) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.hset(key.getBytes(), field.getBytes(), SerializeKit.serialize(value)); - } - }.getResult(); - } - - public Long hset(final String key, final String field, final Serializable value, final int expire) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - Pipeline pipeline = jedis.getShard(key).pipelined(); - Response result = pipeline.hset(key.getBytes(), field.getBytes(), SerializeKit.serialize(value)); - pipeline.expire(key, expire); - pipeline.sync(); - return result.get(); - } - }.getResult(); - } - - /** - * 返回 key 所关联的字符串值。如果 key 不存在那么返回特殊值 nil 。 假如 key 储存的值不是字符串类型,返回一个错误,因为 getString 只能用于处理字符串值。 时间复杂度: O(1) - * - * @param key key - * @return 当 key 不存在时,返回 nil ,否则,返回 key 的值。如果 key 不是字符串类型,那么返回一个错误。 - */ - public String getString(final String key) { - return new Executor(shardedJedisPool) { - - @Override - String execute() { - return jedis.get(key); - } - }.getResult(); - } - - public T get(final String key) { - return new Executor(shardedJedisPool) { - - @SuppressWarnings("unchecked") - @Override - T execute() { - Object result = null; - byte[] retVal = jedis.get(SafeEncoder.encode(key)); - if (null != retVal) { - try { - result = SerializeKit.unserialize(retVal); - } catch (Exception e) { - result = SafeEncoder.encode(retVal); - } - } - return (T) result; - } - }.getResult(); - } - - /** - * 批量的 {@link #setString(String, String)} - * - * @param pairs 键值对数组{数组第一个元素为key,第二个元素为value} - * @return 操作状态的集合 - */ - public List batchSetString(final List> pairs) { - return new Executor>(shardedJedisPool) { - - @Override - List execute() { - ShardedJedisPipeline pipeline = jedis.pipelined(); - for (Pair pair : pairs) { - pipeline.set(pair.getKey(), pair.getValue()); - } - return pipeline.syncAndReturnAll(); - } - }.getResult(); - } - - /** - * 批量的 {@link #getString(String)} - * - * @param keys key数组 - * @return value的集合 - */ - public List batchGetString(final String[] keys) { - return new Executor>(shardedJedisPool) { - - @Override - List execute() { - ShardedJedisPipeline pipeline = jedis.pipelined(); - List result = new ArrayList(keys.length); - List> responses = new ArrayList>(keys.length); - for (String key : keys) { - responses.add(pipeline.get(key)); - } - pipeline.sync(); - for (Response resp : responses) { - result.add(resp.get()); - } - return result; - } - }.getResult(); - } - - /* ======================================Hashes====================================== */ - - /** - * 将哈希表 key 中的域 field 的值设为 value 。 如果 key 不存在,一个新的哈希表被创建并进行 hashSet 操作。 如果域 field 已经存在于哈希表中,旧值将被覆盖。 时间复杂度: O(1) - * - * @param key key - * @param field 域 - * @param value string value - * @return 如果 field 是哈希表中的一个新建域,并且值设置成功,返回 1 。如果哈希表中域 field 已经存在且旧值已被新值覆盖,返回 0 。 - */ - public Long hashSet(final String key, final String field, final String value) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.hset(key, field, value); - } - }.getResult(); - } - - /** - * 将哈希表 key 中的域 field 的值设为 value 。 如果 key 不存在,一个新的哈希表被创建并进行 hashSet 操作。 如果域 field 已经存在于哈希表中,旧值将被覆盖。 - * - * @param key key - * @param field 域 - * @param value string value - * @param expire 生命周期,单位为秒 - * @return 如果 field 是哈希表中的一个新建域,并且值设置成功,返回 1 。如果哈希表中域 field 已经存在且旧值已被新值覆盖,返回 0 。 - */ - public Long hashSet(final String key, final String field, final String value, final int expire) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - Pipeline pipeline = jedis.getShard(key).pipelined(); - Response result = pipeline.hset(key, field, value); - pipeline.expire(key, expire); - pipeline.sync(); - return result.get(); - } - }.getResult(); - } - - /** - * 返回哈希表 key 中给定域 field 的值。 时间复杂度:O(1) - * - * @param key key - * @param field 域 - * @return 给定域的值。当给定域不存在或是给定 key 不存在时,返回 nil 。 - */ - public String hashGet(final String key, final String field) { - return new Executor(shardedJedisPool) { - - @Override - String execute() { - return jedis.hget(key, field); - } - }.getResult(); - } - - /** - * 返回哈希表 key 中给定域 field 的值。 如果哈希表 key 存在,同时设置这个 key 的生存时间 - * - * @param key key - * @param field 域 - * @param expire 生命周期,单位为秒 - * @return 给定域的值。当给定域不存在或是给定 key 不存在时,返回 nil 。 - */ - public String hashGet(final String key, final String field, final int expire) { - return new Executor(shardedJedisPool) { - - @Override - String execute() { - Pipeline pipeline = jedis.getShard(key).pipelined(); - Response result = pipeline.hget(key, field); - pipeline.expire(key, expire); - pipeline.sync(); - return result.get(); - } - }.getResult(); - } - - /** - * 同时将多个 field-value (域-值)对设置到哈希表 key 中。 时间复杂度: O(N) (N为fields的数量) - * - * @param key key - * @param hash field-value的map - * @return 如果命令执行成功,返回 OK 。当 key 不是哈希表(hash)类型时,返回一个错误。 - */ - public String hashMultipleSet(final String key, final Map hash) { - return new Executor(shardedJedisPool) { - - @Override - String execute() { - return jedis.hmset(key, hash); - } - }.getResult(); - } - - /** - * 同时将多个 field-value (域-值)对设置到哈希表 key 中。同时设置这个 key 的生存时间 - * - * @param key key - * @param hash field-value的map - * @param expire 生命周期,单位为秒 - * @return 如果命令执行成功,返回 OK 。当 key 不是哈希表(hash)类型时,返回一个错误。 - */ - public String hashMultipleSet(final String key, final Map hash, final int expire) { - return new Executor(shardedJedisPool) { - - @Override - String execute() { - Pipeline pipeline = jedis.getShard(key).pipelined(); - Response result = pipeline.hmset(key, hash); - pipeline.expire(key, expire); - pipeline.sync(); - return result.get(); - } - }.getResult(); - } - - /** - * 返回哈希表 key 中,一个或多个给定域的值。如果给定的域不存在于哈希表,那么返回一个 nil 值。 时间复杂度: O(N) (N为fields的数量) - * - * @param key key - * @param fields field的数组 - * @return 一个包含多个给定域的关联值的表,表值的排列顺序和给定域参数的请求顺序一样。 - */ - public List hashMultipleGet(final String key, final String... fields) { - return new Executor>(shardedJedisPool) { - - @Override - List execute() { - return jedis.hmget(key, fields); - } - }.getResult(); - } - - /** - * 返回哈希表 key 中,一个或多个给定域的值。如果给定的域不存在于哈希表,那么返回一个 nil 值。 同时设置这个 key 的生存时间 - * - * @param key key - * @param fields field的数组 - * @param expire 生命周期,单位为秒 - * @return 一个包含多个给定域的关联值的表,表值的排列顺序和给定域参数的请求顺序一样。 - */ - public List hashMultipleGet(final String key, final int expire, final String... fields) { - return new Executor>(shardedJedisPool) { - - @Override - List execute() { - Pipeline pipeline = jedis.getShard(key).pipelined(); - Response> result = pipeline.hmget(key, fields); - pipeline.expire(key, expire); - pipeline.sync(); - return result.get(); - } - }.getResult(); - } - - /** - * 批量的{@link #hashMultipleSet(String, Map)},在管道中执行 - * - * @param pairs 多个hash的多个field - * @return 操作状态的集合 - */ - public List batchHashMultipleSet(final List>> pairs) { - return new Executor>(shardedJedisPool) { - - @Override - List execute() { - ShardedJedisPipeline pipeline = jedis.pipelined(); - for (Pair> pair : pairs) { - pipeline.hmset(pair.getKey(), pair.getValue()); - } - return pipeline.syncAndReturnAll(); - } - }.getResult(); - } - - /** - * 批量的{@link #hashMultipleSet(String, Map)},在管道中执行 - * - * @param data Map>格式的数据 - * @return 操作状态的集合 - */ - public List batchHashMultipleSet(final Map> data) { - return new Executor>(shardedJedisPool) { - - @Override - List execute() { - ShardedJedisPipeline pipeline = jedis.pipelined(); - for (Map.Entry> iter : data.entrySet()) { - pipeline.hmset(iter.getKey(), iter.getValue()); - } - return pipeline.syncAndReturnAll(); - } - }.getResult(); - } - - /** - * 批量的{@link #hashMultipleGet(String, String...)},在管道中执行 - * - * @param pairs 多个hash的多个field - * @return 执行结果的集合 - */ - public List> batchHashMultipleGet(final List> pairs) { - return new Executor>>(shardedJedisPool) { - - @Override - List> execute() { - ShardedJedisPipeline pipeline = jedis.pipelined(); - List> result = new ArrayList>(pairs.size()); - List>> responses = new ArrayList>>(pairs.size()); - for (Pair pair : pairs) { - responses.add(pipeline.hmget(pair.getKey(), pair.getValue())); - } - pipeline.sync(); - for (Response> resp : responses) { - result.add(resp.get()); - } - return result; - } - }.getResult(); - - } - - /** - * 返回哈希表 key 中,所有的域和值。在返回值里,紧跟每个域名(field name)之后是域的值(value),所以返回值的长度是哈希表大小的两倍。 时间复杂度: O(N) - * - * @param key key - * @return 以列表形式返回哈希表的域和域的值。若 key 不存在,返回空列表。 - */ - public Map hashGetAll(final String key) { - return new Executor>(shardedJedisPool) { - - @Override - Map execute() { - return jedis.hgetAll(key); - } - }.getResult(); - } - - /** - * 返回哈希表 key 中,所有的域和值。在返回值里,紧跟每个域名(field name)之后是域的值(value),所以返回值的长度是哈希表大小的两倍。 同时设置这个 key 的生存时间 - * - * @param key key - * @param expire 生命周期,单位为秒 - * @return 以列表形式返回哈希表的域和域的值。若 key 不存在,返回空列表。 - */ - public Map hashGetAll(final String key, final int expire) { - return new Executor>(shardedJedisPool) { - - @Override - Map execute() { - Pipeline pipeline = jedis.getShard(key).pipelined(); - Response> result = pipeline.hgetAll(key); - pipeline.expire(key, expire); - pipeline.sync(); - return result.get(); - } - }.getResult(); - } - - /** - * 获取hash的field数量 - * - * @param key - * @return - */ - public Long hashLen(final String key) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.hlen(key); - } - }.getResult(); - } - - /** - * 获取hash的field数量 - * - * @param key - * @return - */ - public Long hashLen(final String key, final int expire) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - Pipeline pipeline = jedis.getShard(key).pipelined(); - Response result = pipeline.hlen(key); - pipeline.expire(key, expire); - pipeline.sync(); - return result.get(); - } - }.getResult(); - } - - /** - * 返回 hash 的所有 field - * - * @param key - * @return - */ - public Set hashKeys(final String key) { - return new Executor>(shardedJedisPool) { - - @Override - Set execute() { - return jedis.hkeys(key); - } - }.getResult(); - } - - /** - * 返回 hash 的所有 field - * - * @param key - * @return - */ - public Set hashKeys(final String key, final int expire) { - return new Executor>(shardedJedisPool) { - - @Override - Set execute() { - Pipeline pipeline = jedis.getShard(key).pipelined(); - Response> result = pipeline.hkeys(key); - pipeline.expire(key, expire); - pipeline.sync(); - return result.get(); - } - }.getResult(); - } - - /** - * 批量的{@link #hashGetAll(String)} - * - * @param keys key的数组 - * @return 执行结果的集合 - */ - public List> batchHashGetAll(final String... keys) { - return new Executor>>(shardedJedisPool) { - - @Override - List> execute() { - ShardedJedisPipeline pipeline = jedis.pipelined(); - List> result = new ArrayList>(keys.length); - List>> responses = new ArrayList>>(keys.length); - for (String key : keys) { - responses.add(pipeline.hgetAll(key)); - } - pipeline.sync(); - for (Response> resp : responses) { - result.add(resp.get()); - } - return result; - } - }.getResult(); - } - - /** - * 批量的{@link #hashMultipleGet(String, String...)},与{@link #batchHashGetAll(String...)}不同的是,返回值为Map类型 - * - * @param keys key的数组 - * @return 多个hash的所有filed和value - */ - public Map> batchHashGetAllForMap(final String... keys) { - return new Executor>>(shardedJedisPool) { - - @Override - Map> execute() { - ShardedJedisPipeline pipeline = jedis.pipelined(); - - // 设置map容量防止rehash - int capacity = 1; - while ((int) (capacity * 0.75) <= keys.length) { - capacity <<= 1; - } - Map> result = new HashMap>(capacity); - List>> responses = new ArrayList>>(keys.length); - for (String key : keys) { - responses.add(pipeline.hgetAll(key)); - } - pipeline.sync(); - for (int i = 0; i < keys.length; ++i) { - result.put(keys[i], responses.get(i).get()); - } - return result; - } - }.getResult(); - } - - /* ======================================List====================================== */ - - /** - * 将一个或多个值 value 插入到列表 key 的表尾(最右边)。 - * - * @param key key - * @param values value的数组 - * @return 执行 listPushTail 操作后,表的长度 - */ - public Long listPushTail(final String key, final String... values) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.rpush(key, values); - } - }.getResult(); - } - - /** - * 将一个或多个值 value 插入到列表 key 的表头 - * - * @param key key - * @param value string value - * @return 执行 listPushHead 命令后,列表的长度。 - */ - public Long listPushHead(final String key, final String... values) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.lpush(key, values); - } - }.getResult(); - } - - /** - * list长度 - * - * @param key - * @return - */ - public Long listLen(final String key) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.llen(key); - } - }.getResult(); - } - - /** - * 将一个或多个值 value 插入到列表 key 的表头, 当列表大于指定长度是就对列表进行修剪(trim) - * - * @param key key - * @param value string value - * @param size 链表超过这个长度就修剪元素 - * @return 执行 listPushHeadAndTrim 命令后,列表的长度。 - */ - public Long listPushHeadAndTrim(final String key, final String value, final long size) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - Pipeline pipeline = jedis.getShard(key).pipelined(); - Response result = pipeline.lpush(key, value); - // 修剪列表元素, 如果 size - 1 比 end 下标还要大,Redis将 size 的值设置为 end 。 - pipeline.ltrim(key, 0, size - 1); - pipeline.sync(); - return result.get(); - } - }.getResult(); - } - - /** - * 移除从表尾到表头,第一个 value - * - * @param key - * @param value - * @return - */ - public Long listDel(final String key, final String value, final int index) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.lrem(key, index, value); - } - }.getResult(); - } - - /** - * 批量的{@link #listPushTail(String, String...)},以锁的方式实现 - * - * @param key key - * @param values value的数组 - * @param delOld 如果key存在,是否删除它。true 删除;false: 不删除,只是在行尾追加 - */ - public void batchListPushTail(final String key, final String[] values, final boolean delOld) { - new Executor(shardedJedisPool) { - - @Override - Object execute() { - if (delOld) { - RedisLock lock = new RedisLock(key, shardedJedisPool); - lock.lock(); - try { - Pipeline pipeline = jedis.getShard(key).pipelined(); - pipeline.del(key); - for (String value : values) { - pipeline.rpush(key, value); - } - pipeline.sync(); - } finally { - lock.unlock(); - } - } else { - jedis.rpush(key, values); - } - return null; - } - }.getResult(); - } - - /** - * 同{@link #batchListPushTail(String, String[], boolean)},不同的是利用redis的事务特性来实现 - * - * @param key key - * @param values value的数组 - * @return null - */ - public Object updateListInTransaction(final String key, final List values) { - return new Executor(shardedJedisPool) { - - @Override - Object execute() { - Transaction transaction = jedis.getShard(key).multi(); - transaction.del(key); - for (String value : values) { - transaction.rpush(key, value); - } - transaction.exec(); - return null; - } - }.getResult(); - } - - /** - * 在key对应list的尾部部添加字符串元素,如果key存在,什么也不做 - * - * @param key key - * @param values value的数组 - * @return 执行insertListIfNotExists后,表的长度 - */ - public Long insertListIfNotExists(final String key, final String[] values) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - RedisLock lock = new RedisLock(key, shardedJedisPool); - lock.lock(); - try { - if (!jedis.exists(key)) { - return jedis.rpush(key, values); - } - } finally { - lock.unlock(); - } - return 0L; - } - }.getResult(); - } - - /** - * 返回list所有元素,下标从0开始,负值表示从后面计算,-1表示倒数第一个元素,key不存在返回空列表 - * - * @param key key - * @return list所有元素 - */ - public List listGetAll(final String key) { - return new Executor>(shardedJedisPool) { - - @Override - List execute() { - return jedis.lrange(key, 0, -1); - } - }.getResult(); - } - - /** - * 返回指定区间内的元素,下标从0开始,负值表示从后面计算,-1表示倒数第一个元素,key不存在返回空列表 - * - * @param key key - * @param beginIndex 下标开始索引(包含) - * @param endIndex 下标结束索引(不包含) - * @return 指定区间内的元素 - */ - public List listRange(final String key, final long beginIndex, final long endIndex) { - return new Executor>(shardedJedisPool) { - - @Override - List execute() { - return jedis.lrange(key, beginIndex, endIndex - 1); - } - }.getResult(); - } - - /** - * 一次获得多个链表的数据 - * - * @param keys key的数组 - * @return 执行结果 - */ - public Map> batchGetAllList(final List keys) { - return new Executor>>(shardedJedisPool) { - - @Override - Map> execute() { - ShardedJedisPipeline pipeline = jedis.pipelined(); - Map> result = new HashMap>(); - List>> responses = new ArrayList>>(keys.size()); - for (String key : keys) { - responses.add(pipeline.lrange(key, 0, -1)); - } - pipeline.sync(); - for (int i = 0; i < keys.size(); ++i) { - result.put(keys.get(i), responses.get(i).get()); - } - return result; - } - }.getResult(); - } - - /* ======================================Pub/Sub====================================== */ - - /** - * 将信息 message 发送到指定的频道 channel。 时间复杂度:O(N+M),其中 N 是频道 channel 的订阅者数量,而 M 则是使用模式订阅(subscribed patterns)的客户端的数量。 - * - * @param channel 频道 - * @param message 信息 - * @return 接收到信息 message 的订阅者数量。 - */ - public Long publish(final String channel, final String message) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - Jedis _jedis = jedis.getShard(channel); - return _jedis.publish(channel, message); - } - - }.getResult(); - } - - /** - * 订阅给定的一个频道的信息。 - * - * @param jedisPubSub 监听器 - * @param channel 频道 - */ - public void subscribe(final JedisPubSub jedisPubSub, final String channel) { - new Executor(shardedJedisPool) { - - @Override - Object execute() { - Jedis _jedis = jedis.getShard(channel); - // 注意subscribe是一个阻塞操作,因为当前线程要轮询Redis的响应然后调用subscribe - _jedis.subscribe(jedisPubSub, channel); - return null; - } - }.getResult(); - } - - /** - * 取消订阅 - * - * @param jedisPubSub 监听器 - */ - public void unSubscribe(final JedisPubSub jedisPubSub) { - jedisPubSub.unsubscribe(); - } - - /* ======================================Sorted set================================= */ - - /** - * sadd添加一个元素 - * - * @param key - * @param members - * @return - */ - public Long sadd(final String key, final String... members) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.sadd(key, members); - } - }.getResult(); - } - - public Long sadd(final String key, final byte[]... members) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.sadd(key.getBytes(), members); - } - }.getResult(); - } - - /** - * 获取key的集合 - * - * @param key - * @return - */ - public Set smembers(final String key) { - return new Executor>(shardedJedisPool) { - - @Override - Set execute() { - return jedis.smembers(key); - } - }.getResult(); - } - - /** - * 移除set元素 - * - * @param key - * @param members - * @return - */ - public Long srem(final String key, final String... members) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.srem(key, members); - } - }.getResult(); - } - - /** - * 统计key的value重复合集(共同关注) - * - * @param keys - * @return - */ - public Set sinter(final String... keys) { - return new Executor>(shardedJedisPool) { - - @Override - Set execute() { - Jedis _jedis = jedis.getShard(keys[0]); - return _jedis.sinter(keys); - } - }.getResult(); - } - - /** - * 并集 - * - * @param keys - * @return - */ - public Set sunion(final String... keys) { - return new Executor>(shardedJedisPool) { - - @Override - Set execute() { - Jedis _jedis = jedis.getShard(keys[0]); - return _jedis.sunion(keys); - } - }.getResult(); - } - - /** - * 差集 - * - * @param keys - * @return - */ - public Set sdiff(final String... keys) { - return new Executor>(shardedJedisPool) { - - @Override - Set execute() { - Jedis _jedis = jedis.getShard(keys[0]); - return _jedis.sdiff(keys); - } - }.getResult(); - } - - /** - * 判断元素是否存在 - * - * @param key - * @param member - * @return - */ - public Boolean sismember(final String key, final String member) { - return new Executor(shardedJedisPool) { - - @Override - Boolean execute() { - return jedis.sismember(key, member); - } - }.getResult(); - } - - /** - * 统计列表集合 - * - * @param key - * @return - */ - public Long scard(final String key) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.scard(key); - } - }.getResult(); - } - - /** - * 将一个 member 元素及其 score 值加入到有序集 key 当中。 - * - * @param key key - * @param score score 值可以是整数值或双精度浮点数。 - * @param member 有序集的成员 - * @return 被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员。 - */ - public Long addWithSortedSet(final String key, final double score, final String member) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.zadd(key, score, member); - } - }.getResult(); - } - - /** - * 将多个 member 元素及其 score 值加入到有序集 key 当中。 - * - * @param key key - * @param scoreMembers score、member的pair - * @return 被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员。 - */ - public Long addWithSortedSet(final String key, final Map scoreMembers) { - return new Executor(shardedJedisPool) { - - @Override - Long execute() { - return jedis.zadd(key, scoreMembers); - } - }.getResult(); - } - - /** - * 返回有序集 key 中,指定区间内的成员。 按照从小到大排序 - * - * @param key - * @param start - * @param end - * @return - */ - public Set rangeSortSet(final String key, final int start, final int end) { - return new Executor>(shardedJedisPool) { - - @Override - Set execute() { - return jedis.zrange(key, start, end); - } - }.getResult(); - } - - /** - * 返回有序集 key 中,指定区间内的成员。 按照从大到小排序 - * @param key - * @param start - * @param end - * @return - */ - public Set revrangeSortSet(final String key, final int start, final int end) { - return new Executor>(shardedJedisPool) { - - @Override - Set execute() { - return jedis.zrevrange(key, start, end); - } - }.getResult(); - } - - /** - * 返回有序集 key 中, score 值介于 max 和 min 之间(默认包括等于 max 或 min )的所有的成员。 有序集成员按 score 值递减(从大到小)的次序排列。 - * - * @param key key - * @param max score最大值 - * @param min score最小值 - * @return 指定区间内,带有 score 值(可选)的有序集成员的列表 - */ - public Set revrangeByScoreWithSortedSet(final String key, final double max, final double min) { - return new Executor>(shardedJedisPool) { - - @Override - Set execute() { - return jedis.zrevrangeByScore(key, max, min); - } - }.getResult(); - } - - /* ======================================Other====================================== */ - - /** - * 构造Pair键值对 - * - * @param key key - * @param value value - * @return 键值对 - */ - public Pair makePair(K key, V value) { - return new Pair(key, value); - } - - /** - * 键值对 - * - * @version V1.0 - * @author fengjc - * @param key - * @param value - */ - public class Pair { - - private K key; - - private V value; - - public Pair(K key, V value) { - this.key = key; - this.value = value; - } - - public K getKey() { - return key; - } - - public void setKey(K key) { - this.key = key; - } - - public V getValue() { - return value; - } - - public void setValue(V value) { - this.value = value; - } - } - - public T hgetObj(final String key, final String field) { - return new Executor(shardedJedisPool) { - - @SuppressWarnings("unchecked") - @Override - T execute() { - Object result = null; - - byte[] retVal = jedis.hget(SafeEncoder.encode(key), SafeEncoder.encode(field)); - if (null != retVal) { - try { - result = SerializeKit.unserialize(retVal); - } catch (Exception e) { - result = SafeEncoder.encode(retVal); - } - } - return (T) result; - } - }.getResult(); - } - -} diff --git a/blade-redis/src/main/java/blade/plugin/redis/RedisLock.java b/blade-redis/src/main/java/blade/plugin/redis/RedisLock.java deleted file mode 100644 index 0743dccc3..000000000 --- a/blade-redis/src/main/java/blade/plugin/redis/RedisLock.java +++ /dev/null @@ -1,120 +0,0 @@ -package blade.plugin.redis; - -import java.util.Random; - -import redis.clients.jedis.ShardedJedis; -import redis.clients.jedis.ShardedJedisPool; - -/** - * redis lock - * - * @author biezhi - * @since 1.0 - */ -public class RedisLock { - - /** 加锁标志 */ - public static final String LOCKED = "TRUE"; - - /** 毫秒与毫微秒的换算单位 1毫秒 = 1000000毫微秒 */ - public static final long MILLI_NANO_CONVERSION = 1000 * 1000L; - - /** 默认超时时间(毫秒) */ - public static final long DEFAULT_TIME_OUT = 1000; - - public static final Random RANDOM = new Random(); - - /** 锁的超时时间(秒),过期删除 */ - public static final int EXPIRE = 3 * 60; - - private ShardedJedisPool shardedJedisPool; - - private ShardedJedis jedis; - - private String key; - - // 锁状态标志 - private boolean locked = false; - - /** - * This creates a RedisLock - */ - public RedisLock(String key, ShardedJedisPool shardedJedisPool) { - this.key = key + "_lock"; - this.shardedJedisPool = shardedJedisPool; - this.jedis = this.shardedJedisPool.getResource(); - } - - /** - * 加锁 应该以: lock(); try { doSomething(); } finally { unlock(); } 的方式调用 - * @param timeout 超时时间 - * @return 成功或失败标志 - */ - public boolean lock(long timeout) { - long nano = System.nanoTime(); - timeout *= MILLI_NANO_CONVERSION; - try { - while ((System.nanoTime() - nano) < timeout) { - if (this.jedis.setnx(this.key, LOCKED) == 1) { - this.jedis.expire(this.key, EXPIRE); - this.locked = true; - return this.locked; - } - // 短暂休眠,避免出现活锁 - Thread.sleep(3, RANDOM.nextInt(500)); - } - } catch (Exception e) { - throw new RuntimeException("Locking error", e); - } - return false; - } - - /** - * 加锁 应该以: lock(); try { doSomething(); } finally { unlock(); } 的方式调用 - * - * @param timeout 超时时间 - * @param expire 锁的超时时间(秒),过期删除 - * @return 成功或失败标志 - */ - public boolean lock(long timeout, int expire) { - long nano = System.nanoTime(); - timeout *= MILLI_NANO_CONVERSION; - try { - while ((System.nanoTime() - nano) < timeout) { - if (this.jedis.setnx(this.key, LOCKED) == 1) { - this.jedis.expire(this.key, expire); - this.locked = true; - return this.locked; - } - // 短暂休眠,避免出现活锁 - Thread.sleep(3, RANDOM.nextInt(500)); - } - } catch (Exception e) { - throw new RuntimeException("Locking error", e); - } - return false; - } - - /** - * 加锁 应该以: lock(); try { doSomething(); } finally { unlock(); } 的方式调用 - * - * @return 成功或失败标志 - */ - public boolean lock() { - return lock(DEFAULT_TIME_OUT); - } - - /** - * 解锁 无论是否加锁成功,都需要调用unlock 应该以: lock(); try { doSomething(); } finally { unlock(); } 的方式调用 - */ - public void unlock() { - try { - if (this.locked) { - this.jedis.del(this.key); - } - } finally { - this.shardedJedisPool.returnResourceObject(this.jedis); -// this.shardedJedisPool.returnResource(this.jedis); - } - } -} diff --git a/blade-redis/src/main/java/blade/plugin/redis/RedisPlugin.java b/blade-redis/src/main/java/blade/plugin/redis/RedisPlugin.java deleted file mode 100644 index 865050f21..000000000 --- a/blade-redis/src/main/java/blade/plugin/redis/RedisPlugin.java +++ /dev/null @@ -1,65 +0,0 @@ -package blade.plugin.redis; - -import java.util.ArrayList; -import java.util.List; - -import redis.clients.jedis.JedisShardInfo; -import blade.kit.log.Logger; -import blade.plugin.Plugin; - -/** - * redis插件 - * - * @author biezhi - * @since 1.0 - */ -public enum RedisPlugin implements Plugin { - - INSTANCE; - - private Logger LOGGER = Logger.getLogger(RedisPlugin.class); - - private RedisPlugin() { - redisPoolConfig = new RedisPoolConfig(); - shards = new ArrayList(); - } - - /** - * redis连接池配置 - */ - private RedisPoolConfig redisPoolConfig; - - /** - * 所有redis实例,多台redis主机时可以做集群 - */ - private List shards; - - public RedisPoolConfig redisPoolConfig() { - return redisPoolConfig; - } - - public RedisPlugin host(String host, int port) { - shards.add(new JedisShardInfo(host, port)); - return INSTANCE; - } - - public RedisPlugin host(JedisShardInfo jedisShardInfo) { - shards.add(jedisShardInfo); - return INSTANCE; - } - - public RedisPlugin host(String host) { - shards.add(new JedisShardInfo(host)); - return INSTANCE; - } - - public List shards() { - return INSTANCE.shards; - } - - @Override - public void run() { - LOGGER.info("redis插件配置成功..."); - } - -} diff --git a/blade-redis/src/main/java/blade/plugin/redis/RedisPoolConfig.java b/blade-redis/src/main/java/blade/plugin/redis/RedisPoolConfig.java deleted file mode 100644 index 94ca8beea..000000000 --- a/blade-redis/src/main/java/blade/plugin/redis/RedisPoolConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package blade.plugin.redis; - -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; - -public class RedisPoolConfig extends GenericObjectPoolConfig { - - public RedisPoolConfig() { - // defaults to make your life with connection pool easier :) - - setMinEvictableIdleTimeMillis(60000); - setTimeBetweenEvictionRunsMillis(60000); - setNumTestsPerEvictionRun(-1); - setTestOnBorrow(true); - setTestOnReturn(true); - setTestWhileIdle(true); - setMaxWaitMillis(3000); - setMaxTotal(10); - setMaxIdle(5); - } - -} diff --git a/blade-sample/pom.xml b/blade-sample/pom.xml new file mode 100644 index 000000000..f94b70b11 --- /dev/null +++ b/blade-sample/pom.xml @@ -0,0 +1,44 @@ + + + + blade + com.bladejava + 1.0 + + 4.0.0 + + blade-sample + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + com.bladejava + blade-core + ${blade-core.version} + + + com.bladejava + blade-embed-jetty + ${blade-embed-jetty.version} + + + com.bladejava + blade-template-velocity + 0.0.4 + + + + \ No newline at end of file diff --git a/blade-sample/src/main/java/com/xxx/hello/Application.java b/blade-sample/src/main/java/com/xxx/hello/Application.java new file mode 100644 index 000000000..7a08575b6 --- /dev/null +++ b/blade-sample/src/main/java/com/xxx/hello/Application.java @@ -0,0 +1,47 @@ +package com.xxx.hello; + +import com.blade.Const; +import com.blade.kit.json.JSONObject; +import com.blade.mvc.http.HttpMethod; +import com.blade.mvc.view.RestResponse; +import com.blade.mvc.view.ViewSettings; +import com.blade.mvc.view.template.VelocityTemplateEngine; +import com.xxx.hello.controller.MsgController; + +import static com.blade.Blade.$; + +/** + * Created by biezhi on 2016/12/17. + */ +public class Application { + + public static void main(String[] args) { + + // setting default template engine is velocity :) + ViewSettings.$().templateEngine(new VelocityTemplateEngine()); + + $().route("/msg", MsgController.class, "msg", HttpMethod.GET); + + $().get("/", (request, response) -> { + + RestResponse restResponse = new RestResponse<>(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", "blade"); + jsonObject.put("version", Const.VERSION); + restResponse.setPayload(jsonObject); + response.json(restResponse); + + }).get("/hello", (request, response)-> { + + String name = request.query("name", "boy"); + + request.attribute("name", name); + response.render("hello.vm"); + + }).delete("/user/:id", (request, response)-> { + int id = request.pathParamAsInt("id"); + System.out.println("userid is " + id); + }).start(Application.class); + } + +} diff --git a/blade-sample/src/main/java/com/xxx/hello/controller/IndexController.java b/blade-sample/src/main/java/com/xxx/hello/controller/IndexController.java new file mode 100644 index 000000000..44395c052 --- /dev/null +++ b/blade-sample/src/main/java/com/xxx/hello/controller/IndexController.java @@ -0,0 +1,24 @@ +package com.xxx.hello.controller; + +import com.blade.mvc.annotation.Controller; +import com.blade.mvc.annotation.PathParam; +import com.blade.mvc.annotation.QueryParam; +import com.blade.mvc.annotation.Route; + +/** + * Created by biezhi on 2017/1/2. + */ +@Controller +public class IndexController { + + @Route("sayHi") + public void sayHi(@QueryParam(value = "name", required = true) String name){ + System.out.println("name = " + name); + } + + @Route("sayHi/:name") + public void sayHi2(@PathParam Long name){ + System.out.println("name = " + name); + } + +} diff --git a/blade-sample/src/main/java/com/xxx/hello/controller/MsgController.java b/blade-sample/src/main/java/com/xxx/hello/controller/MsgController.java new file mode 100644 index 000000000..a3b30f17d --- /dev/null +++ b/blade-sample/src/main/java/com/xxx/hello/controller/MsgController.java @@ -0,0 +1,15 @@ +package com.xxx.hello.controller; + +import com.blade.mvc.http.Request; +import com.blade.mvc.http.Response; + +/** + * Created by biezhi on 2017/1/2. + */ +public class MsgController { + + public void msg(){ + System.out.println("进入msg"); + } + +} diff --git a/blade-sample/src/main/resources/app.properties b/blade-sample/src/main/resources/app.properties new file mode 100644 index 000000000..ba1fcd377 --- /dev/null +++ b/blade-sample/src/main/resources/app.properties @@ -0,0 +1 @@ +app.dev = true \ No newline at end of file diff --git a/blade-sample/src/main/resources/log4j.properties b/blade-sample/src/main/resources/log4j.properties new file mode 100644 index 000000000..1061a1743 --- /dev/null +++ b/blade-sample/src/main/resources/log4j.properties @@ -0,0 +1,4 @@ +log4j.rootLogger=info, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=[blade-sample] %d %-5p [%t] %c | %m%n diff --git a/blade-sample/src/main/resources/public/avatar.jpeg b/blade-sample/src/main/resources/public/avatar.jpeg new file mode 100644 index 000000000..e6f4dd4a4 Binary files /dev/null and b/blade-sample/src/main/resources/public/avatar.jpeg differ diff --git a/blade-sample/src/main/resources/static/img/avatar.jpeg b/blade-sample/src/main/resources/static/img/avatar.jpeg new file mode 100644 index 000000000..e6f4dd4a4 Binary files /dev/null and b/blade-sample/src/main/resources/static/img/avatar.jpeg differ diff --git a/blade-sample/src/main/resources/templates/hello.vm b/blade-sample/src/main/resources/templates/hello.vm new file mode 100644 index 000000000..e8283c602 --- /dev/null +++ b/blade-sample/src/main/resources/templates/hello.vm @@ -0,0 +1,12 @@ + + + + + Hello Page + + + +

Hi ${name}!

+ + + \ No newline at end of file diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/Condition.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/Condition.java deleted file mode 100644 index a044963ba..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/Condition.java +++ /dev/null @@ -1,387 +0,0 @@ -package blade.plugin.sql2o; - -import java.util.Arrays; -import java.util.Map; -import java.util.Set; - -import blade.kit.CollectionKit; -import blade.kit.StringKit; -import blade.kit.log.Logger; - -public class Condition { - - private static final Logger LOGGER = Logger.getLogger(Condition.class); - - String tableName; - - String pkName; - - // 存储sql - String sql; - - // 存储排序 - String orderby; - - // 存储操作,增删改查 - DmlType dmlType; - - // 存储设置的参数,insert-update用 - Map params; - - // 存储where条件,select-update-delete用 - Map equalsParams; - - // 大于 - Map greaterParams; - // 大于等于 - Map greaterThanParams; - // 小于 - Map lessParams; - // 小于等于 - Map lessThanParams; - // like - Map likeParams; - // in - Map inParams; - - enum DmlType { - SELECT, COUNT, INSERT, UPDATE, DELETE - } - - - public Condition(String tableName, String pkName) { - this.tableName = tableName; - this.pkName = pkName; - } - - public String getConditionSql() { - return getConditionSql(null); - } - - public String getConditionSql(String sql) { - - String sqlEnd = this.sql; - - if (null != sql) { - sqlEnd = sql; - } - - String conditionSql = SqlBuider.create(this, sqlEnd) - .addEquals() - .addGreater() - .addLess() - .addGreaterThan() - .addLessThan() - .addLike() - .addIn() - .addAsString(); - - - if (conditionSql.lastIndexOf(" and ") != -1) { - conditionSql = conditionSql.substring(0, conditionSql.length() - 5); - } - return conditionSql; - } - - /** - * 过滤mysql、posrgresql、oracle关键字 - * - * @param field - * @return - */ - public String filterKeyWord(String field) { - String[] filters = { "`", "\"" }; - for (String f : filters) { - if (field.startsWith(f) && field.endsWith(f)) { - return field.substring(1, field.length() - 1); - } - } - if (field.indexOf(".") != -1) { - return field.replace(".", "_"); - } - return field; - } - - /** - * 打印参数列表 - */ - public void printLog() { - if (null != equalsParams && equalsParams.size() > 0) { - LOGGER.debug("execute parameter:" + equalsParams.values()); - } - if (null != greaterParams && greaterParams.size() > 0) { - LOGGER.debug("execute parameter:" + greaterParams.values()); - } - if (null != lessParams && lessParams.size() > 0) { - LOGGER.debug("execute parameter:" + lessParams.values()); - } - if (null != greaterThanParams && greaterThanParams.size() > 0) { - LOGGER.debug("execute parameter:" + greaterThanParams.values()); - } - if (null != lessThanParams && lessThanParams.size() > 0) { - LOGGER.debug("execute parameter:" + lessThanParams.values()); - } - if (null != likeParams && likeParams.size() > 0) { - LOGGER.debug("execute parameter:" + likeParams.values()); - } - if (null != inParams && inParams.size() > 0) { - Set keys = inParams.keySet(); - for (String name : keys) { - LOGGER.debug("execute parameter:" - + Arrays.toString(inParams.get(name))); - } - } - } - - /** - * 参数全部清空 - */ - public void clearMap() { - - if(null != this.params){ - this.params.clear(); - this.params = null; - } - - if(null != this.equalsParams){ - this.equalsParams.clear(); - this.equalsParams = null; - } - - if(null != this.greaterParams){ - this.greaterParams.clear(); - this.greaterParams = null; - } - - if(null != this.greaterThanParams){ - this.greaterThanParams.clear(); - this.greaterThanParams = null; - } - - if(null != this.lessParams){ - this.lessParams.clear(); - this.lessParams = null; - } - - if(null != this.lessThanParams){ - this.lessThanParams.clear(); - this.lessThanParams = null; - } - - if(null != this.likeParams){ - this.likeParams.clear(); - this.likeParams = null; - } - - if(null != this.inParams){ - this.inParams.clear(); - this.inParams = null; - } - - } - - public void select() { - this.sql = "select * from " + this.tableName; - this.equalsParams = CollectionKit.newHashMap(); - this.dmlType = DmlType.SELECT; - } - - /** - * 自定义sql返回查询model对象 - * @param sql - */ - public void select(String sql) { - this.sql = sql; - this.equalsParams = CollectionKit.newHashMap(); - this.dmlType = DmlType.SELECT; - } - - /** - * @return 返回计算count - */ - public void count(){ - this.sql = "select count(1) from " + this.tableName; - this.equalsParams = CollectionKit.newHashMap(); - this.dmlType = DmlType.COUNT; - } - - /** - * 自定义sql返回查询model对象 - * - * @param sql - */ - public void count(String sql){ - this.sql = sql; - this.equalsParams = CollectionKit.newHashMap(); - this.dmlType = DmlType.COUNT; - } - - public void update(){ - this.sql = "update " + this.tableName; - this.params = CollectionKit.newHashMap(); - this.equalsParams = CollectionKit.newHashMap(); - this.dmlType = DmlType.UPDATE; - } - - /** - * 自定义更新语句 - * @param sql - */ - public void update(String sql){ - this.sql = sql; - this.params = CollectionKit.newHashMap(); - this.equalsParams = CollectionKit.newHashMap(); - this.dmlType = DmlType.UPDATE; - } - - public void insert(){ - this.sql = "insert into " + this.tableName; - this.params = CollectionKit.newHashMap(); - this.dmlType = DmlType.INSERT; - } - - /** - * 设置插入sql - * @param sql - */ - public void insert(String sql){ - this.sql = sql; - this.params = CollectionKit.newHashMap(); - this.dmlType = DmlType.INSERT; - } - - public void delete(){ - this.sql = "delete from " + this.tableName; - this.equalsParams = CollectionKit.newHashMap(); - this.dmlType = DmlType.DELETE; - } - - /** - * 设置删除sql - * @param sql - */ - public void delete(String sql){ - this.sql = sql; - this.equalsParams = CollectionKit.newHashMap(); - this.dmlType = DmlType.DELETE; - } - - /** - * 设置参数列表,新增,更新用到 - * - * @param name - * @param value - */ - public void param(String name, Object value){ - if(StringKit.isNotBlank(name) && null != value){ - this.params.put(name, value); - } - } - - /** - * 设置where参数列表,查询,更新,删除用到 - * - * @param name - * @param value - */ - public void where(String name, Object value){ - if(StringKit.isNotBlank(name) && null != value){ - this.equalsParams.put(name, value); - } - } - - /** - * 大于条件 - * - * @param name - * @param value - */ - public void greater(String name, Object value){ - if(StringKit.isNotBlank(name) && null != value){ - if(null == this.greaterParams){ - this.greaterParams = CollectionKit.newHashMap(); - } - this.greaterParams.put(name, value); - } - } - - /** - * 大于等于条件 - * - * @param name - * @param value - */ - public void greaterThan(String name, Object value){ - if(StringKit.isNotBlank(name) && null != value){ - if(null == this.greaterThanParams){ - this.greaterThanParams = CollectionKit.newHashMap(); - } - this.greaterThanParams.put(name, value); - } - } - - /** - * 小于条件 - * - * @param name - * @param value - * @return - */ - public void less(String name, Object value){ - if(StringKit.isNotBlank(name) && null != value){ - if(null == this.lessParams){ - this.lessParams = CollectionKit.newHashMap(); - } - this.lessParams.put(name, value); - } - } - - /** - * 小于等于条件 - * - * @param name - * @param value - */ - public void lessThan(String name, Object value){ - if(StringKit.isNotBlank(name) && null != value){ - - if(null == this.lessThanParams){ - this.lessThanParams = CollectionKit.newHashMap(); - } - - this.lessThanParams.put(name, value); - } - } - - /** - * like条件 - * - * @param name - * @param value - */ - public void like(String name, String value){ - if(StringKit.isNotBlank(name) && StringKit.isNotBlank(value) && !value.equals("%%")){ - - if(null == this.likeParams){ - this.likeParams = CollectionKit.newHashMap(); - } - this.likeParams.put(name, value); - } - } - - /** - * in条件 - * - * @param name - * @param value - */ - public void in(String name, Object... values){ - if(StringKit.isNotBlank(name) && null != values && values.length > 1){ - if(null == this.inParams){ - this.inParams = CollectionKit.newHashMap(); - } - this.inParams.put(name, values); - } - } - -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/DBConfig.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/DBConfig.java deleted file mode 100644 index e7035d750..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/DBConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -package blade.plugin.sql2o; - -/** - * 用于保存数据库元信息 - * - * @author biezhi - * @since 1.0 - */ -public class DBConfig { - - private String drive; - private String url; - private String user; - private String password; - - public DBConfig(String drive, String url, String user, String password) { - super(); - this.drive = drive; - this.url = url; - this.user = user; - this.password = password; - } - - public String getDrive() { - return drive; - } - - public String getUrl() { - return url; - } - - public String getUser() { - return user; - } - - public String getPassword() { - return password; - } - -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/Model.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/Model.java deleted file mode 100644 index a2b3bd265..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/Model.java +++ /dev/null @@ -1,1029 +0,0 @@ -package blade.plugin.sql2o; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.sql2o.Connection; -import org.sql2o.Query; -import org.sql2o.Sql2o; - -import blade.kit.BeanKit; -import blade.kit.StringKit; -import blade.kit.log.Logger; -import blade.plugin.sql2o.Condition.DmlType; -import blade.plugin.sql2o.cache.Sql2oCache; -import blade.plugin.sql2o.cache.Sql2oCacheFactory; -import blade.plugin.sql2o.ds.DataSourceManager; -import blade.plugin.sql2o.kit.MD5; - -/** - * 实体对象基类 - * - * @author biezhi - * @since 1.0 - */ -public class Model implements Serializable { - - private static final long serialVersionUID = -8227936256753441060L; - - private static final Logger LOGGER = Logger.getLogger(Model.class); - - /** - * 是否开启缓存 - */ - private static boolean isOpenCache = Sql2oPlugin.INSTANCE.isOpenCache(); - - /** - * 缓存操作 - */ - private static Sql2oCache sql2oCache = isOpenCache ? Sql2oCacheFactory.getSql2oCache() : null; - - /** - * sql2o对象,操作数据库 - */ - private Sql2o sql2o = DataSourceManager.getSql2o(); - - /** - * 当前class实例 - */ - private Class model; - - /** - * 条件对象 - */ - private Condition condition; - - private final String CACHE_KEY_LIST = this.getClass().getName() + ":list"; - private final String CACHE_KEY_COUNT = this.getClass().getName() + ":count"; - private final String CACHE_KEY_DETAIL = this.getClass().getName() + ":detail"; - - public static Model getModel(Class clazz){ - return new Model(clazz); - } - - public Model() { - this.model = this.getClass(); - this.condition = new Condition(table(), pk()); - } - - public Model(Class clazz) { - this.model = clazz; - this.condition = new Condition(table(), pk()); - } - - public Sql2o getSql2o(){ - return sql2o; - } - - /** - * @return 返回表名称 - */ - public String table(){ - return model.getAnnotation(Table.class).value(); - } - - /** - * @return 返回表主键 - */ - public String pk(){ - return model.getAnnotation(Table.class).PK(); - } - - /** - * @return 返回表是否需要缓存 - */ - public boolean isCache(){ - return isOpenCache && model.getAnnotation(Table.class).isCache(); - } - - /** - * @return 返回查询model对象,推荐方式 - */ - public Model select(){ - condition.select(); - return this; - } - - - /** - * 自定义sql返回查询model对象 - * - * @param sql 要查询的sql语句 - * @return 返回查询model对象 - */ - public Model select(String sql){ - condition.select(sql); - return this; - } - - /** - * @return 返回计算count - */ - public Model count(){ - condition.count(); - return this; - } - - /** - * 自定义sql返回查询model对象 - * - * @param sql 要查询的sql语句 - * @return 返回查询model对象 - */ - public Model count(String sql){ - condition.count(sql); - return this; - } - - /** - * @return 返回更新model对象,推荐方式 - */ - public Model update(){ - condition.update(); - return this; - } - - /** - * 返回更新model对象 - * - * @param sql 自定义更新语句 - * @return 返回更新model对象 - */ - public Model update(String sql){ - condition.update(sql); - return this; - } - - /** - * @return 返回插入model对象,推荐方式 - */ - public Model insert(){ - condition.insert(); - return this; - } - - /** - * 根据自定义sql返回插入model对象 - * @param sql 自定义插入语句 - * @return 返回插入model对象 - */ - public Model insert(String sql){ - condition.insert(sql); - return this; - } - - /** - * @return 返回删除model对象 - */ - public Model delete(){ - condition.delete(); - return this; - } - - /** - * 返回自定义删除model对象 - * - * @param sql 自定义删除语句 - * @return 返回自定义删除model对象 - */ - public Model delete(String sql){ - condition.delete(sql); - return this; - } - - /** - * 设置参数列表,新增,更新用到 - * - * @param name 参数键 - * @param value 参数值 - * @return 返回model对象 - */ - public Model param(String name, Object value){ - condition.param(name, value); - return this; - } - - /** - * 设置where参数列表,查询,更新,删除用到 - * - * @param name 参数键 - * @param value 参数值 - * @return 返回model对象 - */ - public Model where(String name, Object value){ - condition.where(name, value); - return this; - } - - /** - * 大于 - * @param name - * @param value - * @return - */ - public Model greater(String name, Object value){ - condition.greater(name, value); - return this; - } - - /** - * 大于等于 - * - * @param name 参数键 - * @param value 参数值 - * @return 返回model对象 - */ - public Model greaterThan(String name, Object value){ - condition.greaterThan(name, value); - return this; - } - - /** - * 小于 - * @param name 参数键 - * @param value 参数值 - * @return 返回model对象 - */ - public Model less(String name, Object value){ - condition.less(name, value); - return this; - } - - /** - * 小于等于 - * @param name 参数键 - * @param value 参数值 - * @return 返回model对象 - */ - public Model lessThan(String name, Object value){ - condition.lessThan(name, value); - return this; - } - - /** - * like - * @param name - * @param value - * @return - */ - public Model like(String name, String value){ - condition.like(name, value); - return this; - } - - /** - * in - * @param name - * @param value - * @return - */ - public Model in(String name, Object... values){ - condition.in(name, values); - return this; - } - - /** - * 设置排序规则 - * - * @param orderby 排序字段和排序规则,如:ordernum desc - * @return 返回model对象 - */ - public Model orderBy(String orderby){ - condition.orderby = orderby; - return this; - } - - ////////////////////E////////////////////////// - - private String getCacheCountKey(){ - String cacheSql = getCacheKey(null); - if(cacheSql.indexOf("from") != -1 && cacheSql.indexOf("count(") == -1){ - int start = cacheSql.indexOf("from") + 4; - cacheSql = "selectcount(1)from " + cacheSql.substring(start); - } - cacheSql = cacheSql.replaceAll("\\s", ""); - return cacheSql; - } - - private String getCacheKey(String sql){ - - String sqlEnd = condition.sql; - if(StringKit.isNotBlank(sql)){ - sqlEnd = sql; - } - - - String cacheSql = SqlBuider.create(condition, sqlEnd) - .appendEquals() - .appendGreater() - .appendGreaterThan() - .appendLess() - .appendLessThan() - .appendLike() - .appendIn().appendAsString(); - - - if(cacheSql.lastIndexOf(" and ") != -1){ - cacheSql = cacheSql.substring(0, cacheSql.length() - 5); - } - - if(null != condition.orderby){ - cacheSql += " order by " + condition.orderby; - } - - cacheSql = cacheSql.replaceAll("\\s", ""); - - return cacheSql; - } - - /** - * 将参数加入到query中 - * @param query - * @return - */ - private Query parseParams(Query query){ - - // insert、update参数 - if(null != condition.params && condition.params.size() > 0){ - Set keys = condition.params.keySet(); - for(String name : keys){ - query.addParameter(condition.filterKeyWord(name), condition.params.get(name)); - } - } - - // 基础equals条件 - if(null != condition.equalsParams && condition.equalsParams.size() > 0){ - Set keys = condition.equalsParams.keySet(); - for(String name : keys){ - query.addParameter(condition.filterKeyWord(name), condition.equalsParams.get(name)); - } - } - - // 大于条件 - if(null != condition.greaterParams && condition.greaterParams.size() > 0){ - Set keys = condition.greaterParams.keySet(); - for(String name : keys){ - query.addParameter(condition.filterKeyWord(name), condition.greaterParams.get(name)); - } - } - - // 小于条件 - if(null != condition.lessParams && condition.lessParams.size() > 0){ - Set keys = condition.lessParams.keySet(); - for(String name : keys){ - query.addParameter(condition.filterKeyWord(name), condition.lessParams.get(name)); - } - } - - // 大于等于条件 - if(null != condition.greaterThanParams && condition.greaterThanParams.size() > 0){ - Set keys = condition.greaterThanParams.keySet(); - for(String name : keys){ - query.addParameter(condition.filterKeyWord(name), condition.greaterThanParams.get(name)); - } - } - - // 小于等于条件 - if(null != condition.lessThanParams && condition.lessThanParams.size() > 0){ - Set keys = condition.lessThanParams.keySet(); - for(String name : keys){ - query.addParameter(condition.filterKeyWord(name), condition.lessThanParams.get(name)); - } - } - - // like条件 - if(null != condition.likeParams && condition.likeParams.size() > 0){ - Set keys = condition.likeParams.keySet(); - for(String name : keys){ - query.addParameter(condition.filterKeyWord(name), condition.likeParams.get(name)); - } - } - - // in条件 - if(null != condition.inParams && condition.inParams.size() > 0){ - Set keys = condition.inParams.keySet(); - - for(String name : keys){ - Object[] objects = condition.inParams.get(name); - int len = objects.length; - - for(int i=0; i M fetchOne(){ - - if(condition.dmlType.equals(DmlType.SELECT)){ - - String sqlEnd = condition.getConditionSql(); - - M res = null; - String field = null; - - // 是否开启缓存查询 - if(isCache()){ - field = MD5.create(getCacheKey(null)); - - res = sql2oCache.hget(CACHE_KEY_DETAIL, field); - if(null != res){ - return res; - } - } - - Connection conn = sql2o.open(); - Query query = conn.createQuery(sqlEnd); - query = parseParams(query); - - LOGGER.debug("execute sql:" + query.toString()); - condition.printLog(); - condition.clearMap(); - - res = (M) query.executeAndFetchFirst(model); - - // 重新放入缓存 - if(isCache() && null != res){ - sql2oCache.hset(CACHE_KEY_DETAIL, field, res); - } - return res; - } - return null; - } - - @SuppressWarnings("unchecked") - public M fetchByPk(Serializable pk){ - - if(condition.dmlType.equals(DmlType.SELECT) && null != pk){ - - M res = null; - String field = null; - // 启用缓存 - if(isCache()){ - field = MD5.create(getCacheKey(null)); - res = sql2oCache.hget(CACHE_KEY_DETAIL, field); - - if(null != res){ - return res; - } - } - - String sqlEnd = condition.sql + " where " + pk() + " = :pk"; - - Connection conn = sql2o.open(); - Query query = conn.createQuery(sqlEnd).addParameter("pk", pk); - - LOGGER.debug("execute sql:" + query.toString()); - condition.printLog(); - condition.clearMap(); - - res = (M) query.executeAndFetchFirst(model); - - if(isCache() && null != res){ - sql2oCache.hset(CACHE_KEY_DETAIL, field, res); - } - - return res; - } - return null; - } - - /** - * @return 返回查询一个对象 - */ - @SuppressWarnings("unchecked") - public M fetchColum(){ - if(condition.dmlType.equals(DmlType.SELECT)){ - - String sqlEnd = condition.getConditionSql(); - - Connection conn = sql2o.open(); - Query query = conn.createQuery(sqlEnd); - query = parseParams(query); - - LOGGER.debug("execute sql:" + query.toString()); - condition.printLog(); - condition.clearMap(); - - return (M) query.executeScalar(); - } - return null; - } - - /** - * @return 查询list数据 - */ - @SuppressWarnings("unchecked") - public List fetchList(){ - if(condition.dmlType.equals(DmlType.SELECT)){ - - String field = null; - List result = null; - - // 开启缓存 - if(isCache()){ - field = MD5.create(getCacheKey(null)); - result = sql2oCache.hgetlist(CACHE_KEY_LIST, field); - if(null != result){ - return result; - } - } - - String sqlEnd = condition.getConditionSql(); - - if(null != condition.orderby){ - sqlEnd += " order by " + condition.orderby; - } - - Connection conn = sql2o.open(); - Query query = conn.createQuery(sqlEnd); - query = parseParams(query); - - LOGGER.debug("execute sql:" + query.toString()); - - condition.printLog(); - condition.clearMap(); - - result = (List) query.executeAndFetch(model); - - if(isCache() && null != result){ - sql2oCache.hsetlist(CACHE_KEY_LIST, field, result); - } - - return result; - } - return null; - } - - /** - * @return 返回一个listmap类型的数据 - */ - public List> fetchListMap(){ - - if(condition.dmlType.equals(DmlType.SELECT)){ - - List list = fetchList(); - if(null != list && list.size() > 0){ - List> result = BeanKit.toListMap(list); - return result; - } - } - return null; - } - - /** - * 分页检索 - * - * @param page 当前页码 - * @param pageSize 每页条数 - * @return 返回分页后的Page对象 - */ - @SuppressWarnings("unchecked") - public Page fetchPage(Integer page, Integer pageSize){ - - Page pageModel = new Page(0, page, pageSize); - - if(condition.dmlType.equals(DmlType.SELECT) && null != page && null != pageSize && page > 0 && pageSize > 0){ - - // 查询总记录数 - long totalCount = getPageCount(); - - String field = null; - List results = null; - - // 开启缓存 - if(isCache()){ - String cacheSql = getCacheKey(null) + "limit"+ (page - 1) +"," + pageSize; - cacheSql = cacheSql.replaceAll("\\s", ""); - field = MD5.create(cacheSql); - - results = sql2oCache.hgetlist(CACHE_KEY_LIST, field); - - pageModel = new Page(totalCount, page, pageSize); - - if(null != results && results.size() > 0){ - pageModel.setResults((List) results); - return pageModel; - } - - } - - String sqlEnd = condition.getConditionSql(); - - if(null != condition.orderby){ - sqlEnd += " order by " + condition.orderby; - } - - sqlEnd += " limit :page, :pageSize"; - - condition.equalsParams.put("page", page - 1); - condition.equalsParams.put("pageSize", pageSize); - - // 设置query - Connection conn = sql2o.open(); - Query query = conn.createQuery(sqlEnd); - query = parseParams(query); - - LOGGER.debug("execute sql:" + query.toString()); - condition.printLog(); - - results = query.executeAndFetch(this.model); - if(null != results && results.size() > 0){ - - if(isCache()){ - sql2oCache.hsetlist(CACHE_KEY_LIST, field, results); - } - pageModel.setResults((List) results); - } - - return pageModel; - } - - condition.clearMap(); - - return pageModel; - } - - /** - * 分页检索 - * - * @param page 当前页码 - * @param pageSize 每页条数 - * @return 返回分页后的Page对象 - */ - public Page> fetchPageMap(Integer page, Integer pageSize){ - - Page> pageMap = new Page>(0, page, pageSize); - - if(condition.dmlType.equals(DmlType.SELECT) && null != page && null != pageSize && page > 0 && pageSize > 0){ - - Page pageModel = fetchPage(page, pageSize); - - if(null != pageModel && null != pageModel.getResults()){ - - pageMap = new Page>(pageModel.getTotalCount(), page, pageSize); - - List list = pageModel.getResults(); - List> result = BeanKit.toListMap(list); - pageMap.setResults(result); - - } - - condition.clearMap(); - - return pageMap; - } - - return pageMap; - } - - /*************************************SELECT:E*****************************************/ - - - /** - * 执行并提交 - * @return - */ - public T executeAndCommit(){ - return executeAndCommit(null); - } - - /** - * 执行并提交,事务一致 - * - * @return 返回主键 - */ - @SuppressWarnings("unchecked") - public T executeAndCommit(Class returnType){ - - Query query = null; - // 插入 - if(condition.dmlType.equals(DmlType.INSERT)){ - query = insertCommit(null); - - LOGGER.debug("更新缓存:" + model.getName() + " -> count,list"); - - sql2oCache.hdel(CACHE_KEY_COUNT); - sql2oCache.hdel(CACHE_KEY_LIST); - - } - - // 更新 - if(condition.dmlType.equals(DmlType.UPDATE)){ - query = updateCommit(null); - - LOGGER.debug("更新缓存:" + model.getName() + " -> detail,list"); - - sql2oCache.hdel(CACHE_KEY_DETAIL); - sql2oCache.hdel(CACHE_KEY_LIST); - } - - // 删除 - if(condition.dmlType.equals(DmlType.DELETE)){ - query = deleteCommit(null); - - LOGGER.debug("更新缓存:" + model.getName() + " -> count,list,detail"); - - sql2oCache.hdel(CACHE_KEY_COUNT); - sql2oCache.hdel(CACHE_KEY_LIST); - sql2oCache.hdel(CACHE_KEY_DETAIL); - - } - - condition.clearMap(); - - T key = null; - if(null != returnType){ - key = query.executeUpdate().getKey(returnType); - } else { - key = (T) query.executeUpdate().getKey(); - } - return key; - - } - - /** - * 执行并提交,事务一致 - * - * @param connection - * @return - */ - public Query execute(Connection connection){ - - Query query = null; - - if(null == connection){ - connection = sql2o.beginTransaction(); - } - - // 插入 - if(condition.dmlType.equals(DmlType.INSERT)){ - query = insertCommit(connection); - } - - // 更新 - if(condition.dmlType.equals(DmlType.UPDATE)){ - query = updateCommit(connection); - } - - // 删除 - if(condition.dmlType.equals(DmlType.DELETE)){ - query = deleteCommit(connection); - } - - condition.clearMap(); - - return query; - } - - /** - * @return 删除并返回连接 - */ - private Query deleteCommit(Connection conn) { - - String deleteSql = condition.getConditionSql(); - - if(null == conn){ - conn = sql2o.open(); - } - - Query query = conn.createQuery(deleteSql); - query = parseParams(query); - - LOGGER.debug("execute sql:" + query.toString()); - LOGGER.debug("execute parameter:" + condition.equalsParams.values()); - - return query; - } - - /** - * @return 插入并返回连接 - */ - private Query insertCommit(Connection conn){ - - String insertSql = condition.sql; - - if(!condition.params.isEmpty() && condition.params.size() > 0){ - - StringBuffer paramBuf = new StringBuffer("("); - StringBuffer valuesBuf = new StringBuffer(" values("); - - Set keys = condition.params.keySet(); - for(String name : keys){ - paramBuf.append(name + ", "); - valuesBuf.append(":" + condition.filterKeyWord(name) + ", "); - - } - - if(paramBuf.lastIndexOf(", ") != -1 && valuesBuf.lastIndexOf(", ") != -1){ - String start = paramBuf.substring(0, paramBuf.length() - 2); - String end = valuesBuf.substring(0, valuesBuf.length() - 2); - - insertSql = condition.sql + start + ") " + end + ")"; - } - } - - if(null == conn){ - conn = sql2o.open(); - } - - Query query = conn.createQuery(insertSql); - query = parseParams(query); - - LOGGER.debug("execute sql:" + query.toString()); - LOGGER.debug("execute parameter:" + condition.params.values()); - - return query; - } - - /** - * @return 更新并返回连接 - */ - private Query updateCommit(Connection conn){ - - if(null != condition.params && condition.params.size() > 0){ - - StringBuffer setBuf = new StringBuffer(" set "); - StringBuffer whereBuf = new StringBuffer("("); - - String setSql = ""; - String whereSql = ""; - - Set keys = condition.params.keySet(); - for(String name : keys){ - setBuf.append(name + " = :" + condition.filterKeyWord(name) + ", "); - } - - if(setBuf.lastIndexOf(", ") != -1){ - setSql = setBuf.substring(0, setBuf.length() - 2); - } - - whereSql = condition.getConditionSql(""); - - if(whereBuf.lastIndexOf(", ") != -1){ - whereSql = whereBuf.substring(0, whereBuf.length() - 2); - } - - if(setSql.length() > 0){ - - String updateSql = condition.sql + setSql + whereSql; - - if(null == conn){ - conn = sql2o.open(); - } - - Query query = conn.createQuery(updateSql); - query = parseParams(query); - - LOGGER.debug("execute sql:" + query.toString()); - LOGGER.debug("execute parameter:" + condition.params.values() + condition.equalsParams.values()); - - return query; - } - } - return null; - } - - /** - * table转list - * @param table - * @return - */ - public List> tableAsList(org.sql2o.data.Table table) { - if(null != table){ - List> list = table.asList(); - - List> result = new ArrayList>(list.size()); - if(null != list && list.size() > 0){ - for(Map map : list){ - Map m = getMap(map); - if(null != m && m.size() > 0){ - result.add(m); - } - } - - return result; - } - } - return null; - } - - /** - * map转换 - * @param m - * @return - */ - private Map getMap(Map m) { - // 一个map就是数据库一行记录 - Set names = m.keySet(); - if(null != names && names.size() > 0){ - Map map = new HashMap(names.size()); - for(String name : names){ - map.put(name, m.get(name)); - } - return map; - } - return null; - } - -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/Page.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/Page.java deleted file mode 100644 index 9fb0b03d1..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/Page.java +++ /dev/null @@ -1,184 +0,0 @@ -package blade.plugin.sql2o; - -import java.util.ArrayList; -import java.util.List; - -/** - * 分页类 - * - * @author biezhi - * @since 1.0 - * @param - */ -public class Page { - - /** - * 当前页 - */ - private int page = 1; - - /** - * 每页条数 - */ - private int pageSize = 10; - - /** - * 总页数 - */ - private int totalPage = 1; - - /** - * 总记录数 - */ - private long totalCount = 0L; - /** - * 上一页 - */ - private int prev_page = 1; - /** - * 下一页 - */ - private int next_page = 1; - /** - * 首页 - */ - private int home_page = 1; - /** - * 尾页 - */ - private Integer last_page = 1; - - /** - * 链接 - */ - private String url = ""; - /** - * 固定导航数 - */ - private Integer navNum = 1; - - /** - * 数据集 - */ - private List results = new ArrayList(); - - /** - * @param totleCount 总记录数 - * @param page 当前第几页 - * @param pageSize 每页显示条数 - */ - public Page(long totalCount, int page, int pageSize) { - - this.page = page; - - this.pageSize = pageSize; - - //总条数 - this.totalCount = totalCount; - - //总页数 - this.totalPage = ((int) ((this.totalCount + this.pageSize - 1) / pageSize)); - - //首页 - this.home_page = 1; - - //尾页 - this.last_page = totalPage; - - //上一页 - this.prev_page = Math.max(this.page - 1, home_page); - - //下一页 - this.next_page = Math.min(this.page + 1, last_page); - - } - - public int getPage() { - return this.page; - } - - public Integer getPageSize() { - return this.pageSize; - } - - public int getTotalPage() { - return this.totalPage; - } - - public long getTotalCount() { - return totalCount; - } - - public Integer getPrev_page() { - return this.prev_page; - } - - public Integer getNext_page() { - return this.next_page; - } - - public Integer getHome_page() { - return this.home_page; - } - - public Integer getLast_page() { - return this.last_page; - } - - public List getResults() { - return this.results; - } - - public void setResults(List results) { - this.results = results; - } - - public Integer getNavNum() { - return this.navNum; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public void setPage(int page) { - this.page = page; - } - - public void setPageSize(int pageSize) { - this.pageSize = pageSize; - } - - public void setTotalPage(int totalPage) { - this.totalPage = totalPage; - } - - public void setTotalCount(long totalCount) { - this.totalCount = totalCount; - } - - public void setPrev_page(int prev_page) { - this.prev_page = prev_page; - } - - public void setNext_page(int next_page) { - this.next_page = next_page; - } - - public void setHome_page(int home_page) { - this.home_page = home_page; - } - - public void setLast_page(Integer last_page) { - this.last_page = last_page; - } - - public void setNavNum(Integer navNum) { - this.navNum = navNum; - } - -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/Sql2oPlugin.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/Sql2oPlugin.java deleted file mode 100644 index 5e36b7272..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/Sql2oPlugin.java +++ /dev/null @@ -1,71 +0,0 @@ -package blade.plugin.sql2o; - -import javax.sql.DataSource; - -import blade.kit.StringKit; -import blade.kit.log.Logger; -import blade.plugin.Plugin; -import blade.plugin.sql2o.ds.DataSourceManager; - -/** - * sql2o数据库插件 - * - * @author biezhi - * @since 1.0 - */ -public enum Sql2oPlugin implements Plugin { - - INSTANCE; - - private Logger LOGGER = Logger.getLogger(Sql2oPlugin.class); - - private DBConfig dbConfig; - - private boolean openCache; - - private Sql2oPlugin() { - } - - /** - * 设置数据库配置 - * - * @param url - * @param driver - * @param user - * @param pass - */ - public Sql2oPlugin config(String url, String driver, String user, String pass){ - - if(StringKit.isNotEmpty(url) && StringKit.isNotEmpty(driver) - && StringKit.isNotEmpty(user) && StringKit.isNotEmpty(pass)){ - - dbConfig = new DBConfig(driver, url, user, pass); - } - - return INSTANCE; - } - - public Sql2oPlugin openCache(){ - this.openCache = true; - return INSTANCE; - } - - public boolean isOpenCache() { - return openCache; - } - - public DBConfig dbConfig(){ - return dbConfig; - } - - @Override - public void run() { - DataSource dataSource = DataSourceManager.getDataSource(); - if(null != dataSource){ - LOGGER.debug("数据库插件配置成功..."); - } else { - LOGGER.error("数据库插件配置失败"); - } - } - -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/SqlBuider.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/SqlBuider.java deleted file mode 100644 index f94276eda..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/SqlBuider.java +++ /dev/null @@ -1,292 +0,0 @@ -package blade.plugin.sql2o; - -import java.util.Set; - -import blade.plugin.sql2o.Condition; - -/** - * sql组装器 - * - * @author biezhi - * @since 1.0 - */ -public class SqlBuider { - - private Condition condition; - private StringBuffer addSqlBuf; - private StringBuffer appendSqlBuf; - - public static SqlBuider create(Condition condition, String baseSql) { - return new SqlBuider(condition, baseSql); - } - - private SqlBuider(Condition condition, String baseSql) { - this.condition = condition; - this.addSqlBuf = new StringBuffer(baseSql); - this.appendSqlBuf = new StringBuffer(baseSql); - } - - /***************************APPEND:S********************************/ - public SqlBuider appendEquals(){ - // 基础where equals条件 - if(null != condition.equalsParams && condition.equalsParams.size() > 0){ - if(appendSqlBuf.indexOf("where") == -1){ - appendSqlBuf.append(" where "); - } - Set keys = condition.equalsParams.keySet(); - for(String name : keys){ - appendSqlBuf.append(name + "=" + condition.equalsParams.get(name) + " and "); - } - } - return this; - } - - /** - * 大于条件 - * @param sql - * @return - */ - public SqlBuider appendGreater(){ - if(null != condition.greaterParams && condition.greaterParams.size() > 0){ - if(appendSqlBuf.indexOf("where") == -1){ - appendSqlBuf.append(" where "); - } - Set keys = condition.greaterParams.keySet(); - for(String name : keys){ - appendSqlBuf.append(name + " > " + condition.greaterParams.get(name) + " and "); - } - } - return this; - } - - /** - * 小于条件 - * - * @param sql - * @return - */ - public SqlBuider appendLess(){ - if(null != condition.lessParams && condition.lessParams.size() > 0){ - if(appendSqlBuf.indexOf("where") == -1){ - appendSqlBuf.append(" where "); - } - Set keys = condition.lessParams.keySet(); - for(String name : keys){ - appendSqlBuf.append(name + " < " + condition.lessParams.get(name) + " and "); - } - } - return this; - } - - /** - * 大于等于条件 - * @param sql - * @return - */ - public SqlBuider appendGreaterThan(){ - if(null != condition.greaterThanParams && condition.greaterThanParams.size() > 0){ - if(appendSqlBuf.indexOf("where") == -1){ - appendSqlBuf.append(" where "); - } - Set keys = condition.greaterThanParams.keySet(); - for(String name : keys){ - appendSqlBuf.append(name + " >= " + condition.greaterThanParams.get(name) + " and "); - } - } - return this; - } - - public SqlBuider appendLessThan(){ - // 小于等于条件 - if(null != condition.lessThanParams && condition.lessThanParams.size() > 0){ - if(appendSqlBuf.indexOf("where") == -1){ - appendSqlBuf.append(" where "); - } - Set keys = condition.lessThanParams.keySet(); - for(String name : keys){ - appendSqlBuf.append(name + " <= " + condition.lessThanParams.get(name) + " and "); - } - } - return this; - } - - public SqlBuider appendLike(){ - // like条件 - if(null != condition.likeParams && condition.likeParams.size() > 0){ - if(appendSqlBuf.indexOf("where") == -1){ - appendSqlBuf.append(" where "); - } - Set keys = condition.likeParams.keySet(); - for(String name : keys){ - appendSqlBuf.append(name + " like " + condition.likeParams.get(name) + " and "); - } - } - return this; - } - - public SqlBuider appendIn(){ - // in条件 - if(null != condition.inParams && condition.inParams.size() > 0){ - if(appendSqlBuf.indexOf("where") == -1){ - appendSqlBuf.append(" where "); - } - Set keys = condition.inParams.keySet(); - - for(String name : keys){ - int len = condition.inParams.get(name).length; - - appendSqlBuf.append(name + " in ("); - - for(int i=0; i 0){ - if(addSqlBuf.indexOf("where") == -1){ - addSqlBuf.append(" where "); - } - Set keys = condition.equalsParams.keySet(); - for(String name : keys){ - addSqlBuf.append(name + " = :" + condition.filterKeyWord(name) + " and "); - } - } - return this; - } - - /** - * 大于条件 - * @param sql - * @return - */ - public SqlBuider addGreater(){ - if(null != condition.greaterParams && condition.greaterParams.size() > 0){ - if(addSqlBuf.indexOf("where") == -1){ - addSqlBuf.append(" where "); - } - Set keys = condition.greaterParams.keySet(); - for(String name : keys){ - addSqlBuf.append(name + " > :" + condition.filterKeyWord(name) + " and "); - } - } - return this; - } - - /** - * 小于条件 - * - * @param sql - * @return - */ - public SqlBuider addLess(){ - if(null != condition.lessParams && condition.lessParams.size() > 0){ - if(addSqlBuf.indexOf("where") == -1){ - addSqlBuf.append(" where "); - } - Set keys = condition.lessParams.keySet(); - for(String name : keys){ - addSqlBuf.append(name + " < :" + condition.filterKeyWord(name) + " and "); - } - } - return this; - } - - /** - * 大于等于条件 - * @param sql - * @return - */ - public SqlBuider addGreaterThan(){ - if(null != condition.greaterThanParams && condition.greaterThanParams.size() > 0){ - if(addSqlBuf.indexOf("where") == -1){ - addSqlBuf.append(" where "); - } - Set keys = condition.greaterThanParams.keySet(); - for(String name : keys){ - addSqlBuf.append(name + " >= :" + condition.filterKeyWord(name) + " and "); - } - } - return this; - } - - public SqlBuider addLessThan(){ - // 小于等于条件 - if(null != condition.lessThanParams && condition.lessThanParams.size() > 0){ - if(addSqlBuf.indexOf("where") == -1){ - addSqlBuf.append(" where "); - } - Set keys = condition.lessThanParams.keySet(); - for(String name : keys){ - addSqlBuf.append(name + " <= :" + condition.filterKeyWord(name) + " and "); - } - } - return this; - } - - public SqlBuider addLike(){ - // like条件 - if(null != condition.likeParams && condition.likeParams.size() > 0){ - if(addSqlBuf.indexOf("where") == -1){ - addSqlBuf.append(" where "); - } - Set keys = condition.likeParams.keySet(); - for(String name : keys){ - addSqlBuf.append(name + " like :" + condition.filterKeyWord(name) + " and "); - } - } - return this; - } - - public SqlBuider addIn(){ - // in条件 - if(null != condition.inParams && condition.inParams.size() > 0){ - if(addSqlBuf.indexOf("where") == -1){ - addSqlBuf.append(" where "); - } - Set keys = condition.inParams.keySet(); - for (String name : keys) { - int len = condition.inParams.get(name).length; - addSqlBuf.append(name + " in ("); - for (int i = 0; i < len; i++) { - if (i != len - 1) { - addSqlBuf.append(":" + condition.filterKeyWord(name) + "_" + i - + ", "); - } else { - addSqlBuf.append(":" + condition.filterKeyWord(name) + "_" + i); - } - } - addSqlBuf.append(") and "); - } - } - return this; - } - - public String addAsString(){ - if(null == addSqlBuf){ - return ""; - } - return addSqlBuf.toString(); - } - - /******************************A:E**********************************/ -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/Table.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/Table.java deleted file mode 100644 index 8d9ba4eb4..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/Table.java +++ /dev/null @@ -1,26 +0,0 @@ -package blade.plugin.sql2o; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * model表关联注解 - * - * @author biezhi - * @since 1.0 - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Table { - - String value(); - - String PK() default "id"; - - boolean isCache() default true; - -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/cache/SimpleSql2oCache.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/cache/SimpleSql2oCache.java deleted file mode 100644 index 3031b51e7..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/cache/SimpleSql2oCache.java +++ /dev/null @@ -1,106 +0,0 @@ -package blade.plugin.sql2o.cache; - -import java.util.List; - -import blade.cache.Cache; -import blade.cache.CacheManager; -import blade.plugin.sql2o.Model; - -@SuppressWarnings("unchecked") -public class SimpleSql2oCache implements Sql2oCache { - - private CacheManager cm = CacheManager.getInstance(); - - private Cache cache; - - public SimpleSql2oCache() { - // 5小时清理一次 - long cleanTime = 1000 * 3600 * 300; - cm.setCleanInterval(cleanTime); - // 存放1000个缓存,超过即自动清除 - cache = cm.newLRUCache("sql2o_cache").cacheSize(1000); - } - - @Override - public void set(String key, T value) { - cache.set(key, value); - } - - @Override - public void set(String key, T value, long expire) { - cache.set(key, value, expire); - } - - @Override - public void hset(String key, String field, T value) { - cache.hset(key, field, value); - } - - @Override - public void hset(String key, String field, T value, long expire) { - cache.hset(key, field, value, expire); - } - - @Override - public void hset(String key, String field, List value, long expire) { - cache.hset(key, field, value, expire); - } - - @Override - public T get(String key) { - Object value = cache.get(key); - if(null != value){ - return (T) value; - } - return null; - } - - @Override - public V hgetV(String key, String field) { - Object value = cache.hget(key, field); - if(null != value){ - return (V) value; - } - return null; - } - - @Override - public void hsetV(String key, String field, V value) { - cache.hset(key, field, value); - } - - @Override - public M hget(String key, String field) { - Object object = cache.hget(key, field); - if(null != object){ - return (M) object; - } - return null; - } - - - @Override - public List hgetlist(String key, String field) { - Object object = cache.hget(key, field); - if(null != object){ - return (List) object; - } - return null; - } - - @Override - public void hdel(String key) { - cache.hdel(key); - } - - @Override - public void hdel(String key, String field) { - cache.del(key, field); - } - - @Override - public void hsetlist(String key, String field, List value) { - cache.hset(key, field, value); - } - -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/cache/Sql2oCache.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/cache/Sql2oCache.java deleted file mode 100644 index 47cb9c07f..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/cache/Sql2oCache.java +++ /dev/null @@ -1,35 +0,0 @@ -package blade.plugin.sql2o.cache; - -import java.util.List; - -import blade.plugin.sql2o.Model; - -public interface Sql2oCache { - - void set(String key, T value); - - void set(String key, T value, long expire); - - void hset(String key, String field, T value); - - void hsetV(String key, String field, V value); - - void hsetlist(String key, String field, List value); - - void hset(String key, String field, T value, long expire); - - void hset(String key, String field, List value, long expire); - - M get(String key); - - M hget(String key, String field); - - V hgetV(String key, String field); - - List hgetlist(String key, String field); - - void hdel(String key); - - void hdel(String key, String field); - -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/cache/Sql2oCacheFactory.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/cache/Sql2oCacheFactory.java deleted file mode 100644 index 9629b97d4..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/cache/Sql2oCacheFactory.java +++ /dev/null @@ -1,28 +0,0 @@ -package blade.plugin.sql2o.cache; - -import blade.plugin.sql2o.Model; - -/** - * 缓存获取工厂 - * - * @author biezhi - * @since 1.0 - */ -public final class Sql2oCacheFactory { - - private static Sql2oCache sql2oCache = new SimpleSql2oCache(); - - public static Sql2oCache getSql2oCache(){ - return sql2oCache; - } - - /** - * 设置缓存 - * - * @param sql2oCache - */ - public static void setSql2oCache(Sql2oCache sql2oCache){ - Sql2oCacheFactory.sql2oCache = sql2oCache; - } - -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/connection/ConnectionHolder.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/connection/ConnectionHolder.java deleted file mode 100644 index 3aef7b8cb..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/connection/ConnectionHolder.java +++ /dev/null @@ -1,27 +0,0 @@ -package blade.plugin.sql2o.connection; - -import java.sql.Connection; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; - -import javax.sql.DataSource; - -public class ConnectionHolder { - - private Map connectionMap = new HashMap(); - - public Connection getConnection(DataSource dataSource) throws SQLException { - Connection connection = connectionMap.get(dataSource); - if (connection == null || connection.isClosed()) { - connection = dataSource.getConnection(); - connectionMap.put(dataSource, connection); - } - - return connection; - } - - public void removeConnection(DataSource dataSource) { - connectionMap.remove(dataSource); - } -} \ No newline at end of file diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/connection/SingleThreadConnectionHolder.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/connection/SingleThreadConnectionHolder.java deleted file mode 100644 index f3741c0b4..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/connection/SingleThreadConnectionHolder.java +++ /dev/null @@ -1,28 +0,0 @@ -package blade.plugin.sql2o.connection; - -import java.sql.Connection; -import java.sql.SQLException; - -import javax.sql.DataSource; - -public class SingleThreadConnectionHolder { - - private static ThreadLocal localConnectionHolder = new ThreadLocal(); - - public static Connection getConnection(DataSource dataSource) throws SQLException { - return getConnectionHolder().getConnection(dataSource); - } - - public static void removeConnection(DataSource dataSource) { - getConnectionHolder().removeConnection(dataSource); - } - - private static ConnectionHolder getConnectionHolder() { - ConnectionHolder connectionHolder = localConnectionHolder.get(); - if (connectionHolder == null) { - connectionHolder = new ConnectionHolder(); - localConnectionHolder.set(connectionHolder); - } - return connectionHolder; - } -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/ds/AbstractDataSource.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/ds/AbstractDataSource.java deleted file mode 100644 index 5cf7a0b20..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/ds/AbstractDataSource.java +++ /dev/null @@ -1,15 +0,0 @@ -package blade.plugin.sql2o.ds; - -import javax.sql.DataSource; - -/** - * 抽象数据源 - * - * @author biezhi - * @since 1.0 - */ -public interface AbstractDataSource{ - - DataSource getDataSource(); - -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/ds/DataSourceManager.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/ds/DataSourceManager.java deleted file mode 100644 index 5b852af87..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/ds/DataSourceManager.java +++ /dev/null @@ -1,103 +0,0 @@ -package blade.plugin.sql2o.ds; - -import java.sql.Connection; -import java.sql.SQLException; - -import javax.sql.DataSource; - -import org.sql2o.Sql2o; - -import blade.ioc.AbstractBeanFactory; -import blade.ioc.SingleBean; -import blade.plugin.sql2o.DBConfig; -import blade.plugin.sql2o.Sql2oPlugin; -import blade.plugin.sql2o.connection.SingleThreadConnectionHolder; -import blade.plugin.sql2o.exception.DataSourceException; - -/** - * 数据源连接管理器 - * @author biezhi - * @version 1.0 - */ -public final class DataSourceManager { - - private static DataSource dataSource; - - private static Sql2o sql2o = null; - - private static final AbstractBeanFactory beanFactory = new SingleBean(); - - private DataSourceManager() { - } - - static{ - - Object dsFactoryObj = beanFactory.getBean(AbstractDataSource.class); - if(null != dsFactoryObj && dsFactoryObj instanceof AbstractDataSource){ - DataSourceManager.dataSource = ((AbstractDataSource) dsFactoryObj).getDataSource(); - if(null == DataSourceManager.dataSource){ - throw new DataSourceException("数据源初始化失败!"); - } - } else { - // jdbc - DataSourceManager.dataSource = getJdbcDataSource(); - } - - if(null != DataSourceManager.dataSource){ - sql2o = new Sql2o(DataSourceManager.dataSource); - } - } - - public static Sql2o getSql2o(){ - return sql2o; - } - - /** - * 提供动态注入datasource - * @param dataSource_ - */ - public static void setDataSource(DataSource dataSource){ - DataSourceManager.dataSource = dataSource; - if(null != DataSourceManager.dataSource){ - sql2o = new Sql2o(DataSourceManager.dataSource); - } - } - - public static DataSource getDataSource(){ - return dataSource; - } - - /** - * 获取jdbc数据源 - * @return JdbcDataSource - */ - private static DataSource getJdbcDataSource(){ - DBConfig dbConfig = Sql2oPlugin.INSTANCE.dbConfig(); - if(null == dbConfig){ - throw new DataSourceException("没有配置数据库"); - } - String url = dbConfig.getUrl(); - String driver = dbConfig.getDrive(); - String username = dbConfig.getUser(); - String password = dbConfig.getPassword(); - dataSource = new JdbcDataSource(url, driver, username, password); - return dataSource; - } - - /** - * 获取数据库链接 - * @return connection对象 - */ - public static Connection getConnection() { - try { - if(null != dataSource){ - return SingleThreadConnectionHolder.getConnection(dataSource); - } - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException(e); - } - return null; - } - -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/ds/JdbcDataSource.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/ds/JdbcDataSource.java deleted file mode 100644 index 1605d1e03..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/ds/JdbcDataSource.java +++ /dev/null @@ -1,78 +0,0 @@ -package blade.plugin.sql2o.ds; - -import java.io.PrintWriter; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.util.logging.Logger; - -import javax.sql.DataSource; - -public class JdbcDataSource implements DataSource { - - private String url; - private String driver; - private String username; - private String password; - - public JdbcDataSource(String url, String driver, String username, - String password) { - super(); - this.url = url; - this.driver = driver; - this.username = username; - this.password = password; - } - - @Override - public PrintWriter getLogWriter() throws SQLException { - return null; - } - - @Override - public void setLogWriter(PrintWriter out) throws SQLException { - } - - @Override - public void setLoginTimeout(int seconds) throws SQLException { - } - - @Override - public int getLoginTimeout() throws SQLException { - return 0; - } - - @Override - public Logger getParentLogger() throws SQLFeatureNotSupportedException { - return null; - } - - @Override - public T unwrap(Class iface) throws SQLException { - return null; - } - - @Override - public boolean isWrapperFor(Class iface) throws SQLException { - return false; - } - - @Override - public Connection getConnection() throws SQLException { - try { - Class.forName(this.driver) ; - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - Connection conn = DriverManager.getConnection(this.url, this.username, this.password) ; - return conn; - } - - @Override - public Connection getConnection(String username, String password) - throws SQLException { - return null; - } - -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/exception/DataSourceException.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/exception/DataSourceException.java deleted file mode 100644 index 04b590657..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/exception/DataSourceException.java +++ /dev/null @@ -1,27 +0,0 @@ -package blade.plugin.sql2o.exception; - - -/** - * DataSourceException - *

- *

- * - * @author biezhi - * @since 1.0 - */ -public class DataSourceException extends RuntimeException{ - - private static final long serialVersionUID = 4566581404090220394L; - - public DataSourceException() { - throw new RuntimeException(); - } - - public DataSourceException(String e) { - throw new RuntimeException(e); - } - - public DataSourceException(Exception e) { - throw new RuntimeException(e); - } -} diff --git a/blade-sql2o/src/main/java/blade/plugin/sql2o/kit/MD5.java b/blade-sql2o/src/main/java/blade/plugin/sql2o/kit/MD5.java deleted file mode 100644 index 8bd3833c5..000000000 --- a/blade-sql2o/src/main/java/blade/plugin/sql2o/kit/MD5.java +++ /dev/null @@ -1,89 +0,0 @@ -package blade.plugin.sql2o.kit; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * MD5加密 - * - * @author biezhi - * @since 1.0 - */ -public class MD5 { - - /** 全局数组 **/ - private final static String[] strDigits = { "0", "1", "2", "3", "4", "5", - "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" }; - - /** - * 返回形式为数字跟字符串 - * - * @param bByte - * @return - */ - private static String byteAsArrayString(byte bByte) { - int iRet = bByte; - if (iRet < 0) { - iRet += 256; - } - int iD1 = iRet / 16; - int iD2 = iRet % 16; - return strDigits[iD1] + strDigits[iD2]; - } - - /** - * 转换字节数组为16进制字串 - * - * @param bByte - * @return - */ - private static String byteAsString(byte[] bByte) { - StringBuffer sBuffer = new StringBuffer(); - for (int i = 0; i < bByte.length; i++) { - sBuffer.append(byteAsArrayString(bByte[i])); - } - return sBuffer.toString(); - } - - /** - * MD5加密 - * - * @param str - * 待加密的字符串 - * @return - */ - public static String create(String str) { - String result = null; - try { - result = new String(str); - MessageDigest md = MessageDigest.getInstance("MD5"); - result = byteAsString(md.digest(str.getBytes())); - } catch (NoSuchAlgorithmException ex) { - ex.printStackTrace(); - } - return result; - } - - /** - * MD5加密 - * - * @param str 待加密的字符串 - * @param lowerCase 大小写 - * @return - */ - public static String create(String str, boolean lowerCase) { - String result = null; - try { - result = new String(str); - MessageDigest md = MessageDigest.getInstance("MD5"); - result = byteAsString(md.digest(str.getBytes())); - if (lowerCase) { - result = result.toLowerCase(); - } - } catch (NoSuchAlgorithmException ex) { - ex.printStackTrace(); - } - return result; - } - -} \ No newline at end of file diff --git a/blade-starter/pom.xml b/blade-starter/pom.xml new file mode 100644 index 000000000..ca0c41ef4 --- /dev/null +++ b/blade-starter/pom.xml @@ -0,0 +1,109 @@ + + + + blade + com.bladejava + 1.0 + + + 4.0.0 + + blade-starter + pom + 0.0.3 + + + UTF-8 + + 1.7.0-alpha + 1.0.0 + 0.1.3-beta + 1.0.4 + 0.0.7 + 0.0.4 + 0.0.7 + + + + + oss-snapshots + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + + + + + com.bladejava + blade-core + ${blade-core.version} + + + com.bladejava + blade-template-jetbrick + ${blade-template-jetbrick.version} + + + com.bladejava + blade-template-velocity + ${blade-template-velocity.version} + + + com.bladejava + blade-embed-jetty + ${blade-embed-jetty.version} + + + com.bladejava + blade-aop + ${blade-aop.version} + + + com.bladejava + blade-jdbc + ${blade-jdbc.version} + + + com.bladejava + blade-patchca + ${blade-patchca.version} + + + + + + + + src/main/java + false + + + src/main/resources + false + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + 1.8 + 1.8 + UTF-8 + + + + + + + \ No newline at end of file diff --git a/blade-velocity/pom.xml b/blade-velocity/pom.xml deleted file mode 100644 index 32e7ae0ed..000000000 --- a/blade-velocity/pom.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - 4.0.0 - - com.bladejava - blade-root - 1.0 - - - blade-velocity - jar - ${blade.version} - blade-velocity - https://github.com/biezhi/blade/blade-velocity - - - - com.bladejava - blade-core - ${blade.version} - - - org.apache.velocity - velocity - 1.7 - - - diff --git a/blade-velocity/src/main/java/blade/render/VelocityRender.java b/blade-velocity/src/main/java/blade/render/VelocityRender.java deleted file mode 100644 index 4031c74f6..000000000 --- a/blade-velocity/src/main/java/blade/render/VelocityRender.java +++ /dev/null @@ -1,173 +0,0 @@ -package blade.render; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintWriter; -import java.util.Enumeration; -import java.util.Properties; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.Velocity; -import org.apache.velocity.app.VelocityEngine; -import org.apache.velocity.exception.MethodInvocationException; -import org.apache.velocity.exception.ParseErrorException; -import org.apache.velocity.exception.ResourceNotFoundException; - -import blade.Blade; -import blade.BladeWebContext; -import blade.exception.BladeException; - -/** - * Velocity渲染引擎 - * @author biezhi - * - */ -public class VelocityRender extends Render { - - private final VelocityEngine velocityEngine; - - /** - * 默认构造函数 - */ - public VelocityRender() { - Properties properties = new Properties(); - - properties.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, Blade.webRoot()); - properties.setProperty(Velocity.ENCODING_DEFAULT, Blade.encoding()); - properties.setProperty(Velocity.INPUT_ENCODING, Blade.encoding()); - properties.setProperty(Velocity.OUTPUT_ENCODING, Blade.encoding()); - velocityEngine = new VelocityEngine(properties); - } - - /** - * 根据配置文件构造一个Velocity引擎 - * @param propertiesFile - * @throws IOException - */ - public VelocityRender(String propertiesFile) throws IOException { - String loadPath = VelocityRender.class.getClassLoader().getResource("/").getPath(); - String fileName = loadPath + propertiesFile; - - Properties properties = new Properties(); - InputStream inStream = new FileInputStream(new File(fileName)); - properties.load(inStream); - - // 默认查询路径 - if(!properties.contains(Velocity.FILE_RESOURCE_LOADER_PATH)){ - properties.put(Velocity.FILE_RESOURCE_LOADER_PATH, Blade.webRoot()); - } - velocityEngine = new VelocityEngine(properties); - } - - /** - * 根据构造一个Velocity引擎 - * @param properties - */ - public VelocityRender(Properties properties) { - velocityEngine = new VelocityEngine(properties); - } - - /** - * 手动构造Velocity引擎 - * @param velocityEngine_ - */ - public VelocityRender(VelocityEngine velocityEngine_) { - velocityEngine = velocityEngine_; - } - - /** - * 渲染视图 - */ - @Override - public Object render(String view) { - - HttpServletRequest servletRequest = BladeWebContext.servletRequest(); - HttpServletResponse servletResponse = BladeWebContext.servletResponse(); - - try { - PrintWriter writer = null; - VelocityContext context = new VelocityContext(); - - Enumeration attrs = servletRequest.getAttributeNames(); - while (attrs.hasMoreElements()) { - String attrName = attrs.nextElement(); - context.put(attrName, servletRequest.getAttribute(attrName)); - } - - view = disposeView(view); - - Template template = velocityEngine.getTemplate(view); - - writer = servletResponse.getWriter(); - - template.merge(context, writer); - - writer.flush(); - writer.close(); - - } catch (ResourceNotFoundException e) { - render404(servletResponse, view); - } catch (ParseErrorException e) { - e.printStackTrace(); - } catch (MethodInvocationException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - - /** - * 渲染视图 - */ - @Override - public Object render(ModelAndView modelAndView) { - HttpServletRequest servletRequest = BladeWebContext.servletRequest(); - HttpServletResponse servletResponse = BladeWebContext.servletResponse(); - - try { - - if(null == modelAndView){ - throw new BladeException("modelAndView is null"); - } - - PrintWriter writer = null; - VelocityContext context = new VelocityContext(modelAndView.getModel()); - - Enumeration attrs = servletRequest.getAttributeNames(); - while (attrs.hasMoreElements()) { - String attrName = attrs.nextElement(); - context.put(attrName, servletRequest.getAttribute(attrName)); - } - - String view = disposeView(modelAndView.getView()); - - Template template = velocityEngine.getTemplate(view); - - writer = servletResponse.getWriter(); - - template.merge(context, writer); - - writer.flush(); - writer.close(); - - } catch (ResourceNotFoundException e) { - render404(servletResponse, modelAndView.getView()); - } catch (ParseErrorException e) { - e.printStackTrace(); - } catch (MethodInvocationException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - -} diff --git a/blade-websocket-jetty/pom.xml b/blade-websocket-jetty/pom.xml new file mode 100644 index 000000000..3734cdca6 --- /dev/null +++ b/blade-websocket-jetty/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + com.bladejava + blade + 1.0 + + blade-websocket-jetty + 0.0.1 + blade-websocket-jetty + http://maven.apache.org + + + UTF-8 + 9.2.12.v20150709 + + + + + com.bladejava + blade-core + ${blade-core.version} + + + javax.websocket + javax.websocket-api + 1.0 + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + ${jetty.version} + + + diff --git a/blade-websocket-jetty/src/main/java/com/blade/websocket/JettyWebSocketServer.java b/blade-websocket-jetty/src/main/java/com/blade/websocket/JettyWebSocketServer.java new file mode 100644 index 000000000..78ac7159e --- /dev/null +++ b/blade-websocket-jetty/src/main/java/com/blade/websocket/JettyWebSocketServer.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2016, biezhi 王爵 (biezhi.me@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.websocket; + +import javax.websocket.server.ServerContainer; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; + +/** + * JettyWebSocketServer + * + * @author biezhi + * @since 1.6.7 + */ +public class JettyWebSocketServer implements WebSocketServer { + + private Server server; + private ServletContextHandler context; + private ServerContainer wscontainer; + private Class[] endPoints; + + public JettyWebSocketServer(Class...endPoints) { + this.endPoints = endPoints; + } + + @Override + public void start(int port) throws WebSocketException{ + this.start(port, "/"); + } + + @Override + public void start(int port, String contextPath) throws WebSocketException{ + server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(port); + server.addConnector(connector); + + // Setup the basic application "context" for this application at "/" + // This is also known as the handler tree (in jetty speak) + context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath(contextPath); + server.setHandler(context); + try { + // Initialize javax.websocket layer + wscontainer = WebSocketServerContainerInitializer.configureContext(context); + if (null != endPoints) { + // Add WebSocket endpoint to javax.websocket layer + for (int i = 0; i < endPoints.length; i++) { + wscontainer.addEndpoint(endPoints[i]); + } + } + server.start(); + } catch (Throwable t) { + throw new WebSocketException(t); + } + } + + @Override + public void stop() throws WebSocketException{ + try { + server.stop(); + } catch (Exception e) { + throw new WebSocketException(e); + } + } + + @Override + public void addEndpoints(Class... endPoints) { + this.endPoints = endPoints; + } + + @Override + public void join() throws WebSocketException { + try { + server.join(); + } catch (InterruptedException e) { + throw new WebSocketException(e); + } + } + +} diff --git a/pom.xml b/pom.xml index 2fd395ea7..4713a16fa 100644 --- a/pom.xml +++ b/pom.xml @@ -1,75 +1,199 @@ - + 4.0.0 - + com.bladejava - blade-root + blade 1.0 pom - - blade-root + + blade https://github.com/biezhi/blade + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + + biezhi + biezhi.me@gmail.com + + + + + scm:git@github.com:biezhi/blade.git + scm:git@github.com:biezhi/blade.git + git@github.com:biezhi/blade.git + + + + oss-snapshots + https://oss.sonatype.org/content/repositories/snapshots + + true + + + true + + + + - 1.6 - 1.6 + 1.8 + 1.8 UTF-8 - 4.11 - 1.1.4 - 3.0.1 + 3.1.0 + 4.12 + 2.0.2-beta + 1.7.21 + 1.7.21 + 1.7.0-alpha + 1.4.0-alpha + 1.0.0 + 0.0.7 + 9.2.12.v20150709 - + blade-kit blade-core - blade-cache - blade-jetbrick - blade-sql2o - blade-velocity - blade-beetl - blade-redis - - - - - junit - junit - ${junit.version} - - - - javax.servlet - javax.servlet-api - ${servlet.version} - provided - - + blade-embed-jetty + blade-auth + blade-aop + blade-starter + blade-websocket-jetty + blade-sample + + + + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + org.slf4j + slf4j-log4j12 + ${slf4j-log4j12.version} + + + junit + junit + ${junit.version} + test + + + org.mockito + mockito-all + ${mockito.version} + test + + + - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - UTF-8 - - - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + UTF-8 + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.17 + + true + + + + + + + + release + + + oss + https://oss.sonatype.org/content/repositories/snapshots/ + + + oss + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + package + + jar-no-fork + + + + + true + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.2 + + UTF-8 + UTF-8 + + + + package + + jar + + + -Xdoclint:none + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + + + \ No newline at end of file