├── .github
└── ISSUE_TEMPLATE.md
├── .gitignore
├── .travis.yml
├── LICENCE
├── README.md
├── README_EN.md
├── ScreenShot.png
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── org
│ │ └── mym
│ │ └── prettylog
│ │ ├── CrashPrinter.java
│ │ ├── LintTestCase.java
│ │ ├── MainActivity.java
│ │ ├── PLogApplication.java
│ │ ├── TextViewPrinter.java
│ │ ├── data
│ │ ├── JSONEntity.java
│ │ ├── LoremIpsum.java
│ │ ├── Singleton.java
│ │ └── User.java
│ │ ├── util
│ │ └── CrashHandler.java
│ │ └── wrapper
│ │ └── LogWrapper.java
│ └── res
│ ├── drawable-hdpi
│ └── ic_info_outline_white_24dp.png
│ ├── drawable-mdpi
│ └── ic_info_outline_white_24dp.png
│ ├── drawable-xhdpi
│ └── ic_info_outline_white_24dp.png
│ ├── drawable-xxhdpi
│ └── ic_info_outline_white_24dp.png
│ ├── drawable-xxxhdpi
│ └── ic_info_outline_white_24dp.png
│ ├── layout
│ ├── activity_main.xml
│ ├── dialog_about.xml
│ └── item_usage_case.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── default_config.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── plog-formatter
├── .gitignore
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── org
│ └── mym
│ └── plog
│ └── formatter
│ ├── DefaultFormatter.java
│ ├── JSONFormatter.java
│ ├── ObjectFormatter.java
│ ├── ThrowableFormatter.java
│ └── util
│ └── ObjectUtil.java
├── plog-lint-aar
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ └── AndroidManifest.xml
├── plog-lint
├── .gitignore
├── build.gradle
└── src
│ └── main
│ └── java
│ └── org
│ └── mym
│ └── plog
│ └── lint
│ ├── IssueRegistry.java
│ └── LoggingIssueDetector.java
├── plog-printer
├── .gitignore
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── org
│ └── mym
│ └── plog
│ └── printer
│ ├── FilePrinter.java
│ └── FilePrinterHelper.java
├── plog-timing
├── .gitignore
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── org
│ └── mym
│ └── plog
│ └── timing
│ └── TimingLogger.java
├── plog
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── org
│ └── mym
│ └── plog
│ ├── AbsPrinter.java
│ ├── Category.java
│ ├── DebugPrinter.java
│ ├── Formatter.java
│ ├── Interceptor.java
│ ├── LogEngine.java
│ ├── LogRequest.java
│ ├── PLog.java
│ ├── PrintLevel.java
│ ├── Printer.java
│ ├── SimpleCategory.java
│ ├── SoftWrapper.java
│ ├── Style.java
│ ├── config
│ └── PLogConfig.java
│ └── internal
│ ├── WordBreakWrapper.java
│ └── WordLengthWrapper.java
└── settings.gradle
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
3 |
4 | ## Basic info
5 | PLog version:
6 |
7 | ## Figure out a bug
8 |
17 |
18 | ## Feature Request
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | /local.properties
3 | /.idea
4 | .DS_Store
5 | /captures
6 | *.apk
7 | *.ap_
8 | *.dex
9 | *.class
10 | bin/
11 | gen/
12 | out/
13 | .gradle/
14 | build/
15 | proguard/
16 | *.log
17 | .navigation/
18 | captures/
19 | *.jks
20 |
21 | ### Jekyll template
22 | _site/
23 | .sass-cache/
24 | .jekyll-metadata
25 |
26 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # https://docs.travis-ci.com/user/languages/android
2 | language: android
3 | android:
4 | components:
5 | - platform-tools
6 | - tools
7 | - build-tools-25.0.3
8 | - android-25
9 | # - sys-img-armeabi-v7a-android-24
10 | - extra-android-m2repository
11 |
12 | licenses:
13 | - 'android-sdk-license.*'
14 |
15 | #compileSdkVersion 'android-24' requires JDK 1.8 or later to compile.
16 | jdk:
17 | - oraclejdk8
18 | #Only CI for master & dev
19 | branches:
20 | only:
21 | - master
22 | - dev
23 |
24 | before_cache:
25 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
26 | cache:
27 | directories:
28 | - $HOME/.gradle/caches/
29 | - $HOME/.gradle/wrapper/
30 |
31 | #before_script:
32 | # # Create and start emulator
33 | # - echo no | android create avd --force -n test -t android-24 --abi armeabi-v7a
34 | # - emulator -avd test -no-skin -no-audio -no-window &
35 | # - android-wait-for-emulator
36 | # - adb shell input keyevent 82 &
37 | #script:
38 | # - ./gradlew clean connectAndroidTest assembleRelease --stacktrace
39 |
40 | script:
41 | ./gradlew clean build
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [  ](https://android-arsenal.com/details/1/4884)[](https://jitpack.io/#Muyangmin/Android-PLog)[  ](https://bintray.com/muyangmin/org.mym/Android-PLog/_latestVersion)
2 |
3 | # PLog = Pure, Pretty, Powerful logging tool [](https://travis-ci.org/Muyangmin/Android-PLog)
4 |
5 | 
6 |
7 | [English Version](README_EN.md)
8 |
9 | ## 概述
10 | Android-PLog 项目(简称 PLog )是一个专为 Android
11 | 应用程序设计的开源日志封装库,追求纯粹、易用、强大,帮助开发者更好地使用日志调试程序,提高开发效率。概括来讲,它具有以下特性:
12 | #### 容易打印
13 | * **支持无Tag打印**
14 | * **支持空消息打印(通常用于观察某处是否执行)**
15 | * **支持任意参数类型**
16 | * **支持变长参数和自动格式化**
17 | * **支持Builder方式打印**
18 |
19 | #### 输出可控
20 | * **支持多维度的输出拦截**
21 | * **支持自定义输出装饰样式**
22 | * **支持多通道同时输出**
23 | * **支持自动换行(SoftWrap)**
24 |
25 | #### 筛选容易
26 | * **支持全局Tag**(可以区分不同应用)
27 | * **支持自动Tag**(可以区分不同类名)
28 | * **支持保留堆栈**(可以区分不同文件和方法,并且实现在AS中**自动日志定位**)
29 | * **支持分组打印**(可以区分不同开发者, etc)
30 |
31 | #### 按需依赖
32 | 核心功能模块和定制特性完全分离,体积轻巧,并且全部支持Jcenter依赖。
33 |
34 | #### 扩展灵活
35 | 核心功能全部接口化,通过简单的设置和接口注入就可以完成绝大部分日志需求。
36 |
37 |
38 | **关于项目的更多信息,包括下载、使用和注意事项,请访问[完整文档地址](https://jumeirdgroup.github.io/Android-PLog/)。**
39 |
40 | ## Contribution & Contact
41 | 如果您在使用这个库的时候遇到困难,或者有任何的反馈、建议,都可以通过GitHub Issue 功能或下面的邮箱联系我:
42 | ****
43 |
44 | ## Licence
45 | ```
46 | Copyright 2016-2017 Muyangmin
47 |
48 | Licensed under the Apache License, Version 2.0 (the "License");
49 | you may not use this file except in compliance with the License.
50 | You may obtain a copy of the License at
51 |
52 | http://www.apache.org/licenses/LICENSE-2.0
53 |
54 | Unless required by applicable law or agreed to in writing, software
55 | distributed under the License is distributed on an "AS IS" BASIS,
56 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
57 | See the License for the specific language governing permissions and
58 | limitations under the License.
59 | ```
60 |
--------------------------------------------------------------------------------
/README_EN.md:
--------------------------------------------------------------------------------
1 | #Android-PLog [](https://travis-ci.org/Muyangmin/Android-PLog)[  ](https://bintray.com/muyangmin/org.mym/Android-PLog/_latestVersion)
2 |
3 | #### [中文版文档](./README.md)
4 |
5 | ## Summary
6 | PLog is a logging library for Android, which has following features:
7 | #### Easy to print
8 | * print without tag
9 | * print without message
10 | * accept anny type of arguments
11 | * accept variable length arguments
12 | * auto formatting
13 | * print with builder style code
14 |
15 | #### Controllable output
16 | * intercept with multi dimension
17 | * customizable output decoration (style)
18 | * multi channel output
19 | * soft wrap supported
20 |
21 | #### Easy to filter
22 | * global tag (for filtering apps)
23 | * auto tag (for filtering classes)
24 | * keep stacktrace (for filtering files and/or methods)
25 | * category (for filtering developers or groups, etc)
26 |
27 | #### Dependency on demand
28 | You can use `plog-core` module for simple scenarios, but you can also import other modules for
29 | advanced usage. As you like.
30 |
31 | #### Flexible Setting/Extension
32 | You can use built-in options or just a little interface implementation to satisfy almost all
33 | your needs.
34 |
35 | PLog is a logging library for Android, follows following principle:
36 | #### Applicability
37 | > Focus on log itself, discard divider lines and other complicated decors but still keep being
38 | powerful.
39 |
40 | #### Light weight
41 | > PLog is **zero dependency**!
42 |
43 | #### Flexibility and expandability
44 | > PLog provides multi configs to fit your custom needs.
45 |
46 | #### Convention over configuration
47 | > Although PLog provides `init` method,your program will still work by using default logging
48 | configurations. And you can user `Builder` class to simply build your own config.
49 | Bonus: `recommend` method maybe help you much!
50 |
51 | ## Usage
52 | Please view [Wiki](https://github.com/Muyangmin/Android-PLog/wiki) to get usage details and advanced
53 | features.
54 |
55 | ## Features
56 | * **Jcenter support, ZERO DEPENDENCY**
57 | * **Empty method support *(useful for observe somewhere executed )***
58 | * **Log without tag/ global tag/ auto tag**
59 | * **Varargs support and auto formatting**
60 | * **JSON format support**
61 | * **Throwable format support**
62 | * **Timing log support**
63 | * **Line number and stackOffset support**
64 | * **Loggable controller with different level**
65 | * **Local file logger is available**
66 | * **Logger redirect support(useful for adapt a 3-party logging, etc)**
67 | * **Very long log content support, either auto line wrap(soft wrap)**
68 |
69 | ## Compare With Other Libs
70 | | Library Name | [Logger](https://github.com/orhanobut/logger) | [Timber](https://github.com/JakeWharton/timber) | [KLog](https://github.com/ZhaoKaiQiang/KLog) | [Android-PLog](https://github.com/Muyangmin/Android-PLog)
71 | | ------| ------ | ------ | ------ | ----- |
72 | | Star/Fork | 5.7K+/1.0K+ | 3.5K+/366 | 1.1K+/251 | **Welcome!**|
73 | | Easy To Print | √ | √ | √ | √ |
74 | | Easy To Use | √ | √ | √ | √ |
75 | | Flexible Settings | ☆ | ☆ | ☆ | ☆ |
76 | | Light Weight | ☆ | ☆☆ | ☆ | ☆☆ |
77 | | Locating in IDE | √ | × | √ | √ |
78 | | Thread Info | √ | × | × | √ |
79 | | Easy To Filter | ☆ | × | ☆ | ☆☆ |
80 | | Beautify | ☆ | × | ☆ | ☆☆ |
81 | | Controllable and Multi Output | × | ☆☆ | ☆ | ☆☆ |
82 |
83 | ## Sample Screen Shot
84 | 
85 |
86 |
87 | ## Contribution & Contact
88 | Thanks for using PLog, this library is still in active development.**Any contribution and suggestions are welcome.**
89 | Please feel free to contact me by using following way:
90 |
91 | **Email: muyangmin@foxmail.com**
92 |
93 | ## Licence
94 | ```
95 | Copyright 2016-2017 Muyangmin
96 |
97 | Licensed under the Apache License, Version 2.0 (the "License");
98 | you may not use this file except in compliance with the License.
99 | You may obtain a copy of the License at
100 |
101 | http://www.apache.org/licenses/LICENSE-2.0
102 |
103 | Unless required by applicable law or agreed to in writing, software
104 | distributed under the License is distributed on an "AS IS" BASIS,
105 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
106 | See the License for the specific language governing permissions and
107 | limitations under the License.
108 | ```
--------------------------------------------------------------------------------
/ScreenShot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JumeiRdGroup/Android-PLog/904b1957f9a7f9c64221673e8949d15effc97b64/ScreenShot.png
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | //apply plugin: 'android-apt'
3 |
4 | android {
5 | compileSdkVersion rootProject.ext.COMPILE_SDK_VERSION as int
6 | buildToolsVersion rootProject.ext.BUILD_TOOLS_VERSION as String
7 |
8 | defaultConfig {
9 | applicationId "org.mym.prettylog"
10 | minSdkVersion rootProject.ext.MIN_SDK_VERSION as int
11 | // targetSdkVersion rootProject.ext.TARGET_SDK_VERSION as int
12 | targetSdkVersion 25
13 | versionCode 1
14 | versionName VERSION_NAME
15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | packagingOptions {
24 | exclude 'LICENSE.txt'
25 | exclude 'NOTICE.txt'
26 | }
27 | lintOptions {
28 | abortOnError false
29 | }
30 | }
31 |
32 | dependencies {
33 | compile fileTree(include: ['*.jar'], dir: 'libs')
34 | compile "com.android.support:appcompat-v7:${rootProject.ext.SUPPORT_VERSION as String}"
35 | compile "com.android.support:design:${rootProject.ext.SUPPORT_VERSION as String}"
36 | compile 'org.mym.material:md-colors:1.0.0'
37 |
38 | compile('com.github.hotchemi:permissionsdispatcher:2.4.0') {
39 | exclude group: "com.android.support"
40 | }
41 | annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.4.0'
42 |
43 | compile(rootProject.ext.BUTTERKNIFE as String)
44 | annotationProcessor(rootProject.ext.BUTTERKNIFE_APT as String)
45 |
46 | compile project(':plog')
47 | compile project(':plog-printer')
48 | compile project(':plog-timing')
49 | compile project(':plog-formatter')
50 | compile project(':plog-lint-aar')
51 | // compile 'org.mym.plog:android-plog:1.7.0'
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in F:\Developer\AndroidSdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/java/org/mym/prettylog/CrashPrinter.java:
--------------------------------------------------------------------------------
1 | package org.mym.prettylog;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageInfo;
5 | import android.content.pm.PackageManager;
6 | import android.os.Build;
7 | import android.support.annotation.NonNull;
8 | import android.support.annotation.Nullable;
9 | import android.text.TextUtils;
10 | import android.text.format.Formatter;
11 | import android.util.PrintStreamPrinter;
12 |
13 | import org.mym.plog.Category;
14 | import org.mym.plog.PrintLevel;
15 | import org.mym.plog.printer.FilePrinter;
16 |
17 | import java.io.File;
18 | import java.util.Arrays;
19 |
20 | /**
21 | * This class provides a default(sample) crash file utility.
22 | * Created by muyangmin on Jan 17, 2017.
23 | *
24 | * @since 2.0.0
25 | */
26 | public class CrashPrinter extends FilePrinter {
27 |
28 | public static final String CAT_CRASH = "crash";
29 |
30 | private static volatile CrashPrinter sInstance = null;
31 | private static String sExtraInfo;
32 | private Context mApplicationContext;
33 |
34 | private CrashPrinter(Context mContext) {
35 | //Assume not null
36 | //noinspection ConstantConditions
37 | // super(getCrashFileDir(mContext).getAbsolutePath(),
38 | super(mContext, DIR_EXT_FILES + "/crash",
39 | new TimingFileNameGenerator(), 1024 * 1024L);
40 | mApplicationContext = mContext.getApplicationContext();
41 | }
42 |
43 | public static CrashPrinter getInstance(Context context) {
44 | //create a temp variable to improve performance for reading volatile field.
45 | CrashPrinter instance = sInstance;
46 | if (instance == null) {
47 | synchronized (CrashPrinter.class) {
48 | instance = sInstance;
49 | //double check here
50 | if (instance == null) {
51 | instance = new CrashPrinter(context);
52 | sInstance = instance;
53 | }
54 | }
55 | }
56 | return instance;
57 | }
58 |
59 | public static void setExtraInfo(@NonNull String extraInfo) {
60 | sExtraInfo = extraInfo;
61 | }
62 |
63 |
64 | @Override
65 | public boolean onIntercept(@PrintLevel int level, @NonNull String tag,
66 | @Nullable Category category, @NonNull String msg) {
67 | // accept only crash category!
68 | // return !CRASH.equals(category);
69 | return !(category != null && category.isSameAs(CAT_CRASH));
70 | }
71 |
72 | @Override
73 | public void print(@PrintLevel int level, @NonNull String tag, @NonNull String msg) {
74 | super.print(level, tag, msg);
75 | //Only record one crash at a time.
76 | close();
77 | }
78 |
79 | @Override
80 | protected void printFileHeader(PrintStreamPrinter ps) {
81 |
82 | //Print time and thread
83 | // SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss, z",
84 | // Locale.getDefault());
85 | // ps.println("CrashTime: " + format.format(timestamp));
86 | // ps.println("CrashThread: " + thread + ", belong to group " + thread
87 | // .getThreadGroup());
88 | if (!TextUtils.isEmpty(sExtraInfo)) {
89 | ps.println(sExtraInfo);
90 | sExtraInfo = null;
91 | }
92 |
93 | //Print device info, app info, etc.
94 | ps.println(createCrashHeaderStr(mApplicationContext));
95 |
96 | //Print throwable, the core stacktrace
97 | // ps.println(Log.getStackTraceString(throwable));
98 | }
99 |
100 | private String createCrashHeaderStr(Context context) {
101 | StringBuilder sb = new StringBuilder(1024);
102 |
103 | sb.append("Device Model: ").append(Build.MODEL).append("\n")
104 | .append("Device Brand: ").append(Build.BRAND).append("\n")
105 | .append("Device Manufacturer: ").append(Build.MANUFACTURER).append("\n")
106 | .append("OS Version: ").append(Build.VERSION.SDK_INT).append("\n")
107 | .append("OS Name: ").append(Build.VERSION.RELEASE).append("\n")
108 | .append("CPU Hardware: ").append(Build.HARDWARE).append("\n");
109 |
110 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
111 | sb.append("CPU API: ").append(Arrays.toString(Build.SUPPORTED_ABIS)).append("\n");
112 | }
113 |
114 | //NOTE: 这里虽然列出了内存和外存的freeSpace, 但在测试设备 HUAWEI FRD-AL00上两者并不相同。
115 | // 而外存的剩余大小和设置页里看到的存储空间数据一致。
116 | try {
117 | File intStore = context.getFilesDir();
118 | File extStore = context.getExternalFilesDir(null);
119 | sb.append("Internal Storage Free: ")
120 | .append(Formatter.formatFileSize(context, intStore.getFreeSpace()))
121 | .append("\n");
122 | if (extStore != null) {
123 | sb.append("External Storage Free: ")
124 | .append(Formatter.formatFileSize(context, extStore.getFreeSpace()))
125 | .append("\n");
126 | }
127 | } catch (SecurityException ignored) {
128 |
129 | }
130 |
131 | PackageInfo packageInfo = null;
132 | try {
133 | packageInfo = context.getPackageManager()
134 | .getPackageInfo(context.getPackageName(), 0);
135 | } catch (PackageManager.NameNotFoundException ignored) {
136 | //Empty
137 | }
138 | sb.append("App Version: ")
139 | .append(packageInfo == null ? "N/A" : packageInfo.versionName)
140 | .append("\n");
141 |
142 | return sb.toString();
143 | }
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/app/src/main/java/org/mym/prettylog/LintTestCase.java:
--------------------------------------------------------------------------------
1 | package org.mym.prettylog;
2 |
3 | import android.util.Log;
4 |
5 | import org.mym.plog.PLog;
6 |
7 | import java.util.Locale;
8 |
9 | /**
10 | * This source is belong to main, I want it to be just like a normal biz code,
11 | * though its actual purpose is to show and check PLog lint issues.
12 | * Created by muyangmin on Jun 07, 2017.
13 | */
14 | public final class LintTestCase {
15 |
16 | void testLintLogNotPLog() {
17 | System.out.print("This is a test code.");
18 | System.out.println("test lint not plog.");
19 |
20 | Log.d("Lint", "use android.util.Log class.");
21 |
22 | PLog.v("use PLog, this is right.");
23 | }
24 |
25 | void testLintNestedFormatInPLog() {
26 | PLog.d(String.format(Locale.US, "%s %s", "Hello", "World"));
27 |
28 | //emulate a normal formatting
29 | String result = String.format("This is %s right call.", 2);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/org/mym/prettylog/PLogApplication.java:
--------------------------------------------------------------------------------
1 | package org.mym.prettylog;
2 |
3 | import android.Manifest;
4 | import android.app.Application;
5 | import android.content.pm.PackageManager;
6 | import android.support.v4.content.ContextCompat;
7 |
8 | import org.mym.plog.DebugPrinter;
9 | import org.mym.plog.PLog;
10 | import org.mym.plog.config.PLogConfig;
11 | import org.mym.prettylog.util.CrashHandler;
12 |
13 | /**
14 | *
15 | * This class shows how to init PLog Library.
16 | *
6 | * Generated by http://cn.lipsum.com/, please visit this site for more details.
7 | *
8 | * Created by muyangmin on Sep 19, 2017.
9 | */
10 | public final class LoremIpsum {
11 | public static final String TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
12 | "Duis malesuada ligula mauris, eget consectetur dolor rhoncus in. Fusce vulputate dui" +
13 | " sit amet elit laoreet elementum. Etiam aliquet, diam vel tincidunt iaculis, odio " +
14 | "ipsum convallis nunc, id laoreet lorem odio in tellus. Suspendisse fringilla sodales" +
15 | " ligula, volutpat ornare elit placerat vitae. Cras iaculis gravida ipsum, in tempus " +
16 | "eros laoreet in. Pellentesque nisl dolor, bibendum ac vehicula at, molestie et ex. " +
17 | "In hac habitasse platea dictumst. Aliquam sem lorem, lacinia nec urna at, tempor " +
18 | "fringilla lectus. Suspendisse potenti. Ut quis faucibus quam.\n" +
19 | "\n" +
20 | "Duis a arcu non leo placerat aliquam quis sed lectus. Class aptent taciti sociosqu " +
21 | "ad litora torquent per conubia nostra, per inceptos himenaeos. Maecenas non ex leo. " +
22 | "Sed luctus at diam at pulvinar. Aenean ut condimentum justo, sit amet luctus est. " +
23 | "Sed nisi purus, accumsan eu posuere id, fermentum sit amet diam. Praesent interdum " +
24 | "felis pretium suscipit placerat. Aenean a vehicula dui. Orci varius natoque " +
25 | "penatibus et magnis dis parturient montes, nascetur ridiculus mus.\n" +
26 | "\n" +
27 | "Fusce dignissim ut erat eget eleifend. Donec tristique nunc diam, quis blandit lacus" +
28 | " ornare at. Duis rhoncus nulla purus, nec gravida lorem efficitur sit amet. Sed nec " +
29 | "accumsan velit, vel pretium justo. Nullam dignissim ultricies nisl. Maecenas " +
30 | "malesuada vitae eros vitae tincidunt. Cras sed augue dictum, scelerisque orci sit " +
31 | "amet, vulputate tortor. In laoreet mauris lorem, in laoreet dui placerat vitae. " +
32 | "Morbi rutrum purus neque, sed consequat dui laoreet eget. Curabitur mattis et massa " +
33 | "nec porttitor. Donec pulvinar a quam at mattis. Phasellus feugiat est eget aliquet " +
34 | "pretium. Donec orci elit, lacinia et ex in, semper ultricies nisl.\n" +
35 | "\n" +
36 | "Mauris vel dapibus purus. Duis non semper turpis. Cras aliquam posuere lacus, eget " +
37 | "posuere enim tempus quis. Suspendisse condimentum, tellus vitae cursus ullamcorper, " +
38 | "lorem sapien congue lectus, in auctor orci lorem nec erat. Aenean viverra risus " +
39 | "tellus, gravida maximus libero ullamcorper ac. Sed id orci id eros consequat " +
40 | "molestie. Donec iaculis erat eget aliquam congue. Proin fringilla, lectus maximus " +
41 | "rutrum dapibus, diam lorem volutpat diam, in condimentum erat lorem ut ante.\n" +
42 | "\n" +
43 | "Sed dui tortor, interdum sit amet bibendum sed, maximus sed ante. Aliquam nec " +
44 | "scelerisque leo, in condimentum mauris. Lorem ipsum dolor sit amet, consectetur " +
45 | "adipiscing elit. Etiam congue elit in tincidunt aliquet. Vivamus erat felis, laoreet" +
46 | " sed tellus at, egestas lacinia odio. Pellentesque vitae facilisis tellus. Donec " +
47 | "porta arcu non dui lacinia, non vulputate leo eleifend. Mauris sit amet mauris " +
48 | "commodo nisl finibus luctus. Nullam vitae justo ac enim semper tempor.\n" +
49 | "\n" +
50 | "Suspendisse egestas dictum tellus, a dapibus leo suscipit eu. Quisque commodo tempor" +
51 | " blandit. Ut vehicula quis sem ut tempor. Donec posuere pellentesque vulputate. In " +
52 | "eu ex eget neque lobortis vehicula vitae eu turpis. Suspendisse potenti. Nam " +
53 | "porttitor efficitur enim, id vehicula risus sodales ac. Ut et enim nec justo " +
54 | "consequat gravida vitae vel risus. Praesent hendrerit consectetur eros eget ornare. " +
55 | "Proin pharetra ante vitae tellus ultricies commodo. Aenean at libero non risus " +
56 | "pellentesque porta id aliquet quam. Curabitur in dolor imperdiet, lobortis dolor eu," +
57 | " tempus elit. Sed metus sem, gravida at laoreet eget, aliquam nec metus. Sed rhoncus" +
58 | " turpis eu elit ornare, ut mollis libero luctus. Fusce pulvinar magna volutpat augue" +
59 | " mattis, in accumsan lorem ultrices.\n" +
60 | "\n" +
61 | "Donec mollis venenatis tellus. Phasellus quis viverra erat. Etiam ultrices ipsum a " +
62 | "dignissim finibus. Cras quis ligula ullamcorper, tempus erat eu, commodo nisi. Class" +
63 | " aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos " +
64 | "himenaeos. Cras est tortor, sodales gravida lectus nec, lacinia commodo tellus. Nam " +
65 | "dapibus sodales ipsum in porta. Donec eget diam placerat, luctus ligula ac, cursus " +
66 | "justo.\n" +
67 | "\n" +
68 | "Vestibulum est tortor, tristique nec semper ac, mollis vitae quam. Proin efficitur " +
69 | "felis sed massa vulputate, quis maximus urna viverra. Etiam molestie blandit ante, " +
70 | "in laoreet tortor fringilla in. Phasellus in ante diam. Suspendisse mi mi, luctus " +
71 | "quis mauris non, venenatis viverra quam. Lorem ipsum dolor sit amet, consectetur " +
72 | "adipiscing elit. Mauris risus arcu, semper in luctus ut, interdum eget mauris. Donec" +
73 | " malesuada orci mi, nec congue mi luctus quis. Vivamus placerat mi mauris, ut " +
74 | "laoreet diam scelerisque ac. Aenean et sem id dolor dictum blandit. Duis at lobortis" +
75 | " nibh. Vivamus cursus risus dui, sed lacinia erat commodo iaculis.\n" +
76 | "\n" +
77 | "Aenean gravida aliquet arcu, eu ultrices est gravida et. Orci varius natoque " +
78 | "penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec ut augue " +
79 | "consequat, scelerisque tellus sed, eleifend odio. Sed nec vehicula erat. Mauris nibh" +
80 | " dolor, ultrices vel risus at, congue sollicitudin sapien. Integer eu mattis augue. " +
81 | "Maecenas vitae venenatis lorem, sit amet finibus justo. Cras lacinia pellentesque " +
82 | "lorem, eget imperdiet massa rhoncus ac. Pellentesque eu sem at libero consequat " +
83 | "hendrerit quis sit amet quam. Curabitur convallis pretium molestie. Nunc placerat " +
84 | "elit eu dictum euismod. Suspendisse potenti.\n" +
85 | "\n" +
86 | "Nulla pellentesque nibh felis, sit amet ornare tortor pulvinar ac. Praesent id diam " +
87 | "ut erat fermentum commodo. Nam eu feugiat ligula, ac vulputate leo. Integer justo " +
88 | "quam, ultricies sed metus sit amet, pretium congue felis. Cras iaculis blandit " +
89 | "scelerisque. Proin faucibus nulla sem, ac hendrerit tellus iaculis sit amet. Nunc " +
90 | "vulputate pellentesque leo vitae dapibus. Fusce lacus mi, efficitur id diam sit " +
91 | "amet, mollis dictum lacus. Sed nec arcu a ante tempor dapibus. Duis ultrices odio at" +
92 | " erat tempus cursus.\n" +
93 | "\n" +
94 | "Sed rhoncus, arcu non porttitor feugiat, sem nibh ullamcorper neque, id rutrum leo " +
95 | "purus imperdiet velit. In hac habitasse platea dictumst. Etiam semper a purus ac " +
96 | "tincidunt. Mauris vulputate, velit vel ultrices ullamcorper, libero arcu ultrices " +
97 | "sapien, quis iaculis justo massa at neque. Praesent pellentesque tristique metus, " +
98 | "vel dictum odio. Cras vitae commodo massa, quis laoreet justo. Vestibulum fermentum " +
99 | "interdum diam non cursus. Proin quis elementum ante, id commodo nisl. Class aptent " +
100 | "taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. " +
101 | "Maecenas quis ante rutrum, condimentum nisi eget, placerat lacus. Donec a urna eu " +
102 | "justo maximus dictum. Praesent at vulputate orci. Quisque sit amet pellentesque diam" +
103 | ".\n" +
104 | "\n" +
105 | "Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos " +
106 | "himenaeos. Duis at auctor massa. Mauris iaculis iaculis ante, molestie pharetra urna" +
107 | " venenatis ac. Donec ac quam sed eros varius lacinia ornare ac nisi. Class aptent " +
108 | "taciti sociosqu ad litora torquent per.";
109 | }
110 |
--------------------------------------------------------------------------------
/app/src/main/java/org/mym/prettylog/data/Singleton.java:
--------------------------------------------------------------------------------
1 | package org.mym.prettylog.data;
2 |
3 | /**
4 | * This is a test case class for cycling reference sample: singleton.
5 | *
6 | * Without recursive depth handling, recursively access this object will lead to
7 | * StackOverflowError.
8 | *
11 | * This class is a sample for how to wrap the log library for extensibility or replace library.
12 | * But I still recommend you to continuously use PLog library :)
13 | * This file is published on GitHub Gist; you can find it in my public gist list:
14 | * https://gist.github.com/Muyangmin
15 | *
35 | Using this library can lead you away from duplicate and boring works, e.g. thinking of log tags,
36 | writing object params, and filtering large logs from which logged by colleagues.
37 | ]]>
38 | Library Home
39 |
42 | Library version
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
16 |
17 |
18 |
24 |
25 |
33 |
34 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.3.3'
9 | // classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
10 | classpath 'com.novoda:bintray-release:0.4.0'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | jcenter()
20 | }
21 | }
22 |
23 | //Define all module compile version here.
24 | ext {
25 | //BUILD VERSIONS
26 | MIN_SDK_VERSION = 14
27 | TARGET_SDK_VERSION = 25
28 | COMPILE_SDK_VERSION = 25
29 | BUILD_TOOLS_VERSION = "25.0.3"
30 |
31 | //DEV DEPENDENCIES
32 | SUPPORT_VERSION = "25.3.1"
33 | BUTTERKNIFE_VERSION = "8.4.0"
34 |
35 | APPCOMPAT_V7 = "com.android.support:appcompat-v7:${SUPPORT_VERSION}"
36 | SUPPORT_ANNOTATION = "com.android.support:support-annotations:${SUPPORT_VERSION}"
37 |
38 | BUTTERKNIFE = "com.jakewharton:butterknife:${BUTTERKNIFE_VERSION}"
39 | BUTTERKNIFE_APT = "com.jakewharton:butterknife-compiler:${BUTTERKNIFE_VERSION}"
40 |
41 | }
42 |
43 | task clean(type: Delete) {
44 | delete rootProject.buildDir
45 | }
46 |
47 | //tasks.findByPath(":library:androidJavadocs").enabled = false
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | #Add jcenter upload support
21 | VERSION_NAME=2.0.0
22 | BINTRAY_REPO=org.mym
23 | GROUP=org.mym.plog
24 | POM_NAME=Android-PLog
25 | POM_PACKAGING=aar
26 |
27 | POM_DESCRIPTION=An log library focused on high extensibility, powerful enough but easy to use.
28 |
29 | #Project addr, using github here.
30 | POM_URL=https://github.com/Muyangmin/Android-PLog
31 |
32 | #Licence
33 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JumeiRdGroup/Android-PLog/904b1957f9a7f9c64221673e8949d15effc97b64/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Mar 04 15:17:59 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/plog-formatter/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/plog-formatter/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'bintray-release'
3 |
4 | android {
5 | compileSdkVersion rootProject.ext.COMPILE_SDK_VERSION as int
6 | buildToolsVersion rootProject.ext.BUILD_TOOLS_VERSION as String
7 |
8 | defaultConfig {
9 | minSdkVersion rootProject.ext.MIN_SDK_VERSION as int
10 | targetSdkVersion rootProject.ext.TARGET_SDK_VERSION as int
11 | }
12 |
13 | packagingOptions {
14 | exclude 'LICENSE.txt'
15 | exclude 'NOTICE.txt'
16 | }
17 | lintOptions {
18 | abortOnError false
19 | }
20 | }
21 |
22 | dependencies {
23 | // compile 'org.mym.plog:android-plog:${VERSION_NAME}'
24 |
25 | //For develop only
26 | provided project(':plog')
27 | provided "com.android.support:support-annotations:${rootProject.ext.SUPPORT_VERSION as String}"
28 | }
29 |
30 | publish {
31 | artifactId = 'plog-formatter'
32 | repoName = BINTRAY_REPO
33 | // userOrg =
34 | groupId = GROUP
35 | uploadName = POM_NAME
36 | publishVersion = VERSION_NAME
37 | desc = POM_DESCRIPTION
38 | website = POM_URL
39 | licences = POM_LICENCE_NAME
40 | }
--------------------------------------------------------------------------------
/plog-formatter/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/plog-formatter/src/main/java/org/mym/plog/formatter/DefaultFormatter.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog.formatter;
2 |
3 | import android.support.annotation.IntDef;
4 | import android.support.annotation.NonNull;
5 | import android.text.TextUtils;
6 |
7 | import org.json.JSONArray;
8 | import org.json.JSONObject;
9 | import org.mym.plog.Formatter;
10 |
11 | import java.lang.annotation.ElementType;
12 | import java.lang.annotation.Retention;
13 | import java.lang.annotation.RetentionPolicy;
14 | import java.lang.annotation.Target;
15 | import java.util.ArrayList;
16 | import java.util.Collections;
17 | import java.util.List;
18 |
19 | /**
20 | * Default implementation for {@link Formatter}, provide all supported types.
21 | *
22 | * @since 1.0.0
23 | */
24 | @SuppressWarnings("WeakerAccess")
25 | public class DefaultFormatter implements Formatter {
26 |
27 | public static final int FLAG_FMT_JSON = 1 << 1;
28 | public static final int FLAG_FMT_POJO = 1 << 2;
29 | public static final int FLAG_FMT_THROWABLE = 1 << 3;
30 | private int mFormatFlag;
31 | /**
32 | * Use a collection to save implementations to avoid redundant implementation.
33 | */
34 | private List mFormatterImpls = new ArrayList<>();
35 |
36 | /**
37 | * Create a formatter with all supported type format enabled.
38 | */
39 | public DefaultFormatter() {
40 | this(true);
41 | }
42 |
43 | /**
44 | * Create a formatter which only support specified types.
45 | *
46 | * @param enabledTypes types to format.
47 | */
48 | public DefaultFormatter(@FormatFlag int... enabledTypes) {
49 | this(false, enabledTypes);
50 | }
51 |
52 | private DefaultFormatter(boolean enableAll, @FormatFlag int... enabledTypes) {
53 | if (enableAll) {
54 | mFormatFlag = ~0;
55 | } else {
56 | for (int enabledType : enabledTypes) {
57 | mFormatFlag |= enabledType;
58 | }
59 | }
60 | initFormatter();
61 | }
62 |
63 | /**
64 | * concat array content to string using a consistent format.
65 | */
66 | /*package*/
67 | static String arrayToString(@NonNull Object[] params) {
68 | StringBuilder sb = new StringBuilder("");
69 | for (int i = 0; i < params.length; i++) {
70 | sb.append("param[")
71 | .append(i)
72 | .append("]=")
73 | .append(params[i])
74 | .append("\n")
75 | ;
76 | }
77 | return sb.toString();
78 | }
79 |
80 | /**
81 | * Initialize formatter map using type flag passed in constructor.
82 | */
83 | private void initFormatter() {
84 | if (isEnabled(FLAG_FMT_JSON)) {
85 | mFormatterImpls.add(new FormatterImpl(FormatterImpl.PRIORITY_NORMAL,
86 | new JSONFormatter(), FLAG_FMT_JSON, new Class[]{
87 | JSONObject.class, JSONArray.class
88 | }));
89 | }
90 | if (isEnabled(FLAG_FMT_POJO)) {
91 | //NOTICE: ensure pojo formatter is the lowest priority!
92 | mFormatterImpls.add(new FormatterImpl(FormatterImpl.PRIORITY_LOW,
93 | new ObjectFormatter(), FLAG_FMT_POJO, new Class[]{
94 | Object.class
95 | }));
96 | }
97 | if (isEnabled(FLAG_FMT_THROWABLE)) {
98 | mFormatterImpls.add(new FormatterImpl(FormatterImpl.PRIORITY_NORMAL,
99 | new ThrowableFormatter(), FLAG_FMT_THROWABLE, new Class[]{
100 | Throwable.class
101 | }));
102 | }
103 | //Ensure all types are sorted by priority
104 | Collections.sort(mFormatterImpls);
105 | }
106 |
107 | private boolean isEnabled(@FormatFlag int flag) {
108 | return (mFormatFlag & flag) != 0;
109 | }
110 |
111 | @Override
112 | public String format(String msg, Object... params) throws Exception {
113 | if (params == null || params.length == 0) {
114 | return msg;
115 | }
116 | Object[] formattedParam = new Object[params.length];
117 | for (int i = 0; i < params.length; i++) {
118 | Object param = params[i];
119 | boolean formatted = false;
120 | for (FormatterImpl impl : mFormatterImpls) {
121 | if (isFormatterAvailable(impl.typeFlag, param, impl.supportedClass)){
122 | formattedParam[i] = impl.formatter.format(msg, param);
123 | formatted = true;
124 | break;
125 | }
126 | }
127 | if (!formatted) {
128 | formattedParam[i] = param;
129 | }
130 | }
131 | // return String.format(msg, (Object[]) formattedParam);
132 | String formatResult;
133 | if (!TextUtils.isEmpty(msg)) {
134 | formatResult = String.format(msg, (Object[]) formattedParam);
135 | } else if (formattedParam.length == 1) {
136 | formatResult = formattedParam[0] == null ? null : formattedParam[0].toString();
137 | } else {
138 | formatResult = arrayToString(formattedParam);
139 | }
140 | return formatResult;
141 | }
142 |
143 | private boolean isFormatterAvailable(@FormatFlag int flag, @NonNull Object param,
144 | @NonNull Class... types) throws Exception {
145 | //If this formatter is disabled at all, it is always unavailable
146 | if (!isEnabled(flag)) {
147 | return false;
148 | }
149 | Class> paramClz = param.getClass();
150 | //Primitive type is not allowed to format, since we may using %d, %f, and other format.
151 | if (isPrimitiveWrapperClass(paramClz)) {
152 | return false;
153 | }
154 | for (Class> clz : types) {
155 | //if clz is equal or super class, the format operation is safe.
156 | if (clz.isAssignableFrom(paramClz)) {
157 | return true;
158 | }
159 | }
160 | //all formatter in sparse array iterated, but still not found.
161 | return false;
162 | }
163 |
164 | /**
165 | * Determine whether param class is a standard wrapper class of primitive classes.
166 | * NOTE that class in Object[] would never be primitive class, but can be wrapper class.see:
167 | * http://stackoverflow.com/questions/709961/determining-if-an-object-is-of-primitive-type.
168 | *
169 | * @return true if is wrapper of primitive class, false otherwise.
170 | */
171 | private boolean isPrimitiveWrapperClass(@NonNull Class> clz) {
172 | return clz.equals(Boolean.class) || clz.equals(Byte.class)
173 | || clz.equals(Short.class) || clz.equals(Integer.class)
174 | || clz.equals(Long.class) || clz.equals(Float.class)
175 | || clz.equals(Double.class);
176 | }
177 |
178 | @Retention(RetentionPolicy.SOURCE)
179 | @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
180 | @IntDef(flag = true, value = {FLAG_FMT_JSON, FLAG_FMT_POJO, FLAG_FMT_THROWABLE})
181 | public @interface FormatFlag {
182 |
183 | }
184 |
185 | private class FormatterImpl implements Comparable {
186 |
187 | private static final int PRIORITY_LOW = 3;
188 | private static final int PRIORITY_NORMAL = 2;
189 | private static final int PRIORITY_HIGH = 1;
190 |
191 | /**
192 | * Priority to decide use which formatter is preferred.
193 | */
194 | private int priority;
195 |
196 | /**
197 | * the real implementation.
198 | */
199 | private Formatter formatter;
200 |
201 | /**
202 | * associated format flag.
203 | */
204 | @FormatFlag
205 | private int typeFlag;
206 |
207 | /**
208 | * Supported classes.
209 | */
210 | private Class>[] supportedClass;
211 |
212 | public FormatterImpl(int priority, @NonNull Formatter formatter, int typeFlag,
213 | @NonNull Class>[] supportedClass) {
214 | this.priority = priority;
215 | this.formatter = formatter;
216 | this.typeFlag = typeFlag;
217 | this.supportedClass = supportedClass;
218 | }
219 |
220 | @Override
221 | public int compareTo(@NonNull FormatterImpl o) {
222 | return Integer.valueOf(priority).compareTo(o.priority);
223 | }
224 | }
225 |
226 | }
227 |
--------------------------------------------------------------------------------
/plog-formatter/src/main/java/org/mym/plog/formatter/JSONFormatter.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog.formatter;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONObject;
5 | import org.mym.plog.Formatter;
6 |
7 | /**
8 | * Format standard JSON/JSONArray.
9 | *
10 | * @since 2.0.0
11 | */
12 | public class JSONFormatter implements Formatter {
13 |
14 | private static final int INTENT_SPACES = 4;
15 |
16 | @Override
17 | public String format(String msg, Object... params) throws Exception {
18 | // if (msg == null || msg.length() < 2) { // at lease {} or [], so 1 char is absolutely
19 | // wrong.
20 | // return msg;
21 | // }
22 | // //Guess JSONObject
23 | // if (msg.startsWith("{") && msg.endsWith("}")) {
24 | // //If not a json then JSONException is thrown, do not worry
25 | // JSONObject jsonObject = new JSONObject(msg);
26 | // msg = jsonObject.toString(getIntentSpaces());
27 | // }
28 | // //Guess JSONArray
29 | // else if (msg.startsWith("[") && msg.endsWith("]")) {
30 | // JSONArray jsonArray = new JSONArray(msg);
31 | // msg = jsonArray.toString(getIntentSpaces());
32 | // }
33 | if (params != null && params.length >= 1) {
34 | //ONLY recognize the first param.
35 | Object obj = params[0];
36 | if (obj instanceof JSONObject) {
37 | msg = ((JSONObject) obj).toString(INTENT_SPACES);
38 | } else if (obj instanceof JSONArray) {
39 | msg = ((JSONArray) obj).toString(INTENT_SPACES);
40 | }
41 | }
42 | return msg;
43 | }
44 | //
45 | // @Override
46 | // public boolean isPreWrappedFormat() {
47 | // return true;
48 | // }
49 |
50 | /**
51 | * Indicate how many spaces should be used to intent.
52 | *
53 | * @see JSONObject#toString(int)
54 | * @see JSONArray#toString(int)
55 | */
56 | protected int getIntentSpaces() {
57 | return INTENT_SPACES;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/plog-formatter/src/main/java/org/mym/plog/formatter/ObjectFormatter.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog.formatter;
2 |
3 | import org.mym.plog.Formatter;
4 | import org.mym.plog.PLog;
5 | import org.mym.plog.formatter.util.ObjectUtil;
6 |
7 | /**
8 | * Recursively format all type of objects.
9 | *
10 | * @author muyangmin
11 | * @since 1.5.0
12 | */
13 | public class ObjectFormatter implements Formatter {
14 |
15 | /**
16 | * @param msg IGNORED!
17 | */
18 | @Override
19 | public String format(String msg, Object... params) throws Exception {
20 | if (params.length < 1) {
21 | throw new IllegalArgumentException("No objects need to be formatted!");
22 | }
23 |
24 | int maxRecursiveDepth = PLog.getCurrentConfig().getMaxRecursiveDepth();
25 |
26 | if (params.length == 1) {
27 | return ObjectUtil.objectToString(params[0], false, maxRecursiveDepth);
28 | }
29 | StringBuilder sb = new StringBuilder();
30 | for (int i = 0; i < params.length; i++) {
31 | sb.append("param[")
32 | .append(i)
33 | .append("]=")
34 | .append(ObjectUtil.objectToString(params[i], false, maxRecursiveDepth))
35 | .append("\n")
36 | ;
37 | }
38 | return sb.toString();
39 | }
40 | //
41 | // @Override
42 | // public boolean isPreWrappedFormat() {
43 | // return false;
44 | // }
45 | }
46 |
--------------------------------------------------------------------------------
/plog-formatter/src/main/java/org/mym/plog/formatter/ThrowableFormatter.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog.formatter;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Log;
5 |
6 | import org.mym.plog.Formatter;
7 |
8 | /**
9 | * Format Throwable into standard trace strings.
10 | *
11 | * @since 1.5.0
12 | */
13 | public class ThrowableFormatter implements Formatter {
14 |
15 | /**
16 | * @param msg msg to be formatted at the 1st line. This is likely
17 | * with{@link Log#e(String, String, Throwable)}, etc.
18 | * @param params params to format msg. the 1st argument must be a Throwable instance, and others
19 | * are ignored.
20 | * @throws IllegalArgumentException when argument do not contain a Throwable at 1st position.
21 | */
22 | @Override
23 | public String format(String msg, Object... params) throws Exception {
24 | if (TextUtils.isEmpty(msg) && params.length < 1 || (!(params[0] instanceof Throwable))) {
25 | throw new IllegalArgumentException("Throwable argument not found!");
26 | }
27 | Throwable tr = (Throwable) params[0];
28 | String stackTrace = Log.getStackTraceString(tr);
29 | if (TextUtils.isEmpty(msg)) {
30 | return stackTrace;
31 | } else {
32 | return msg + "\n" + stackTrace;
33 | }
34 | }
35 | //
36 | // @Override
37 | // public boolean isPreWrappedFormat() {
38 | // return true;
39 | // }
40 | }
41 |
--------------------------------------------------------------------------------
/plog-formatter/src/main/java/org/mym/plog/formatter/util/ObjectUtil.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog.formatter.util;
2 |
3 | import android.text.TextUtils;
4 |
5 | import org.json.JSONArray;
6 | import org.json.JSONException;
7 | import org.json.JSONObject;
8 | import org.mym.plog.BuildConfig;
9 |
10 | import java.lang.reflect.Field;
11 | import java.util.AbstractCollection;
12 | import java.util.AbstractList;
13 | import java.util.AbstractMap;
14 | import java.util.Iterator;
15 | import java.util.List;
16 | import java.util.Map;
17 |
18 | /**
19 | *
20 | * This class provides method to format normal objects to string.
21 | *
22 | * Created by muyangmin on 9/6/16.
23 | *
24 | * @author muyangmin
25 | * @since V1.3.0
26 | */
27 | public class ObjectUtil {
28 |
29 | private static final String STR_OBJECT_EMPTY = "[null object]";
30 | /**
31 | * a-zA-Z\. matches for class name, while 0-9a-fA-F matches hashcode.
32 | * Use $ to match inner classes.
33 | */
34 | //Sample complicated class name: AnimalFactory$CatFactory2$CatImpl
35 | private static final String REGEX_STANDARD_HASHCODE = "[a-zA-Z0-9\\.\\$]+@[0-9a-fA-F]+";
36 |
37 | /**
38 | * intent spaces for json format.
39 | * @see JSONObject#toString(int)
40 | * @see JSONArray#toString(int)
41 | */
42 | private static final int JSON_INDENT_SPACE = 4;
43 |
44 | /**
45 | * Format an object to a well-formed string.
46 | *
47 | * If the class of target object overrides {@link Object#toString()}, then this method
48 | * simply returns the return value of that method. Otherwise it would try to access
49 | * declared fields and append after class name and hashcode. In this case the final
50 | * result maybe like this:
51 | *
52 | * org.mym.plog.Driver@23ac3874[mName=Tank, mAge=199, mCar=Benz S400]
53 | *
54 | *
55 | *
56 | * @param obj the object to log.
57 | * @param usingSimpleClassNamePrefix Decide if parsed class using simple name;
58 | * this is useful for decrease collection string length
59 | * and large POJOs.
60 | * @param allowRecursiveDepth if non-positive, recursive format is disabled.
61 | * @return formatted string
62 | */
63 | public static String objectToString(Object obj, boolean usingSimpleClassNamePrefix,
64 | int allowRecursiveDepth) {
65 | if (obj == null) {
66 | return STR_OBJECT_EMPTY;
67 | }
68 | String result = obj.toString();
69 | if (result.matches(REGEX_STANDARD_HASHCODE)) {
70 | result = allowRecursiveDepth <= 0 ? obj.toString()
71 | : parseObject(obj, usingSimpleClassNamePrefix, --allowRecursiveDepth);
72 | } else if (obj instanceof AbstractList && ((AbstractList) obj).size() > 0) {
73 | //Assume all objects in list are same type, that is, **TYPE SAFE**
74 | //A IMPORTANT assume logic is, if (and only if) the first object is not well-formed,
75 | // the whole collection should be re-formatted.
76 | Object flagObject = ((AbstractList) obj).get(0);
77 | //If this is not formatted class, recursively format
78 | if (flagObject.toString().matches(REGEX_STANDARD_HASHCODE)) {
79 | result = formatNormalList((List>) obj, usingSimpleClassNamePrefix,
80 | allowRecursiveDepth);
81 | }
82 | } else if (obj instanceof AbstractMap && ((AbstractMap) obj).size() > 0) {
83 | //see docs on list branch
84 | Object flagKey = ((AbstractMap) obj).keySet().iterator().next();
85 | Object flagValue = ((AbstractMap) obj).get(flagKey);
86 | //If this is not formatted class, recursively format
87 | if (flagValue.toString().matches(REGEX_STANDARD_HASHCODE)) {
88 | result = formatMap((Map, ?>) obj, usingSimpleClassNamePrefix,
89 | allowRecursiveDepth);
90 | }
91 | } else if (!TextUtils.isEmpty(result)) {
92 | //Guess JSONObject, use double check to improve accuracy
93 | if ( result.startsWith("{") && result.endsWith("}")){
94 | try{
95 | JSONObject jsonObject = new JSONObject(result);
96 | String fmtJson = jsonObject.toString(JSON_INDENT_SPACE);
97 | if (!TextUtils.isEmpty(fmtJson)){
98 | result = fmtJson;
99 | }
100 | }catch (JSONException ignored){
101 | //This is PLog's BuildConfig; so this was automatically disabled in jcenter release
102 | if (BuildConfig.DEBUG){
103 | ignored.printStackTrace();
104 | }
105 | }
106 | }
107 | //Guess JSONArray
108 | else if (result.startsWith("[") && result.endsWith("]") && result.indexOf('{') != -1
109 | && result.indexOf('}') != -1) {
110 | try{
111 | JSONArray jsonArray = new JSONArray(result);
112 | String fmtJson = jsonArray.toString(JSON_INDENT_SPACE);
113 | if (!TextUtils.isEmpty(fmtJson)){
114 | result = fmtJson;
115 | }
116 | }catch (JSONException ignored){
117 | //This is PLog's BuildConfig; so this was automatically disabled in jcenter release
118 | if (BuildConfig.DEBUG){
119 | ignored.printStackTrace();
120 | }
121 | }
122 | }
123 | }
124 | return result;
125 | }
126 |
127 | /**
128 | * This method is a copy of {@link AbstractCollection#toString()} but changes its parameter!
129 | * @see #objectToString(Object, boolean, int)
130 | */
131 | private static String formatNormalList(List list, boolean usingSimpleClassNamePrefix,
132 | final int allowRecursiveDepth) {
133 | Iterator it = list.iterator();
134 | if (!it.hasNext())
135 | return "[]";
136 |
137 | StringBuilder sb = new StringBuilder();
138 | sb.append('[');
139 | for (; ; ) {
140 | E e = it.next();
141 | sb.append(e == list ? "(this Collection)" : objectToString(e,
142 | usingSimpleClassNamePrefix, allowRecursiveDepth));
143 | if (!it.hasNext())
144 | return sb.append(']').toString();
145 | sb.append(',').append(' ');
146 | }
147 | }
148 |
149 | private static String formatMap(Map map, boolean usingSimpleClassNamePrefix,
150 | final int allowRecursiveDepth) {
151 |
152 | Iterator> i = map.entrySet().iterator();
153 | if (!i.hasNext())
154 | return "{}";
155 |
156 | StringBuilder sb = new StringBuilder();
157 | sb.append('{');
158 | for (; ; ) {
159 | Map.Entry e = i.next();
160 | K key = e.getKey();
161 | V value = e.getValue();
162 | sb.append(key == map ? "(this Map)" : objectToString(key,
163 | usingSimpleClassNamePrefix, allowRecursiveDepth));
164 | sb.append('=');
165 | sb.append(value == map ? "(this Map)" : objectToString(value,
166 | usingSimpleClassNamePrefix, allowRecursiveDepth));
167 | if (!i.hasNext())
168 | return sb.append('}').toString();
169 | sb.append(',').append(' ');
170 | }
171 | }
172 |
173 | /**
174 | * @see #objectToString(Object, boolean, int)
175 | */
176 | private static String parseObject(Object obj, boolean usingSimpleClassNamePrefix,
177 | final int allowRecursiveDepth) {
178 | // declare concat symbols here to define final format.
179 | // Current format is [a=1, b=2]
180 | final String FIELD_CONCAT_SYMBOL = ", ";
181 | final String FIELD_VALUE_SYMBOL = "=";
182 | final String OBJECT_VALUE_SYMBOL_LEFT = "[";
183 | final String OBJECT_VALUE_SYMBOL_RIGHT = "]";
184 | try {
185 | Class clz = obj.getClass();
186 | Field[] fields = clz.getDeclaredFields();
187 | StringBuilder sb = new StringBuilder();
188 | // sb.append(clz.getSimpleName())
189 | // .append("@")
190 | // .append(obj.hashCode())
191 | if (usingSimpleClassNamePrefix) {
192 | sb.append(clz.getSimpleName());
193 | } else {
194 | sb.append(clz.getName())
195 | .append("@")
196 | .append(Integer.toHexString(obj.hashCode()));
197 | }
198 | //Add [ symbol
199 | sb.append(OBJECT_VALUE_SYMBOL_LEFT);
200 | boolean appended = false;
201 | for (Field f : fields) {
202 | boolean isAccessible = f.isAccessible();
203 | f.setAccessible(true);
204 | if (f.isAccessible()) {
205 | // age=18,
206 | sb.append(f.getName()).append(FIELD_VALUE_SYMBOL);
207 | //In internal fields, do not use full class name to avoid too long log in
208 | // arrays and collections, etc.
209 | sb.append(objectToString(f.get(obj), true, allowRecursiveDepth));
210 | sb.append(FIELD_CONCAT_SYMBOL);
211 | appended = true;
212 | }
213 | //Restore accessibility
214 | f.setAccessible(isAccessible);
215 | }
216 | //delete last ", "
217 | if (appended) {
218 | sb.delete(sb.length() - FIELD_CONCAT_SYMBOL.length(), sb.length());
219 | }
220 | sb.append(OBJECT_VALUE_SYMBOL_RIGHT);
221 | return sb.toString();
222 | } catch (Exception e) {
223 | e.printStackTrace();
224 | return null;
225 | }
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/plog-lint-aar/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/plog-lint-aar/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'bintray-release'
3 |
4 | android {
5 | compileSdkVersion rootProject.ext.COMPILE_SDK_VERSION as int
6 | buildToolsVersion rootProject.ext.BUILD_TOOLS_VERSION as String
7 |
8 | defaultConfig {
9 | minSdkVersion rootProject.ext.MIN_SDK_VERSION as int
10 | targetSdkVersion rootProject.ext.TARGET_SDK_VERSION as int
11 | }
12 |
13 | packagingOptions {
14 | exclude 'LICENSE.txt'
15 | exclude 'NOTICE.txt'
16 | }
17 |
18 | lintOptions {
19 | abortOnError false
20 | }
21 | }
22 |
23 | /*
24 | * rules for including "lint.jar" in aar
25 | */
26 | configurations {
27 | lintJarImport
28 | }
29 |
30 | dependencies {
31 | lintJarImport project(path: ":plog-lint", configuration: "lintJarOutput")
32 | }
33 |
34 | task copyLintJar(type: Copy) {
35 | from(configurations.lintJarImport) {
36 | rename {
37 | String fileName ->
38 | 'lint.jar'
39 | }
40 | }
41 | into 'build/intermediates/lint/'
42 | }
43 |
44 | project.afterEvaluate {
45 | def compileLintTask = project.tasks.find { it.name == 'compileLint' }
46 | compileLintTask.dependsOn(copyLintJar)
47 | }
48 |
49 | publish {
50 | artifactId = 'plog-lint'
51 | repoName = BINTRAY_REPO
52 | // userOrg =
53 | groupId = GROUP
54 | uploadName = POM_NAME
55 | publishVersion = VERSION_NAME
56 | desc = POM_DESCRIPTION
57 | website = POM_URL
58 | licences = POM_LICENCE_NAME
59 | }
--------------------------------------------------------------------------------
/plog-lint-aar/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/muyangmin/Android/android-sdk-macosx/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/plog-lint-aar/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/plog-lint/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/plog-lint/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 |
3 | //sourceCompatibility = "1.7"
4 | //targetCompatibility = "1.7"
5 |
6 | configurations {
7 | lintJarOutput
8 | }
9 |
10 | jar {
11 | manifest {
12 | attributes('Lint-Registry': 'org.mym.plog.lint.IssueRegistry')
13 | }
14 | }
15 |
16 | dependencies {
17 | compile "com.android.tools.lint:lint-api:${rootProject.ext.SUPPORT_VERSION}"
18 | compile "com.android.tools.lint:lint-checks:${rootProject.ext.SUPPORT_VERSION}"
19 |
20 | lintJarOutput files(jar)
21 | }
22 |
23 | defaultTasks 'assemble'
--------------------------------------------------------------------------------
/plog-lint/src/main/java/org/mym/plog/lint/IssueRegistry.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog.lint;
2 |
3 | import com.android.tools.lint.detector.api.Issue;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * This is automatically called by lint tool.
9 | */
10 | @SuppressWarnings("unused")
11 | public class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
12 | @Override
13 | public List getIssues() {
14 | System.out.println("Called customized issue registry!");
15 | return LoggingIssueDetector.getIssues();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/plog-lint/src/main/java/org/mym/plog/lint/LoggingIssueDetector.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog.lint;
2 |
3 | import com.android.tools.lint.detector.api.Category;
4 | import com.android.tools.lint.detector.api.Detector;
5 | import com.android.tools.lint.detector.api.Implementation;
6 | import com.android.tools.lint.detector.api.Issue;
7 | import com.android.tools.lint.detector.api.JavaContext;
8 | import com.android.tools.lint.detector.api.LintUtils;
9 | import com.android.tools.lint.detector.api.Scope;
10 | import com.android.tools.lint.detector.api.Severity;
11 | import com.android.tools.lint.detector.api.TextFormat;
12 | import com.intellij.psi.JavaElementVisitor;
13 | import com.intellij.psi.PsiCodeBlock;
14 | import com.intellij.psi.PsiElement;
15 | import com.intellij.psi.PsiMethod;
16 | import com.intellij.psi.PsiMethodCallExpression;
17 | import com.intellij.psi.PsiReferenceExpression;
18 |
19 | import java.util.ArrayList;
20 | import java.util.Arrays;
21 | import java.util.List;
22 | import java.util.regex.Pattern;
23 |
24 | /**
25 | * Detect issues for logging usage.
26 | * Created by muyangmin on Jun 05, 2017.
27 | * @see #ISSUE_LOG_CLASS
28 | */
29 | //This class must be public since its actual caller is Class
30 | // com.android.tools.lint.client.api.IssueRegistry
31 | @SuppressWarnings("WeakerAccess")
32 | public final class LoggingIssueDetector extends Detector implements Detector.JavaPsiScanner {
33 |
34 | /**
35 | * Reports issue on calls to `android.util.Log` or `System.out.println`。
36 | */
37 | private static final Issue ISSUE_LOG_CLASS =
38 | Issue.create("LogNotPLog",
39 | "Should use PLog or wrapper class",
40 | "Your project has included PLog, logging calls should be going to PLog " +
41 | "instead of `android.util.Log` or `System.out.println`.",
42 | Category.MESSAGES,
43 | 6,
44 | Severity.WARNING,
45 | new Implementation(LoggingIssueDetector.class, Scope.JAVA_FILE_SCOPE));
46 |
47 | /**
48 | * Reports issue on explicit format on v/d/i/w/e methods.
49 | */
50 | private static final Issue ISSUE_NESTED_FORMAT =
51 | Issue.create("NestedFormatInPLog",
52 | "Explicit String.format() is redundant when use PLog",
53 | "PLog will handle string formatting automatically, " +
54 | "so this explicit format() is useless.",
55 | Category.MESSAGES,
56 | 6,
57 | Severity.WARNING,
58 | new Implementation(LoggingIssueDetector.class, Scope.JAVA_FILE_SCOPE));
59 |
60 | private static final IssueReportHelper sHelper = new IssueReportHelper();
61 |
62 | /*package*/
63 | static List getIssues() {
64 | List issues = new ArrayList<>();
65 |
66 | issues.add(ISSUE_LOG_CLASS);
67 | issues.add(ISSUE_NESTED_FORMAT);
68 | //To be continue if needed ...
69 |
70 | return issues;
71 | }
72 |
73 | /**
74 | * Reports issue if this call is inside PLog.x().
75 | * Calling this method assumes actual calling method is 'String#format'.
76 | *
77 | * @see #ISSUE_NESTED_FORMAT
78 | */
79 | private static void checkNestedStringFormat(JavaContext context, PsiMethodCallExpression call) {
80 | PsiElement current = call;
81 | while (true) {
82 | current = LintUtils.skipParentheses(current.getParent());
83 | if (current == null || current instanceof PsiCodeBlock) {
84 | // Reached AST root or code block node; String.format not inside PLog.X(..).
85 | return;
86 | }
87 | if (current instanceof PsiMethodCallExpression) {
88 | PsiMethodCallExpression expression = (PsiMethodCallExpression) current;
89 | if (Pattern.matches("org\\.mym\\.plog\\.PLog\\.(v|d|i|w|e)",
90 | expression.getMethodExpression().getQualifiedName())) {
91 | sHelper.reportIssue(context, ISSUE_NESTED_FORMAT, call);
92 | return;
93 | }
94 | }
95 | }
96 | }
97 |
98 | @Override
99 | public List getApplicableMethodNames() {
100 | return Arrays.asList("format", "v", "d", "i", "w", "e", "wtf");
101 | }
102 |
103 | @Override
104 | public void visitMethod(JavaContext context, JavaElementVisitor visitor,
105 | PsiMethodCallExpression call, PsiMethod method) {
106 | PsiReferenceExpression methodExpression = call.getMethodExpression();
107 | String fullyQualifiedMethodName = methodExpression.getQualifiedName();
108 |
109 | if (fullyQualifiedMethodName.startsWith("android.util.Log")
110 | //Handle multiple overloaded out.print(and println, etc) methods.
111 | || fullyQualifiedMethodName.startsWith("java.lang.System.out.print")) {
112 | sHelper.reportIssue(context, ISSUE_LOG_CLASS, methodExpression);
113 | return;
114 | }
115 |
116 | if (fullyQualifiedMethodName.equals("java.lang.String.format")) {
117 | checkNestedStringFormat(context, call);
118 | return;
119 | }
120 | }
121 |
122 | private static class IssueReportHelper {
123 | /**
124 | * All param should be non-null.
125 | */
126 | void reportIssue(JavaContext context, Issue issue, PsiElement element) {
127 | context.report(issue, element, context.getLocation(element),
128 | issue.getBriefDescription(TextFormat.TEXT));
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/plog-printer/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/plog-printer/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'bintray-release'
3 |
4 | android {
5 | compileSdkVersion rootProject.ext.COMPILE_SDK_VERSION as int
6 | buildToolsVersion rootProject.ext.BUILD_TOOLS_VERSION as String
7 |
8 | defaultConfig {
9 | minSdkVersion rootProject.ext.MIN_SDK_VERSION as int
10 | targetSdkVersion rootProject.ext.TARGET_SDK_VERSION as int
11 | }
12 |
13 | packagingOptions {
14 | exclude 'LICENSE.txt'
15 | exclude 'NOTICE.txt'
16 | }
17 | lintOptions {
18 | abortOnError false
19 | }
20 | }
21 |
22 | dependencies {
23 | // compile 'org.mym.plog:android-plog:${VERSION_NAME}'
24 | // compile 'org.mym.plog:plog-formatter:${VERSION_NAME}'
25 |
26 | //For develop only
27 | provided project(':plog')
28 | // provided project(':plog-formatter')
29 | provided "com.android.support:support-annotations:${rootProject.ext.SUPPORT_VERSION as String}"
30 | }
31 |
32 | publish {
33 | artifactId = 'plog-printer'
34 | repoName = BINTRAY_REPO
35 | // userOrg =
36 | groupId = GROUP
37 | uploadName = POM_NAME
38 | publishVersion = VERSION_NAME
39 | desc = POM_DESCRIPTION
40 | website = POM_URL
41 | licences = POM_LICENCE_NAME
42 | }
--------------------------------------------------------------------------------
/plog-printer/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/plog-printer/src/main/java/org/mym/plog/printer/FilePrinter.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog.printer;
2 |
3 | import android.content.Context;
4 | import android.os.Environment;
5 | import android.os.Handler;
6 | import android.os.HandlerThread;
7 | import android.os.Looper;
8 | import android.os.Message;
9 | import android.os.Process;
10 | import android.support.annotation.NonNull;
11 | import android.text.TextUtils;
12 | import android.util.Log;
13 | import android.util.PrintStreamPrinter;
14 |
15 | import org.mym.plog.AbsPrinter;
16 | import org.mym.plog.PLog;
17 | import org.mym.plog.PrintLevel;
18 |
19 | import java.io.Closeable;
20 | import java.io.File;
21 | import java.io.FileOutputStream;
22 | import java.io.FilenameFilter;
23 | import java.io.IOException;
24 | import java.io.PrintStream;
25 | import java.lang.ref.WeakReference;
26 | import java.text.SimpleDateFormat;
27 | import java.util.Locale;
28 |
29 | /**
30 | * Print logs into a file. Soft wrap is disabled by default.
31 | *
32 | * @author Muyangmin
33 | * @since 2.0.0
34 | */
35 | @SuppressWarnings({"WeakerAccess", "unused"})
36 | public class FilePrinter extends AbsPrinter implements Closeable {
37 |
38 | /**
39 | * Place holder string for the internal files dir.
40 | *
41 | * @see Context#getFilesDir()
42 | */
43 | protected static final String DIR_INT_FILES = "${INT_PKG}";
44 | /**
45 | * Place holder string for the external files dir. (requires no permission)
46 | *
47 | * @see Context#getExternalFilesDir(String)
48 | */
49 | protected static final String DIR_EXT_FILES = "${EXT_PKG}";
50 | /**
51 | * Place holder string for the external storage root dir.(requires WRITE PERMISSION)
52 | *
53 | * @see Environment#getExternalStorageDirectory()
54 | */
55 | protected static final String DIR_EXT_ROOT = "${EXT_ROOT}";
56 |
57 | /**
58 | * File size limit.
59 | */
60 | private long mFileSizeLimit;
61 |
62 | /**
63 | * Define log file path.
64 | */
65 | private String mLogFilePath;
66 |
67 | /**
68 | * To decide file name when need to create new log file.
69 | */
70 | private FileNameGenerator mFileNameGenerator;
71 |
72 | /**
73 | * Actually do the print work.
74 | */
75 | private Handler mPrintHandler;
76 |
77 | private SimpleDateFormat mTimeFormatter;
78 |
79 | /**
80 | * Create a file printer using default path (external files dir).
81 | *
82 | * @throws IllegalStateException If try to use external storage but external storage
83 | * is unavailable.
84 | */
85 | public FilePrinter(@NonNull Context context) throws IllegalStateException {
86 | this(context, DIR_EXT_FILES + "/logs");
87 | }
88 |
89 | /**
90 | * Create a file printer using specified path.
91 | *
92 | * @param logFilePath absolute path; may use predefined placeholders in start. Currently
93 | * supported placeholders: {@link #DIR_INT_FILES}, {@link #DIR_EXT_FILES},
94 | * {@link #DIR_EXT_ROOT}.
95 | * @throws IllegalStateException If try to use external storage but external storage
96 | * is unavailable.
97 | */
98 | public FilePrinter(@NonNull Context context, @NonNull String logFilePath)
99 | throws IllegalStateException {
100 | this(context, logFilePath, new TimingFileNameGenerator(), 1024 * 1024);
101 | }
102 |
103 | /**
104 | * Create a file printer.
105 | *
106 | * @param fileSizeLimit file size limit. By default a single log file would not over 1M.
107 | * If a non-positive integer is provided, log files has no size limit.
108 | * @param generator a generator to provide filename when creating new log file.
109 | */
110 | public FilePrinter(@NonNull Context context, @NonNull String filePath,
111 | @NonNull FileNameGenerator generator, long fileSizeLimit)
112 | throws IllegalStateException {
113 | this.mLogFilePath = FilePrinterHelper.parseActualPath(context, filePath);
114 | this.mFileNameGenerator = generator;
115 | this.mFileSizeLimit = fileSizeLimit;
116 |
117 | HandlerThread mPrintThread = new HandlerThread("PLogFilePrinterThread",
118 | Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE);
119 | mPrintThread.start();
120 | mPrintHandler = new PrintHandler(mPrintThread.getLooper(), this);
121 |
122 | mTimeFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS", Locale.US);
123 | }
124 |
125 | @Override
126 | public void print(@PrintLevel int level, @NonNull String tag, @NonNull String msg) {
127 | mPrintHandler.sendMessage(mPrintHandler.obtainMessage(PrintHandler.MSG_WRITE_LOG,
128 | createPrintText(level, tag, msg)));
129 | }
130 |
131 | /**
132 | * This method provides a quick method to provide file header, e.g. crash files.
133 | * Although you still need to extends this class, but you needn't copy whole file management
134 | * code.
135 | */
136 | protected void printFileHeader(PrintStreamPrinter printer) {
137 |
138 | }
139 |
140 | /**
141 | * Override this method to redefine your preferred output format.
142 | * By default the log format is similar as shown in logcat.
143 | *
144 | * @param level log level
145 | * @param tag log tag
146 | * @param msg log content
147 | * @return the text to be written to file
148 | */
149 | protected String createPrintText(final int level, final String tag, final String msg) {
150 | String levelChar = "V";
151 | switch (level) {
152 | case Log.VERBOSE:
153 | levelChar = "V";
154 | break;
155 | case Log.DEBUG:
156 | levelChar = "D";
157 | break;
158 | case Log.INFO:
159 | levelChar = "I";
160 | break;
161 | case Log.WARN:
162 | levelChar = "W";
163 | break;
164 | case Log.ERROR:
165 | levelChar = "E";
166 | break;
167 | case Log.ASSERT:
168 | levelChar = "A";
169 | break;
170 | }
171 | String currentTime = mTimeFormatter.format(System.currentTimeMillis());
172 | return String.format("%s %s/%s: %s", currentTime, levelChar, tag, msg);
173 | }
174 |
175 | /**
176 | * Get the absolute path for log files. The proposal of this method is to debug or
177 | * traverse log path, not setting it.
178 | *
179 | * Override this method is not really recommended; consider using constructor instead.
180 | *
181 | * @return the absolute path for log files; should not be null
182 | */
183 | public final String getLogFilePath() {
184 | return mLogFilePath;
185 | }
186 |
187 | /**
188 | * Close associated files, etc.
189 | */
190 | @Override
191 | public void close() {
192 | if (mPrintHandler != null) {
193 | mPrintHandler.sendEmptyMessage(PrintHandler.MSG_CLOSE_FILE);
194 | }
195 | }
196 |
197 | /**
198 | * The interface to generate new log file.
199 | */
200 | public interface FileNameGenerator {
201 | /**
202 | * Generate a file name
203 | *
204 | * NOTE: the return value for this method should NOT be null, and should be a simple name
205 | * because the file path is decided by file logger instead of this interface.
206 | *
207 | *
208 | * @return the simple name of next log file, e.g. "log.txt"
209 | */
210 | String nextFile();
211 |
212 | /**
213 | * NOTE: the return value for this method should NOT be null, and should be a simple name
214 | * because the file path is decided by file logger instead of this interface.
215 | *
216 | * @param dir the directory of log files. This parameter is provided for
217 | * accessing files
218 | * if needed.
219 | * @param lastGenerated last returned value for {@link #nextFile()}.
220 | * @return the simple name of next log file, e.g. "log1.txt". Directly return a value
221 | * that equals lastGenerate param will cause an Exception.
222 | */
223 | String nextFileIfDuplicate(File dir, String lastGenerated);
224 | }
225 |
226 | //Using standard static handler pattern, although logger would never be GC
227 | private static class PrintHandler extends Handler {
228 |
229 | private static final int MSG_WRITE_LOG = 0x0001;
230 | private static final int MSG_CLOSE_FILE = 0x1000;
231 | private File mCurrentFile;
232 | private PrintStream mPrintStream;
233 | private PrintStreamPrinter mPrinter;
234 | private WeakReference mFilePrinter;
235 |
236 | PrintHandler(Looper looper, FilePrinter logger) {
237 | super(looper);
238 | mFilePrinter = new WeakReference<>(logger);
239 | }
240 |
241 | /**
242 | * For {@link #MSG_WRITE_LOG}, obj field should be a CharSequence.
243 | * For {@link #MSG_CLOSE_FILE}, all fields are ignored.
244 | *
245 | * @param msg should not be null
246 | */
247 | @Override
248 | public void handleMessage(Message msg) {
249 | super.handleMessage(msg);
250 | switch (msg.what) {
251 | case MSG_WRITE_LOG:
252 | if (!(msg.obj instanceof CharSequence)) {
253 | throw new IllegalArgumentException("Msg.obj must be charsequence!");
254 | }
255 | try {
256 | printLogToFile(((CharSequence) msg.obj));
257 | } catch (IOException e) {
258 | PLog.throwable(e);
259 | }
260 | break;
261 | case MSG_CLOSE_FILE:
262 | resetOutputStream();
263 | break;
264 | default:
265 | throw new IllegalArgumentException("Unsupported message type " + msg.what);
266 | }
267 | }
268 |
269 | private void printLogToFile(CharSequence content) throws IOException {
270 | if (TextUtils.isEmpty(content)) {
271 | return;
272 | }
273 | FilePrinter logger = mFilePrinter.get();
274 | if (logger == null) {
275 | return;
276 | }
277 |
278 | //single log length (content) may over limit; but needn't to care this situation
279 | // because it's still safe.
280 | if (mCurrentFile != null
281 | && (logger.mFileSizeLimit > 0 && mCurrentFile.length() + content.length() > logger.mFileSizeLimit)) {
282 | resetOutputStream();
283 | }
284 |
285 | //If printer or file is not ready, create it
286 | if (mCurrentFile == null && !createOutputStream(logger)) {
287 | return;
288 | }
289 | //Printer is ready, print text now
290 | mPrinter.println(content.toString());
291 | }
292 |
293 | /**
294 | * Close file object and output stream.
295 | */
296 | private void resetOutputStream() {
297 | if (mPrintStream != null) {
298 | mPrintStream.close();
299 | mPrinter = null;
300 | mCurrentFile = null;
301 | }
302 | }
303 |
304 | /**
305 | * Create log file and output stream.
306 | *
307 | * @return true if create succeed, false otherwise
308 | * @throws IOException if occurs
309 | */
310 | private boolean createOutputStream(FilePrinter logger) throws IOException {
311 | File path;
312 | try {
313 | path = FilePrinterHelper.resolveDirOrCreate(logger.getLogFilePath());
314 | } catch (SecurityException ignored) {
315 | path = null;
316 | }
317 |
318 | //Create failed, not read/writable, etc
319 | if (path == null) {
320 | return false;
321 | }
322 |
323 | String name = logger.mFileNameGenerator.nextFile();
324 |
325 | File file = new File(path, name);
326 | while (file.exists()) {
327 | String nextName = logger.mFileNameGenerator.nextFileIfDuplicate(path, name);
328 | if (TextUtils.isEmpty(nextName) || nextName.equals(name)) {
329 | throw new RuntimeException("File name already duplicated!");
330 | }
331 | file = new File(path, nextName);
332 | }
333 |
334 | mCurrentFile = new File(path, name);
335 | //If file not found and create failed, just return
336 | if (!mCurrentFile.exists() && !mCurrentFile.createNewFile()) {
337 | return false;
338 | }
339 | mPrintStream = new PrintStream(new FileOutputStream(mCurrentFile), true, "UTF-8");
340 | mPrinter = new PrintStreamPrinter(mPrintStream);
341 | FilePrinter printer;
342 | if (mFilePrinter != null && ((printer = mFilePrinter.get()) != null)) {
343 | printer.printFileHeader(mPrinter);
344 | }
345 | return true;
346 | }
347 | }
348 |
349 | public static class TimingFileNameGenerator implements FileNameGenerator {
350 |
351 | private static final String formatStr = "yyyyMMdd-HHmm";
352 | private static final String FORMAT_REGEX = "\\d{8}\\-\\d{4}";
353 | private static final String CONCAT = "-";
354 | private SimpleDateFormat timingFormat = new SimpleDateFormat(formatStr, Locale.US);
355 |
356 | @Override
357 | public String nextFile() {
358 | return timingFormat.format(System.currentTimeMillis()) + ".log";
359 | }
360 |
361 | @Override
362 | public String nextFileIfDuplicate(File dir, String timestamp) {
363 | File[] files = dir.listFiles(new FilenameFilter() {
364 | @Override
365 | public boolean accept(File dir, String name) {
366 | return name.matches(FORMAT_REGEX + CONCAT + "\\d+" + "\\.log");
367 | }
368 | });
369 | if (files == null || files.length == 0) {
370 | // throw new RuntimeException("File name not valid but no such file exists.");
371 | return formatNameWithSerialNum(timestamp, 1);
372 | }
373 |
374 | //Find last file and
375 | long lastModifiedTime = 0;
376 | File lastModifiedFile = null;
377 | for (File file : files) {
378 | if (lastModifiedFile == null || file.lastModified() > lastModifiedTime) {
379 | lastModifiedTime = file.lastModified();
380 | lastModifiedFile = file;
381 | }
382 | }
383 | //Must be not null
384 | //noinspection ConstantConditions
385 | String last = lastModifiedFile.getName();
386 | //Because we used FilenameFilter, so can be sure that suffix must be a number.
387 | int serialNum = Integer.parseInt(last.substring((formatStr + CONCAT).length()));
388 | return formatNameWithSerialNum(timestamp, serialNum + 1);
389 | }
390 |
391 | private String formatNameWithSerialNum(String timestamp, int serialNum) {
392 | return timestamp + CONCAT + serialNum + ".log";
393 | }
394 | }
395 | }
396 |
--------------------------------------------------------------------------------
/plog-printer/src/main/java/org/mym/plog/printer/FilePrinterHelper.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog.printer;
2 |
3 | import android.content.Context;
4 | import android.os.Environment;
5 | import android.support.annotation.NonNull;
6 | import android.support.annotation.Nullable;
7 |
8 | import java.io.File;
9 | import java.util.regex.Matcher;
10 | import java.util.regex.Pattern;
11 |
12 | import static org.mym.plog.printer.FilePrinter.DIR_EXT_FILES;
13 | import static org.mym.plog.printer.FilePrinter.DIR_EXT_ROOT;
14 | import static org.mym.plog.printer.FilePrinter.DIR_INT_FILES;
15 |
16 | /**
17 | * A helper class for file printer to resolve file dir, providing extra grammar sugar.
18 | * Created by muyangmin on Apr 25, 2017.
19 | */
20 | /*package*/ final class FilePrinterHelper {
21 |
22 | /**
23 | * Check whether argument path matches predefined storage path.
24 | * If true, replace the placeholder by the actual path.
25 | * For example, "${EXT_STORE}/app/crash" may be replaced by "/storage/emulated/0/app/crash".
26 | *
27 | * @return the actual path, or original argument if it not match any pre-supported dirs.
28 | * @throws IllegalStateException If try to use external storage but external storage
29 | * is unavailable.
30 | */
31 | static String parseActualPath(Context context, String path) throws IllegalStateException {
32 | String[] supportedPath = new String[]{DIR_EXT_ROOT, DIR_EXT_FILES, DIR_INT_FILES};
33 | for (String s : supportedPath) {
34 | // match only line start, e.g."^${EXT_STORE}"
35 | // must process escape char for regex
36 | String regex = "^" + s.replace("$", "\\$").replace("{", "\\{")
37 | .replace("}", "\\}");
38 |
39 | Matcher matcher = Pattern.compile(regex).matcher(path);
40 | if (!matcher.find()) {
41 | continue;
42 | }
43 | String param = matcher.group();
44 | String actualPath = null;
45 | switch (param) {
46 | case DIR_EXT_ROOT:
47 | File root = Environment.getExternalStorageDirectory();
48 | if ((!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()))
49 | || root == null) {
50 | throw new IllegalStateException("External storage is not available!");
51 | }
52 | actualPath = matcher.replaceFirst(root.getAbsolutePath());
53 | break;
54 |
55 | case DIR_EXT_FILES:
56 | //This method requires no permission
57 | File filesDir = context.getExternalFilesDir(null);
58 | if (filesDir == null) {
59 | throw new IllegalStateException("External storage is not available!");
60 | }
61 | actualPath = matcher.replaceFirst(filesDir.getAbsolutePath());
62 | break;
63 |
64 | case DIR_INT_FILES:
65 | actualPath = matcher.replaceFirst(context.getFilesDir().getAbsolutePath());
66 | break;
67 | }
68 | return actualPath;
69 | }
70 | return path;
71 | }
72 |
73 | /**
74 | * Resolve and check for log path.
75 | *
76 | * @return A readable and writable path. If path does not exist, create it automatically.
77 | * If create failed, this method will return null.
78 | * @throws SecurityException
79 | * If a security manager exists and its {@link
80 | * java.lang.SecurityManager#checkRead(java.lang.String)}
81 | * method does not permit verification of the existence of the
82 | * named directory and all necessary parent directories; or if
83 | * the {@link
84 | * java.lang.SecurityManager#checkWrite(java.lang.String)}
85 | * method does not permit the named directory and all necessary
86 | * parent directories to be created
87 | */
88 | @Nullable
89 | static File resolveDirOrCreate(@NonNull String path) throws SecurityException {
90 | File file = new File(path);
91 | if (!file.exists() && !file.mkdirs()) {
92 | return null;
93 | }
94 | if (!file.isDirectory()) {
95 | // throw new IllegalArgumentException("Path is not a directory!");
96 | return null;
97 | }
98 | if (!file.canRead() || !file.canWrite()) {
99 | // throw new IllegalArgumentException("Path cannot be read/write!");
100 | return null;
101 | }
102 | return file;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/plog-timing/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/plog-timing/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'bintray-release'
3 |
4 | android {
5 | compileSdkVersion rootProject.ext.COMPILE_SDK_VERSION as int
6 | buildToolsVersion rootProject.ext.BUILD_TOOLS_VERSION as String
7 |
8 | defaultConfig {
9 | minSdkVersion rootProject.ext.MIN_SDK_VERSION as int
10 | targetSdkVersion rootProject.ext.TARGET_SDK_VERSION as int
11 | }
12 |
13 | packagingOptions {
14 | exclude 'LICENSE.txt'
15 | exclude 'NOTICE.txt'
16 | }
17 | lintOptions {
18 | abortOnError false
19 | }
20 | }
21 |
22 | dependencies {
23 | // compile 'org.mym.plog:android-plog:${VERSION_NAME}'
24 |
25 | //For develop only
26 | provided project(':plog')
27 | }
28 |
29 | publish {
30 | artifactId = 'plog-timing'
31 | repoName = BINTRAY_REPO
32 | // userOrg =
33 | groupId = GROUP
34 | uploadName = POM_NAME
35 | publishVersion = VERSION_NAME
36 | desc = POM_DESCRIPTION
37 | website = POM_URL
38 | licences = POM_LICENCE_NAME
39 | }
--------------------------------------------------------------------------------
/plog-timing/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/plog-timing/src/main/java/org/mym/plog/timing/TimingLogger.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog.timing;
2 |
3 | import android.os.SystemClock;
4 | import android.util.Log;
5 |
6 | import org.mym.plog.PLog;
7 |
8 | import java.util.ArrayList;
9 |
10 | /**
11 | * IMPORTANT:This class is copied from AOSP standard class {@link android.util.TimingLogger}.
12 | *
13 | * CHANGES:
14 | *
15 | *
redirect output logic to PLog standard log flow.
16 | *
removed disable logic. using PLog's timing logger means you totally wants to log these
17 | * messages. However you can still intercept logs via PLog core library API.
18 | *
19 | *
20 | */
21 | @SuppressWarnings({"WeakerAccess", "unused"})
22 | public class TimingLogger {
23 |
24 | /** Stores the time of each split. */
25 | private ArrayList mSplits;
26 | /** Stores the labels for each split. */
27 | private ArrayList mSplitLabels;
28 | /**
29 | * The Log tag to use for checking Log.isLoggable and for
30 | * logging the timings.
31 | */
32 | private String mTag;
33 | /** A label to be included in every log. */
34 | private String mLabel;
35 |
36 | /**
37 | * Create and initialize a TimingLogger object that will log using
38 | * the specific tag. If the Log.isLoggable is not enabled to at
39 | * least the Log.VERBOSE level for that tag at creation time then
40 | * the addSplit and dumpToLog call will do nothing.
41 | * @param tag the log tag to use while logging the timings
42 | * @param label a string to be displayed with each log
43 | */
44 | public TimingLogger(String tag, String label) {
45 | reset(tag, label);
46 | }
47 |
48 | public void reset(String tag, String label) {
49 | mTag = tag;
50 | mLabel = label;
51 | reset();
52 | }
53 |
54 | public void reset() {
55 | // mDisabled = !Log.isLoggable(mTag, Log.VERBOSE);
56 | if (mSplits == null) {
57 | mSplits = new ArrayList<>();
58 | mSplitLabels = new ArrayList<>();
59 | } else {
60 | mSplits.clear();
61 | mSplitLabels.clear();
62 | }
63 | addSplit(null);
64 | }
65 |
66 | public void addSplit(String splitLabel) {
67 | long now = SystemClock.elapsedRealtime();
68 | mSplits.add(now);
69 | mSplitLabels.add(splitLabel);
70 | }
71 |
72 | public void dumpToLog() {
73 | StringBuilder sb = new StringBuilder();
74 | // callLogger(mTag, mLabel + ": begin");
75 | sb.append(mLabel).append(":begin").append("\n");
76 | final long first = mSplits.get(0);
77 | long now = first;
78 | for (int i = 1; i < mSplits.size(); i++) {
79 | now = mSplits.get(i);
80 | final String splitLabel = mSplitLabels.get(i);
81 | final long prev = mSplits.get(i - 1);
82 |
83 | // callLogger(mTag, mLabel + ": " + (now - prev) + " ms, " + splitLabel);
84 | sb.append(mLabel).append(": ").append(now - prev).append(" ms, ")
85 | .append(splitLabel)
86 | .append("\n");
87 | }
88 | // callLogger(mTag, mLabel + ": end, " + (now - first) + " ms");
89 | sb.append(mLabel).append(": end, ").append(now - first).append(" ms")
90 | .append("\n");
91 | callLogger(mTag, sb.toString());
92 | }
93 |
94 | private void callLogger(String tag, String msg){
95 | PLog.level(Log.DEBUG).tag(tag).msg(msg).stackOffset(2).print();
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/plog/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/plog/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'bintray-release'
3 |
4 | android {
5 | compileSdkVersion rootProject.ext.COMPILE_SDK_VERSION as int
6 | buildToolsVersion rootProject.ext.BUILD_TOOLS_VERSION as String
7 |
8 | defaultConfig {
9 | minSdkVersion rootProject.ext.MIN_SDK_VERSION as int
10 | targetSdkVersion rootProject.ext.TARGET_SDK_VERSION as int
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 |
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | packagingOptions {
21 | exclude 'LICENSE.txt'
22 | exclude 'NOTICE.txt'
23 | }
24 | lintOptions {
25 | abortOnError false
26 | }
27 | }
28 |
29 | dependencies {
30 | provided "${rootProject.ext.SUPPORT_ANNOTATION as String}"
31 | }
32 |
33 | publish {
34 | artifactId = 'plog-core'
35 | repoName = BINTRAY_REPO
36 | // userOrg =
37 | groupId = GROUP
38 | uploadName = POM_NAME
39 | publishVersion = VERSION_NAME
40 | desc = POM_DESCRIPTION
41 | website = POM_URL
42 | licences = POM_LICENCE_NAME
43 | }
--------------------------------------------------------------------------------
/plog/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in F:\Developer\AndroidSdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/plog/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/AbsPrinter.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 |
6 | /**
7 | * An abstract implementation for printer,Only default initialization and get/set methods are
8 | * implemented.
9 | *
10 | * @since 2.0.0-beta2
11 | */
12 | public abstract class AbsPrinter implements Printer {
13 |
14 | private Formatter mFormatter;
15 |
16 | private SoftWrapper mSoftWrapper;
17 |
18 | private Style mStyle;
19 |
20 | private Interceptor mInterceptor;
21 |
22 | private int mMaxLengthPerLine;
23 |
24 | public AbsPrinter() {
25 | this(0);
26 | }
27 |
28 | public AbsPrinter(int maxLengthPerLine) {
29 | mMaxLengthPerLine = maxLengthPerLine;
30 | doDefaultInit();
31 | }
32 |
33 | private void doDefaultInit() {
34 | try {
35 | //noinspection unchecked
36 | Class extends Formatter> clz = ((Class extends Formatter>)
37 | Class.forName("org.mym.plog.formatter.DefaultFormatter"));
38 | //Only create a instance for provided dependency
39 | mFormatter = clz.newInstance();
40 | } catch (Exception e) {
41 | //If formatter module is not included, use null formatter.
42 | mFormatter = null;
43 | }
44 | }
45 |
46 | @Nullable
47 | @Override
48 | public Formatter getFormatter() {
49 | return mFormatter;
50 | }
51 |
52 | public void setFormatter(Formatter formatter) {
53 | mFormatter = formatter;
54 | }
55 |
56 | @Override
57 | public SoftWrapper getSoftWrapper() {
58 | return mSoftWrapper;
59 | }
60 |
61 | public void setSoftWrapper(SoftWrapper softWrapper) {
62 | mSoftWrapper = softWrapper;
63 | }
64 |
65 | @Nullable
66 | @Override
67 | public Style getStyle() {
68 | return mStyle;
69 | }
70 |
71 | public void setStyle(Style style) {
72 | mStyle = style;
73 | }
74 |
75 | @Override
76 | public int getMaxLengthPerLine() {
77 | return mMaxLengthPerLine;
78 | }
79 |
80 | @Override
81 | public boolean onIntercept(@PrintLevel int level, @NonNull String tag,
82 | @Nullable Category category, @NonNull String msg) {
83 | return mInterceptor != null && mInterceptor.onIntercept(level, tag, category, msg);
84 | }
85 |
86 | public void setInterceptor(Interceptor interceptor) {
87 | mInterceptor = interceptor;
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/Category.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | /**
6 | * Defines a dimension of interception, usually useful for large teams.
7 | *
8 | * @since 2.0.0
9 | */
10 | public interface Category {
11 | @NonNull
12 | String getName();
13 |
14 | /**
15 | * A help method to compare with another category.
16 | *
17 | * @param name another category name, must be not null; often pass by {cat.getName()}.
18 | * @return true if this category is compatible with param category.
19 | */
20 | boolean isSameAs(@NonNull String name);
21 | }
22 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/DebugPrinter.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.os.SystemClock;
5 | import android.support.annotation.CheckResult;
6 | import android.support.annotation.NonNull;
7 | import android.support.annotation.Nullable;
8 | import android.util.Log;
9 |
10 | /**
11 | * Print all message to logcat.
12 | *
13 | *
14 | * Note that messages longer than 4K characters will be truncated into parts, to avoid
15 | * default logcat 4K issue.
16 | *
12 | * NOTE VERY IMPORTANT: Sub implementation can decide whether discard one or more arguments,
13 | * so make sure you are using right Formatter in right usage.
14 | *
15 | * @param msg msg to be formatted. If this is null or empty but params provided, params
16 | * still should be formatted.
17 | * @param params params to format msg. maybe empty.
18 | * @return formatted msg.
19 | * @throws Exception any type of Exception can be thrown by implementation when
20 | * format failed.
21 | */
22 | String format(String msg, Object... params) throws Exception;
23 | //
24 | // /**
25 | // * Indicate whether the result of this formatter is well line-wrapped.
26 | // * NOTE: This return value is important, and if returns true, then line wrap procedure can be
27 | // * omitted to improve performance.
28 | // */
29 | // boolean isPreWrappedFormat(Class> paramClz);
30 | }
31 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/Interceptor.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog;
2 |
3 | import android.support.annotation.CheckResult;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 |
7 | /**
8 | * Decide intercept a log, or let it print out.
9 | *
10 | * @since 2.0.0-beta2
11 | */
12 | public interface Interceptor {
13 | /**
14 | * Decide whether this item of log should be intercepted.
15 | *
16 | * @param level print level of this log.
17 | * @param category category of this log, if specified.
18 | * @param msg content of this log(before formatting!). Note if msg in the
19 | * original request is null, you'll get an empty string here.
20 | * @return if returns true, this log won't be printed and just be ignored. Otherwise it would
21 | * be formatted and printed as usual.
22 | */
23 | @CheckResult
24 | boolean onIntercept(@PrintLevel int level, @NonNull String tag,
25 | @Nullable Category category, @NonNull String msg);
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/LogEngine.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 | import android.text.TextUtils;
6 |
7 | import org.mym.plog.config.PLogConfig;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Collections;
11 | import java.util.List;
12 | import java.util.Locale;
13 |
14 | /**
15 | * This is the core logic for PLog 2.0: it contains dependencies of almost all interfaces, and
16 | * decide how to use them.
17 | *
18 | * @since 2.0.0
19 | */
20 | final class LogEngine {
21 | private static final int STACK_TRACE_INDEX = 5;
22 |
23 | private static boolean HAS_WARN_NO_PRINTERS = false;
24 |
25 | private static List mPrinters = new ArrayList<>();
26 |
27 | /*package*/
28 | static void setPrinters(Printer... printers) {
29 | if (printers != null && printers.length > 0) {
30 | if (!mPrinters.isEmpty()) {
31 | mPrinters.clear();
32 | }
33 | //Arrays.ArrayList cannot support remove operation thus cannot clear.
34 | // mPrinters = Arrays.asList(printers);
35 | Collections.addAll(mPrinters, printers);
36 | }
37 | }
38 |
39 | /*package*/
40 | static void appendPrinter(@NonNull Printer printer) {
41 | mPrinters.add(printer);
42 | }
43 |
44 | /*package*/
45 | static void handleLogRequest(LogRequest request) {
46 | //REMOVED this check on 2.0.0-beta5.
47 | // This check is introduced at earlier version to help developers to find useless calls.
48 | // However, there is a product environment case: developer only print log(SomeVar) while
49 | // SomeVar becomes null at runtime. In this case log should not be rejected, so just
50 | // remove this check.
51 |
52 | // if (request == null || (!request.isPrintTraceOnly() && TextUtils.isEmpty(request.getMsg())
53 | // && (request.getParams() == null || request.getParams().length == 0))) {
54 | // throw new IllegalArgumentException("Bad request: both msg and param are null/empty!");
55 | // }
56 |
57 | if (mPrinters.isEmpty() && !HAS_WARN_NO_PRINTERS) {
58 | mPrinters.add(new DebugPrinter(true));
59 | PLog.e("No printer prepared, did you forgot it?");
60 | HAS_WARN_NO_PRINTERS = true;
61 | }
62 |
63 | //Check tag
64 | PLogConfig config = PLog.getCurrentConfig();
65 |
66 | int offset = STACK_TRACE_INDEX + config.getGlobalStackOffset() + request
67 | .getStackOffset();
68 | StackTraceElement element = getLogStackElement(offset);
69 | String tag = prepareFinalTag(config, request.getTag(), element);
70 |
71 | Category category = request.getCategory();
72 | if (category != null && !TextUtils.isEmpty(category.getName())) {
73 | tag = String.format("%s[Category:%s]", tag, category.getName());
74 | }
75 |
76 | //If intercepted by global interceptor, just do nothing and return
77 | String safeMsgForIntercept = request.getMsg() == null ? "" : request.getMsg();
78 | if ((config.getGlobalInterceptor() != null && config.getGlobalInterceptor().onIntercept
79 | (request.getLevel(), tag, request.getCategory(), safeMsgForIntercept))) {
80 | return;
81 | }
82 |
83 | for (Printer printer : mPrinters) {
84 |
85 | //Check intercept result
86 | if (printer.onIntercept(request.getLevel(), tag, request.getCategory(),
87 | safeMsgForIntercept)) {
88 | //Skip and ignore this log
89 | continue;
90 | }
91 |
92 | // ---------- Build formatted(if need) content for this printer ----------
93 |
94 | //Format if allowed
95 | String content = request.getMsg();
96 |
97 | if (request.isPrintTraceOnly()) {
98 | StackTraceElement[] stack = getLogStackForDiagnosis();
99 | StringBuilder sb = new StringBuilder();
100 | if (stack == null) {
101 | sb.append("Stack trace unavailable!");
102 | } else {
103 | sb.append(String.format(Locale.US,
104 | "FYI: Library offset = %d, globalOffset = %d, requestOffset = %d.\n",
105 | STACK_TRACE_INDEX, config.getGlobalStackOffset(), request
106 | .getStackOffset()));
107 | sb.append("Tips: VMStack(0) maybe not shown on some cases.\n");
108 | for (int i = 1; i < stack.length; i++) {
109 | StackTraceElement traceElement = stack[i];
110 | sb.append(String.format(Locale.US, "\t%2d %s\n",
111 | i, traceElement));
112 | }
113 | }
114 | content = sb.toString();
115 | }
116 |
117 | //If no formatter specified but has param, just toString.
118 | else if (printer.getFormatter() == null && content == null) {
119 | StringBuilder sb = new StringBuilder();
120 | Object[] params = request.getParams();
121 | for (int i = 0; i < params.length; i++) {
122 | sb.append("param[")
123 | .append(i)
124 | .append("]=")
125 | .append(params[i])
126 | .append("\n")
127 | ;
128 | }
129 | content = sb.toString();
130 | }
131 |
132 | //Else: assume formatter can handle all cases
133 | else if (printer.getFormatter() != null) {
134 | try {
135 | content = printer.getFormatter().format(request.getMsg(), request.getParams());
136 | } catch (Exception ignored) {
137 | //Empty
138 | }
139 | }
140 |
141 | // ---------- Soft wrap(if need) content for this printer ----------
142 | Style style = printer.getStyle();
143 | if (style == null) {
144 | style = DefaultStyle.INSTANCE;
145 | }
146 |
147 | //SoftWrap if allowed
148 | if (printer.getSoftWrapper() != null) {
149 | if (printer.getMaxLengthPerLine() <= 0) {
150 | throw new IllegalArgumentException("max length should be a positive integer!");
151 | } else {
152 | content = printer.getSoftWrapper().wrapLine(content,
153 | printer.getMaxLengthPerLine());
154 | }
155 | }
156 |
157 | // ---------- Build Final content for this printer ----------
158 |
159 | StringBuilder outputSb = new StringBuilder(content == null ? 32 : content.length() * 2);
160 | if (config.isNeedLineNumber() && element != null) {
161 | outputSb.append(generateLineInfo(element));
162 | }
163 |
164 | if (config.isNeedThreadInfo()) {
165 | outputSb.append(generateThreadInfo(Thread.currentThread()));
166 | }
167 |
168 | if (!TextUtils.isEmpty(style.msgPrefix())) {
169 | outputSb.append(style.msgPrefix());
170 | }
171 | outputSb.append(content);
172 | if (!TextUtils.isEmpty(style.msgSuffix())) {
173 | outputSb.append(style.msgSuffix());
174 | }
175 |
176 | // ---------- Call printer at last ----------
177 | printer.print(request.getLevel(), tag, outputSb.toString());
178 | }
179 | }
180 |
181 | @NonNull
182 | private static String prepareFinalTag(PLogConfig config, String explicitTag, @Nullable
183 | StackTraceElement element) {
184 | String tag = explicitTag;
185 | //Checking for auto tag
186 | if (TextUtils.isEmpty(tag) && config.isUseAutoTag() && element != null) {
187 | tag = generateAutoTag(element);
188 | }
189 | //Only concat when tag is not empty and config is specified to true
190 | if ((!TextUtils.isEmpty(tag)) && config.isForceConcatGlobalTag()) {
191 | tag = config.getGlobalTag() + "-" + tag;
192 | }
193 | //If still empty, using global
194 | else if (TextUtils.isEmpty(tag)) {
195 | tag = config.getGlobalTag();
196 | }
197 | return tag;
198 | }
199 |
200 | @Nullable
201 | private static StackTraceElement getLogStackElement(int offset) {
202 | StackTraceElement[] stack = Thread.currentThread().getStackTrace();
203 | //VMStack->ThreadStack->getLogStack->handleLogRequest->LogRequest.execute()
204 | //->CallerCode.
205 | if (stack == null || stack.length <= offset) {
206 | return null;
207 | }
208 | return stack[offset];
209 | }
210 |
211 | /**
212 | * Note this is a emulated method to keep same level with user calling; normally user code
213 | * just call {@link #getLogStackElement(int)}.
214 | *
215 | * @see #getLogStackElement(int)
216 | */
217 | @Nullable
218 | private static StackTraceElement[] getLogStackForDiagnosis() {
219 | return Thread.currentThread().getStackTrace();
220 | }
221 |
222 | private static String generateAutoTag(@NonNull StackTraceElement element) {
223 | String className = element.getClassName();
224 | //parse to simple name
225 | String pkgPath[] = className.split("\\.");
226 | if (pkgPath.length > 0) {
227 | className = pkgPath[pkgPath.length - 1];
228 | }
229 |
230 |
231 | //IMPORTANT:
232 | // 因为Java语法允许在匿名类中继续包含具名的子类,因此必须逆序遍历,但是lastIndex方法没有endIndex参数。
233 | // 所以只能反向遍历, 每次截取最后一段执行subString, 如果全是数字,则继续往前遍历。
234 | // Since nested inner class in anonymous is allowed, here we must do reversal traverse
235 | // for the string.
236 | StringBuilder sbInnerClass = new StringBuilder();
237 | int index;
238 | String strLoop = className;
239 | while ((index = strLoop.lastIndexOf("$")) != -1) {
240 | String piece = strLoop.substring(index + 1); //skip dollar
241 | sbInnerClass.insert(0, piece);
242 | //Careful: if only judge 0-9, then A$1$2$3 case would get unexpected answer 2$3.
243 | if (!piece.matches("[0-9$]+")) {
244 | break;
245 | }
246 | //still anonymous class, continue loop
247 | sbInnerClass.insert(0, "$");
248 | //truncate last piece
249 | strLoop = strLoop.substring(0, index);
250 | }
251 | //delete first leading dollar
252 | if (sbInnerClass.length() > 0 && sbInnerClass.charAt(0) == '$') {
253 | sbInnerClass.deleteCharAt(0);
254 | }
255 | String innerClassName = sbInnerClass.toString();
256 | //This happens on class like MainActivity$1.
257 | if (TextUtils.isDigitsOnly(innerClassName)) {
258 | //Reset; use full name instead.
259 | innerClassName = null;
260 | }
261 |
262 | return TextUtils.isEmpty(innerClassName) ? className : innerClassName;
263 | }
264 |
265 | @NonNull
266 | private static String generateLineInfo(@NonNull StackTraceElement element) {
267 | return String.format("[(%s:%s):%s]",
268 | TextUtils.isEmpty(element.getFileName()) ? "Unknown Source" : element.getFileName(),
269 | element.getLineNumber(),
270 | element.getMethodName());
271 | }
272 |
273 | @NonNull
274 | private static String generateThreadInfo(@Nullable Thread thread) {
275 | return thread == null ? " [Thread:N/A] " : " [" + thread.toString() + "] ";
276 | }
277 |
278 | private static class DefaultStyle implements Style {
279 |
280 | /*package*/ static DefaultStyle INSTANCE = new DefaultStyle();
281 |
282 | @Nullable
283 | @Override
284 | public String msgPrefix() {
285 | return null;
286 | }
287 |
288 | @Nullable
289 | @Override
290 | public String msgSuffix() {
291 | return null;
292 | }
293 |
294 | @Nullable
295 | @Override
296 | public String lineHeader() {
297 | return null;
298 | }
299 | }
300 | }
301 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/LogRequest.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog;
2 |
3 | import android.util.Log;
4 |
5 | /**
6 | * Builder style API; use this class to fit complicated needs.
7 | *
8 | * NOTE: APIs in {@link PLog} class is for common scenarios and this class is for advanced usage.
9 | * Some decor method in {@link PLog} will NOT be added into this class, e.g.
10 | * {@link PLog#wtf(Throwable)}.
11 | *
12 | *
13 | * @author Muyangmin
14 | * @since 2.0.0
15 | */
16 | public final class LogRequest {
17 |
18 | @PrintLevel
19 | private int level;
20 | private int stackOffset;
21 | private String tag;
22 | private Category category;
23 | private String msg;
24 | private Object[] params;
25 | private boolean printTraceOnly;
26 |
27 | public LogRequest() {
28 | //level must be assigned in constructor explicitly due to 0 is not belong to valid levels.
29 | level = Log.VERBOSE;
30 | }
31 |
32 | @PrintLevel
33 | public int getLevel() {
34 | return level;
35 | }
36 |
37 | public int getStackOffset() {
38 | return stackOffset;
39 | }
40 |
41 | public String getTag() {
42 | return tag;
43 | }
44 |
45 | public Category getCategory() {
46 | return category;
47 | }
48 |
49 | public String getMsg() {
50 | return msg;
51 | }
52 |
53 | public Object[] getParams() {
54 | return params;
55 | }
56 |
57 | /*package*/ boolean isPrintTraceOnly() {
58 | return printTraceOnly;
59 | }
60 |
61 |
62 | // ----- Decor methods; just for better usage BEGIN -----
63 | public LogRequest throwable(Throwable throwable) {
64 | return params(throwable);
65 | }
66 |
67 | public LogRequest category(String category) {
68 | return category(SimpleCategory.obtain(category));
69 | }
70 | // ----- Decor methods; just for better usage END -----
71 |
72 | // -----BUILDER STYLE CODE BEGIN -----
73 |
74 | public LogRequest level(@PrintLevel int level) {
75 | this.level = level;
76 | return this;
77 | }
78 |
79 | public LogRequest tag(String tag) {
80 | this.tag = tag;
81 | return this;
82 | }
83 |
84 | public LogRequest category(Category category) {
85 | this.category = category;
86 | return this;
87 | }
88 |
89 | public LogRequest msg(String msg) {
90 | this.msg = msg;
91 | return this;
92 | }
93 |
94 | public LogRequest params(Object... params) {
95 | this.params = params;
96 | return this;
97 | }
98 |
99 | public LogRequest stackOffset(int stackOffset) {
100 | this.stackOffset = stackOffset;
101 | return this;
102 | }
103 |
104 | /*package*/ LogRequest printTraceOnly() {
105 | this.printTraceOnly = true;
106 | return this;
107 | }
108 |
109 | // -----BUILDER STYLE CODE END -----
110 |
111 | /**
112 | * @deprecated This method name may be a little ambiguous, and will be removed in future
113 | * release. Consider use {@link #print()} instead.
114 | */
115 | public void execute() {
116 | //add try-catch block to avoid crash from library if any internal exception is thrown.
117 | try {
118 | LogEngine.handleLogRequest(this);
119 | } catch (Throwable t) {
120 | t.printStackTrace();
121 | }
122 | }
123 |
124 | /**
125 | * Print log to prepared printers.
126 | *
127 | * @since 2.0.0-beta5
128 | */
129 | public void print() {
130 | // Use same code with execute() to keep same stack level
131 | //add try-catch block to avoid crash from library if any internal exception is thrown.
132 | try {
133 | LogEngine.handleLogRequest(this);
134 | } catch (Throwable t) {
135 | t.printStackTrace();
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/PLog.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.util.Log;
5 |
6 | import org.json.JSONObject;
7 | import org.mym.plog.config.PLogConfig;
8 |
9 |
10 | /**
11 | *
12 | * Entry of all library public API; you can set configurations and launch log requests using this
13 | * class with only a static method call.
14 | *
15 | * @since 1.0.0
16 | */
17 | @SuppressWarnings("unused")
18 | public final class PLog {
19 |
20 | private static PLogConfig mConfig;
21 |
22 | //The constructor of this class is meaningless, so make it private
23 | private PLog() {
24 | //Empty
25 | }
26 |
27 | /**
28 | * Check and set {@link #mConfig} field. this operation guarantee the reading of config for
29 | * later operation is safe.
30 | */
31 | private static synchronized void safelySetConfig(PLogConfig config) throws RuntimeException {
32 | PLogConfig.checkConfigSafe(config);
33 | mConfig = config;
34 | }
35 |
36 | /**
37 | * Init PLog using customized config.
38 | */
39 | public static void init(PLogConfig config) {
40 | safelySetConfig(config);
41 | }
42 |
43 | /**
44 | * Get current config, useful for temporarily change config and backup then.
45 | * Another scenario is to debug this library, or using in extend libraries.
46 | *
47 | * @return Current config; or default config if {@link #init(PLogConfig)} is not called yet.
48 | * @see #init(PLogConfig)
49 | * @since 1.3.0
50 | */
51 | @NonNull
52 | public static PLogConfig getCurrentConfig() {
53 | checkInitOrUseDefaultConfig();
54 | return mConfig;
55 | }
56 |
57 | public static LogRequest category(String category) {
58 | return category(SimpleCategory.obtain(category));
59 | }
60 |
61 | public static LogRequest category(@NonNull Category category) {
62 | return new LogRequest().category(category);
63 | }
64 |
65 | public static LogRequest level(@PrintLevel int level){
66 | return new LogRequest().level(level);
67 | }
68 |
69 | public static LogRequest tag(@NonNull String tag){
70 | return new LogRequest().tag(tag);
71 | }
72 |
73 | public static void v(String msg, Object... params) {
74 | //TODO implement a pool of logRequest
75 | new LogRequest().level(Log.VERBOSE).msg(msg).params(params).stackOffset(1).print();
76 | }
77 |
78 | public static void d(String msg, Object... params) {
79 | new LogRequest().level(Log.DEBUG).msg(msg).params(params).stackOffset(1).print();
80 | }
81 |
82 | public static void i(String msg, Object... params) {
83 | new LogRequest().level(Log.INFO).msg(msg).params(params).stackOffset(1).print();
84 | }
85 |
86 | public static void w(String msg, Object... params) {
87 | new LogRequest().level(Log.WARN).msg(msg).params(params).stackOffset(1).print();
88 | }
89 |
90 | public static void e(String msg, Object... params) {
91 | new LogRequest().level(Log.ERROR).msg(msg).params(params).stackOffset(1).print();
92 | }
93 |
94 | /**
95 | * Print a predefined message using a predefined level.
96 | *
97 | * @see org.mym.plog.config.PLogConfig.Builder#emptyMsg(String)
98 | * @see org.mym.plog.config.PLogConfig.Builder#emptyMsgLevel(int)
99 | */
100 | public static void empty() {
101 | new LogRequest().level(getCurrentConfig().getEmptyMsgLevel())
102 | .msg(getCurrentConfig().getEmptyMsg())
103 | .stackOffset(1).print();
104 | }
105 |
106 | /**
107 | * A helper method to print a PLOG call stack trace here.
108 | * @since 2.0.0-beta4
109 | */
110 | public static void printStackTraceHere() {
111 | new LogRequest().level(Log.INFO).printTraceOnly().print();
112 | }
113 |
114 | /**
115 | * A recommended helper method useful when you just want to print objects using default format.
116 | * The log level for this method is defined as
Log.DEBUG
.
117 | *
118 | * @param params objects to print.
119 | */
120 | public static void objects(Object... params) {
121 | new LogRequest().level(Log.DEBUG).params(params).stackOffset(1).print();
122 | }
123 |
124 | /**
125 | * Print out json string representing by a JSONObject.
126 | * @deprecated use {@link #objects(Object...)} instead. This method will be removed in future
127 | * release.
128 | * @since 2.0.0
129 | */
130 | public static void json(JSONObject jsonObject) {
131 | new LogRequest().level(Log.DEBUG).params(jsonObject).stackOffset(1).print();
132 | }
133 |
134 | /**
135 | * Print exceptions in WARN level.
136 | */
137 | public static void throwable(Throwable throwable) {
138 | new LogRequest().level(Log.WARN).params(throwable).stackOffset(1).print();
139 | }
140 |
141 | /**
142 | * What a Terrible Failure: Report an exception that should never happen.
143 | */
144 | public static void wtf(Throwable throwable) {
145 | new LogRequest().level(Log.ERROR).params(throwable).stackOffset(1).print();
146 | }
147 |
148 | private static void checkInitOrUseDefaultConfig() {
149 | if (mConfig == null) {
150 | init(new PLogConfig.Builder().build());
151 | }
152 | }
153 |
154 | /**
155 | * Prepare printers; this method should always be called on application start because you should
156 | * set your own onIntercept logic using {@link Printer} interface.
157 | *
162 | *
163 | * Note that calling this method will clear all printers set before.
164 | *
165 | * @param printers printers to print logs; they are parallel from each other.
166 | */
167 | public static void prepare(Printer... printers) {
168 | LogEngine.setPrinters(printers);
169 | }
170 |
171 | /**
172 | * Append an extra printer to currently printer collection.
173 | *
174 | * @param printer cannot be null
175 | */
176 | public static void appendPrinter(@NonNull Printer printer) {
177 | LogEngine.appendPrinter(printer);
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/PrintLevel.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog;
2 |
3 | import android.support.annotation.IntDef;
4 | import android.util.Log;
5 |
6 | import java.lang.annotation.ElementType;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.RetentionPolicy;
9 | import java.lang.annotation.Target;
10 |
11 | /**
12 | * A utility annotation for log level.
13 | *
14 | * @author Muyangmin
15 | * @since 2.0.0
16 | */
17 | @Retention(RetentionPolicy.SOURCE)
18 | @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
19 | @IntDef({Log.VERBOSE, Log.DEBUG, Log.INFO, Log.WARN, Log.ERROR, Log.ASSERT})
20 | public @interface PrintLevel {
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/Printer.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 |
6 | /**
7 | * This interface and its subclasses define the onIntercept and output rules.
8 | * Please consider it as compose of logger and controller in PLog 1.x version;
9 | * or just like the "Tree" concept of Timber library.
10 | *
11 | * @since 2.0.0
12 | */
13 | @SuppressWarnings("unused")
14 | public interface Printer extends Interceptor {
15 |
16 | /**
17 | * Specify the formatter of this printer.
18 | * @return can be null. If so, msg would never be formatted.
19 | */
20 | @Nullable
21 | Formatter getFormatter();
22 |
23 | @Nullable
24 | Style getStyle();
25 |
26 | /**
27 | * Specify word wrapper, if needed.
28 | * @return the soft wrapper implementation; return null means disable soft wrap for this
29 | * printer. The constant implementation defined in {@link SoftWrapper} is strongly recommended.
30 | * @see SoftWrapper#WORD_BREAK_WRAPPER
31 | * @see SoftWrapper#WORD_LENGTH_WRAPPER
32 | */
33 | SoftWrapper getSoftWrapper();
34 |
35 | /**
36 | * Indicate how many characters should contains in one line; but only affect when soft wrap
37 | * is enabled. That is, {@link #getSoftWrapper()} returns not null.
38 | *
39 | * @return Should be a positive integer; otherwise you will get an argument exception.
40 | */
41 | int getMaxLengthPerLine();
42 |
43 | /**
44 | * Do the real output operation, e.g. call Log.x(), or write to file.
45 | * @param level print level of this log.
46 | * @param msg content of this log(already formatted and soft wrapped, if needed).
47 | */
48 | void print(@PrintLevel int level, @NonNull String tag, @NonNull String msg);
49 | }
50 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/SimpleCategory.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | /**
9 | * The simplest category implementation: it just return the value passed in the constructor.
10 | *
11 | * @since 2.0.0
12 | */
13 | public class SimpleCategory implements Category {
14 |
15 | /**
16 | * Use a static Map to cache same categories and avoid frequently object allocating.
17 | */
18 | private static final Map sCachedMap = new HashMap<>();
19 | private final String mName;
20 |
21 | private SimpleCategory(@NonNull String name) {
22 | this.mName = name;
23 | }
24 |
25 | //This class may be used by outer call
26 | @SuppressWarnings("WeakerAccess")
27 | @NonNull
28 | public static SimpleCategory obtain(@NonNull String name) {
29 | SimpleCategory category = sCachedMap.get(name);
30 | if (category == null) {
31 | category = new SimpleCategory(name);
32 | sCachedMap.put(name, category);
33 | }
34 | return category;
35 | }
36 |
37 | @NonNull
38 | @Override
39 | public String getName() {
40 | return mName;
41 | }
42 |
43 | @Override
44 | public boolean isSameAs(@NonNull String name) {
45 | return mName.equals(name);
46 | }
47 |
48 | @Override
49 | public boolean equals(Object obj) {
50 | if (obj instanceof Category) {
51 | return mName.equalsIgnoreCase(((Category) obj).getName());
52 | }
53 | return super.equals(obj);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/SoftWrapper.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import org.mym.plog.internal.WordLengthWrapper;
6 | import org.mym.plog.internal.WordBreakWrapper;
7 |
8 | /**
9 | * Provide soft wrap feature.
10 | *
11 | * @since 2.0.0
12 | */
13 | public interface SoftWrapper {
14 |
15 | SoftWrapper WORD_BREAK_WRAPPER = new WordBreakWrapper();
16 |
17 | SoftWrapper WORD_LENGTH_WRAPPER = new WordLengthWrapper();
18 |
19 | // int WRAP_LENGTH_SHORT = 100;
20 | // int WRAP_LENGTH_MIDDLE = 200;
21 | // int WRAP_LENGTH_LONG = 400;
22 |
23 | String wrapLine(@NonNull String input, int wrapLength);
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/Style.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog;
2 |
3 | import android.support.annotation.Nullable;
4 |
5 | /**
6 | * This interface and its subclasses defines how logs are decorated.
7 | *
8 | * @since 2.0.0
9 | */
10 | public interface Style {
11 |
12 | /**
13 | * Prefix of each log.
14 | * @return can be null
15 | */
16 | @Nullable
17 | String msgPrefix();
18 |
19 | /**
20 | * Suffix of each log.
21 | * @return can be null
22 | */
23 | @Nullable
24 | String msgSuffix();
25 |
26 | /**
27 | * the line header property is only effected when soft wrap is enabled.
28 | * @return the string of each line header, e.g. |.
29 | */
30 | @Nullable
31 | String lineHeader();
32 | }
33 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/config/PLogConfig.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog.config;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 | import android.text.TextUtils;
6 | import android.util.Log;
7 |
8 | import org.mym.plog.Interceptor;
9 | import org.mym.plog.PLog;
10 | import org.mym.plog.PrintLevel;
11 |
12 | /**
13 | * Configuration class for the whole library.
14 | *
15 | *
16 | * You can't call constructor directly; instead we recommend you to use Builder. All options
17 | * are documented in that class.
18 | *
19 | *
20 | * @see PLogConfig.Builder
21 | * @since 1.0.0
22 | */
23 | public class PLogConfig {
24 | // -------------- DEFAULT FIELDS BEGIN --------------
25 | private static final int DEFAULT_EMPTY_MSG_LEVEL = Log.DEBUG;
26 | private static final String DEFAULT_EMPTY_MSG = "Here executed.";
27 | private static final String DEFAULT_GLOBAL_TAG = "GlobalTag";
28 | // -------------- DEFAULT FIELDS END --------------
29 |
30 | /*
31 | * A very useful setting when user does NOT directly call `PLog.xxx` but called by
32 | * `PLogWrapper.xxx`, etc.
33 | * Be careful for this setting: if you pass a wrong value, all your line number maybe incorrect.
34 | * @since 1.4.0
35 | */
36 | private int globalStackOffset;
37 | private String globalTag;
38 | /*
39 | * If this config is set to true, then all tags would be appended after global tag.
40 | * @since 1.0.0
41 | */
42 | private boolean forceConcatGlobalTag;
43 | /*
44 | * If set to true, then use class name as tag. The concat global tag config is still valid.
45 | * @since 1.2.0
46 | */
47 | private boolean useAutoTag;
48 | private String emptyMsg;
49 | private int emptyMsgLevel;
50 | private boolean needLineNumber;
51 |
52 | /*
53 | * If set to true, all log message will contains current thread information.
54 | *
55 | * @since 2.0.0
56 | */
57 | private boolean needThreadInfo;
58 |
59 | /*
60 | * Global interceptor which would affect all printers, may be null.
61 | *
62 | * @since 2.0.0
63 | */
64 | private Interceptor globalInterceptor;
65 |
66 | /*
67 | * Define a max limit for recursive formatting. Currently only affect when using default
68 | * formatter, please see `formatter` module or documentation for more details.
69 | *
70 | * @since 2.0.0
71 | */
72 | private int maxRecursiveDepth;
73 |
74 | private PLogConfig(Builder builder) {
75 | globalStackOffset = builder.globalStackOffset;
76 | globalTag = builder.globalTag;
77 | forceConcatGlobalTag = builder.forceConcatGlobalTag;
78 | useAutoTag = builder.useAutoTag;
79 | emptyMsg = builder.emptyMsg;
80 | emptyMsgLevel = builder.emptyMsgLevel;
81 | needLineNumber = builder.needLineNumber;
82 | globalInterceptor = builder.globalInterceptor;
83 | needThreadInfo = builder.needThreadInfo;
84 | maxRecursiveDepth = builder.maxRecursiveDepth;
85 | }
86 |
87 | /**
88 | * Check whether a config is valid or not. This is useful when our library importing new
89 | * config items, and can prevent user's wrong usage.
90 | *
91 | * If all condition check pass, then this method do nothing; otherwise it would throw a
92 | * Runtime Exception.
93 | *
94 | *
95 | * @param config config object to detect
96 | * @throws RuntimeException maybe any subclass of RuntimeException if any assertion failed.
97 | */
98 | public static void checkConfigSafe(PLogConfig config) throws RuntimeException {
99 | if (config == null) {
100 | throw new NullPointerException("Customized config cannot be null!");
101 | }
102 | if (config.getEmptyMsg() == null) {
103 | throw new NullPointerException("Empty msg cannot be null!");
104 | }
105 | if (config.getGlobalTag() == null) {
106 | throw new NullPointerException("Global tag cannot be null!");
107 | }
108 | }
109 |
110 | /**
111 | * Create a new Builder.
112 | */
113 | @SuppressWarnings("unused")
114 | public static Builder newBuilder() {
115 | return new Builder();
116 | }
117 |
118 | /**
119 | * Create a new builder and clone all options in the argument.
120 | */
121 | @SuppressWarnings("unused")
122 | public static Builder newBuilder(PLogConfig copy) {
123 | return new Builder(copy);
124 | }
125 |
126 | public int getGlobalStackOffset() {
127 | return globalStackOffset;
128 | }
129 |
130 | public String getEmptyMsg() {
131 | return emptyMsg;
132 | }
133 |
134 | @PrintLevel
135 | public int getEmptyMsgLevel() {
136 | return emptyMsgLevel;
137 | }
138 |
139 | public String getGlobalTag() {
140 | return globalTag;
141 | }
142 |
143 | public boolean isForceConcatGlobalTag() {
144 | return forceConcatGlobalTag;
145 | }
146 |
147 | public boolean isUseAutoTag() {
148 | return useAutoTag;
149 | }
150 |
151 | @Nullable
152 | public Interceptor getGlobalInterceptor() {
153 | return globalInterceptor;
154 | }
155 |
156 | public boolean isNeedLineNumber() {
157 | return needLineNumber;
158 | }
159 |
160 | public boolean isNeedThreadInfo() {
161 | return needThreadInfo;
162 | }
163 |
164 | public int getMaxRecursiveDepth() {
165 | return maxRecursiveDepth;
166 | }
167 |
168 | @SuppressWarnings("unused")
169 | public static final class Builder {
170 | private String globalTag;
171 | private boolean forceConcatGlobalTag;
172 | private boolean useAutoTag;
173 | @PrintLevel
174 | private int emptyMsgLevel;
175 | private String emptyMsg;
176 | private boolean needLineNumber;
177 | private int globalStackOffset;
178 | @Nullable
179 | private Interceptor globalInterceptor;
180 | private boolean needThreadInfo;
181 | private int maxRecursiveDepth;
182 |
183 | /**
184 | * Create a builder, you can also use static method {@link #newBuilder()}.
185 | */
186 | public Builder() {
187 | //DEFAULT FIELDS IS INIT HERE
188 | useAutoTag = true;
189 | maxRecursiveDepth = 2;
190 | }
191 |
192 | /**
193 | * Create a builder.
194 | */
195 | public Builder(PLogConfig copy) {
196 | this.globalStackOffset = copy.globalStackOffset;
197 | this.globalTag = copy.globalTag;
198 | this.forceConcatGlobalTag = copy.forceConcatGlobalTag;
199 | this.useAutoTag = copy.useAutoTag;
200 | this.emptyMsg = copy.emptyMsg;
201 | this.emptyMsgLevel = copy.emptyMsgLevel;
202 | this.globalInterceptor = copy.globalInterceptor;
203 | this.needLineNumber = copy.needLineNumber;
204 | this.needThreadInfo = copy.needThreadInfo;
205 | this.maxRecursiveDepth = copy.maxRecursiveDepth;
206 | }
207 |
208 | /**
209 | * Set global tag for all logs, param should not be null.
210 | */
211 | public Builder globalTag(@NonNull String val) {
212 | globalTag = val;
213 | return this;
214 | }
215 |
216 | /**
217 | * If set to true, all tags will be appended after global tag, regardless of it is an
218 | * auto tag or explicit tag.
219 | *
220 | * We recommend you set to true if you've set a `global tag`.
221 | */
222 | public Builder forceConcatGlobalTag(boolean val) {
223 | forceConcatGlobalTag = val;
224 | return this;
225 | }
226 |
227 | /**
228 | * If set to true(as default option), use class name as tag.
229 | *
230 | * @since 1.2.0
231 | */
232 | public Builder useAutoTag(boolean val) {
233 | useAutoTag = val;
234 | return this;
235 | }
236 |
237 | /**
238 | * Set level for empty log. Default is
Log.DEBUG
.
239 | *
240 | * @param val Must be one of
241 | * {@link Log#VERBOSE}, {@link Log#DEBUG}, {@link Log#INFO},
242 | * {@link Log#WARN}, {@link Log#ERROR}.
243 | * Otherwise this param is ignored.
244 | */
245 | public Builder emptyMsgLevel(@PrintLevel int val) {
246 | emptyMsgLevel = val;
247 | return this;
248 | }
249 |
250 | /**
251 | * Set default message for log printed by calling {@link PLog#empty()}.
252 | */
253 | public Builder emptyMsg(@NonNull String val) {
254 | emptyMsg = val;
255 | return this;
256 | }
257 |
258 | /**
259 | * If set to true, the line number info will be printed in log message.
260 | * @deprecated This method name is a little ambiguous, and will be removed in future
261 | * release. Use {@link #needLineNumber(boolean)} instead.
262 | */
263 | public Builder keepLineNumber(boolean val) {
264 | return needLineNumber(val);
265 | }
266 |
267 | /**
268 | * If set to true, the source line number info will be printed in log message.
269 | */
270 | public Builder needLineNumber(boolean val) {
271 | needLineNumber = val;
272 | return this;
273 | }
274 |
275 | /**
276 | * Set a global stack offset.
277 | *
278 | * The recommended value is your wrapper level to `PLog` calls.
279 | * If you use PLog.xxx() directly, you DO NOT need this method.
280 | *
281 | */
282 | public Builder globalStackOffset(int val) {
283 | globalStackOffset = val;
284 | return this;
285 | }
286 |
287 | /**
288 | * Register a global interceptor, useful for handle different app environments etc.
289 | */
290 | public Builder globalInterceptor(@Nullable Interceptor val) {
291 | globalInterceptor = val;
292 | return this;
293 | }
294 |
295 | /**
296 | * @deprecated This method name is a little ambiguous, and will be removed in future
297 | * release. Use {@link #needThreadInfo(boolean)} instead.
298 | */
299 | public Builder keepThreadInfo(boolean val) {
300 | return needThreadInfo(val);
301 | }
302 |
303 | /**
304 | * If set to true, thread info will be printed into log message.
305 | */
306 | public Builder needThreadInfo(boolean val) {
307 | needThreadInfo = val;
308 | return this;
309 | }
310 |
311 | /**
312 | * Set max depth limit when recursively format objects. If you do not use `formatter`
313 | * dependency, you don't need this option as well.
314 | *
315 | * @param depth should be positive. pass non-positive value will totally disable
316 | * recursive formatting.
317 | * @since 2.0.0
318 | */
319 | public Builder maxRecursiveDepth(int depth) {
320 | maxRecursiveDepth = depth;
321 | return this;
322 | }
323 |
324 | public PLogConfig build() {
325 | //check fields which can be unsafe
326 | if (emptyMsgLevel < Log.VERBOSE || emptyMsgLevel > Log.ASSERT) {
327 | emptyMsgLevel = DEFAULT_EMPTY_MSG_LEVEL;
328 | }
329 |
330 | if (TextUtils.isEmpty(emptyMsg)) {
331 | emptyMsg = DEFAULT_EMPTY_MSG;
332 | }
333 |
334 | if (globalTag == null) {
335 | globalTag = DEFAULT_GLOBAL_TAG;
336 | }
337 |
338 | return new PLogConfig(this);
339 | }
340 | }
341 | }
342 |
--------------------------------------------------------------------------------
/plog/src/main/java/org/mym/plog/internal/WordBreakWrapper.java:
--------------------------------------------------------------------------------
1 | package org.mym.plog.internal;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import org.mym.plog.SoftWrapper;
6 |
7 | import java.util.regex.Matcher;
8 | import java.util.regex.Pattern;
9 |
10 | /**
11 | * This is a copy of WordUtils in apache commons-lang, version 3.5.
12 | * Disclaimer:
13 | * 1. PLog use this only for providing more stable soft-wrap feature without include dependency.
14 | * 2. PLog also respect APACHE licence.
15 | *
Wraps a single line of text, identifying words by wrapOn.
26 | *
27 | *
Leading spaces on a new line are stripped.
28 | * Trailing spaces are not stripped.
29 | *
30 | *
31 | *
32 | *
input
33 | *
wrapLength
34 | *
newLineString
35 | *
wrapLongWords
36 | *
wrapOn
37 | *
result
38 | *
39 | *
40 | *
null
41 | *
*
42 | *
*
43 | *
true/false
44 | *
*
45 | *
null
46 | *
47 | *
48 | *
""
49 | *
*
50 | *
*
51 | *
true/false
52 | *
*
53 | *
""
54 | *
55 | *
56 | *
"Here is one line of text that is going to be wrapped after 20 columns."
57 | *
20
58 | *
"\n"
59 | *
true/false
60 | *
" "
61 | *
"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."
62 | *
63 | *
64 | *
"Here is one line of text that is going to be wrapped after 20 columns."
65 | *
20
66 | *
"<br />"
67 | *
true/false
68 | *
" "
69 | *
"Here is one line of<br />text that is going<br />to be wrapped after<
70 | * br />20 columns."
71 | *
72 | *
73 | *
"Here is one line of text that is going to be wrapped after 20 columns."
74 | *
20
75 | *
null
76 | *
true/false
77 | *
" "
78 | *
"Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be
79 | * wrapped after" + systemNewLine + "20 columns."
80 | *
81 | *
82 | *
"Click here to jump to the commons website - http://commons.apache.org"
83 | *
20
84 | *
"\n"
85 | *
false
86 | *
" "
87 | *
"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"
88 | *
89 | *
90 | *
"Click here to jump to the commons website - http://commons.apache.org"
91 | *
20
92 | *
"\n"
93 | *
true
94 | *
" "
95 | *
"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"
96 | *
97 | *
98 | *
"flammable/inflammable"
99 | *
20
100 | *
"\n"
101 | *
true
102 | *
"/"
103 | *
"flammable\ninflammable"
104 | *
105 | *
106 | *
107 | * @param str the String to be word wrapped, may be null
108 | * @param wrapLength the column to wrap the words at, less than 1 is treated as 1
109 | * @param newLineStr the string to insert for a new line,
110 | * null uses the system property line separator
111 | * @param wrapLongWords true if long words (such as URLs) should be wrapped
112 | * @param wrapOn regex expression to be used as a breakable characters,
113 | * if blank string is provided a space character will be used
114 | * @return a line with newlines inserted, null if null input
115 | */
116 | public static String wrap(final String str, int wrapLength, String newLineStr, final boolean
117 | wrapLongWords, String wrapOn) {
118 | if (str == null) {
119 | return null;
120 | }
121 | if (newLineStr == null) {
122 | newLineStr = LINE_SEPARATOR;
123 | }
124 | if (wrapLength < 1) {
125 | wrapLength = 1;
126 | }
127 | if (isBlank(wrapOn)) {
128 | wrapOn = " ";
129 | }
130 | final Pattern patternToWrapOn = Pattern.compile(wrapOn);
131 | final int inputLineLength = str.length();
132 | int offset = 0;
133 | final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32);
134 |
135 | while (offset < inputLineLength) {
136 | int spaceToWrapAt = -1;
137 | Matcher matcher = patternToWrapOn.matcher(str.substring(offset, Math.min(offset +
138 | wrapLength + 1, inputLineLength)));
139 | if (matcher.find()) {
140 | //这里会跳过每一行的前导断词符,即前导空格
141 | if (matcher.start() == 0) {
142 | offset += matcher.end();
143 | continue;
144 | } else {
145 | spaceToWrapAt = matcher.start() + offset;
146 | }
147 | }
148 |
149 | // only last line without leading spaces is left
150 | if (inputLineLength - offset <= wrapLength) {
151 | break;
152 | }
153 |
154 | while (matcher.find()) {
155 | spaceToWrapAt = matcher.start() + offset;
156 | }
157 |
158 | if (spaceToWrapAt >= offset) {
159 | // normal case
160 | wrappedLine.append(str.substring(offset, spaceToWrapAt));
161 | wrappedLine.append(newLineStr);
162 | offset = spaceToWrapAt + 1;
163 |
164 | } else {
165 | // really long word or URL
166 | if (wrapLongWords) {
167 | // wrap really long word one line at a time
168 | wrappedLine.append(str.substring(offset, wrapLength + offset));
169 | wrappedLine.append(newLineStr);
170 | offset += wrapLength;
171 | } else {
172 | // do not wrap really long word, just extend beyond limit
173 | matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength));
174 | if (matcher.find()) {
175 | spaceToWrapAt = matcher.start() + offset + wrapLength;
176 | }
177 |
178 | if (spaceToWrapAt >= 0) {
179 | wrappedLine.append(str.substring(offset, spaceToWrapAt));
180 | wrappedLine.append(newLineStr);
181 | offset = spaceToWrapAt + 1;
182 | } else {
183 | wrappedLine.append(str.substring(offset));
184 | offset = inputLineLength;
185 | }
186 | }
187 | }
188 | }
189 |
190 | // Whatever is left in line is short enough to just pass through
191 | wrappedLine.append(str.substring(offset));
192 |
193 | return wrappedLine.toString();
194 | }
195 |
196 | /**
197 | *
198 | * Gets a System property, defaulting to {@code null} if the property cannot be read.
199 | *
200 | *
201 | * If a {@code SecurityException} is caught, the return value is {@code null} and a message
202 | * is written to
203 | * {@code System.err}.
204 | *
205 | *
206 | * @param property the system property name
207 | * @return the system property value or {@code null} if a security problem occurs
208 | */
209 | private static String getSystemProperty(final String property) {
210 | try {
211 | return System.getProperty(property);
212 | } catch (final SecurityException ex) {
213 | // we are not allowed to look at this property
214 | System.err.println("Caught a SecurityException reading the system property '" + property
215 | + "'; the SystemUtils property value will default to null.");
216 | return null;
217 | }
218 | }
219 |
220 | // -----------------------------------------------------------------------
221 |
222 | /**
223 | *
Checks if a CharSequence is empty (""), null or whitespace only.
224 | *
225 | *
Whitespace is defined by {@link Character#isWhitespace(char)}.