Repository: vipshop/vjtools
Branch: master
Commit: 2f780edd504e
Files: 317
Total size: 1022.4 KB
Directory structure:
gitextract_3z1lwxqk/
├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── docs/
│ ├── .nojekyll
│ ├── README.md
│ ├── _coverpage.md
│ ├── _sidebar.md
│ ├── index.html
│ ├── other/
│ │ └── othertools.md
│ └── standard/
│ ├── README.md
│ ├── _sidebar.md
│ ├── ali.md
│ ├── chapter01.md
│ ├── chapter02.md
│ ├── chapter03.md
│ ├── chapter04.md
│ ├── chapter05.md
│ ├── chapter06.md
│ ├── chapter07.md
│ ├── chapter08.md
│ ├── chapter09.md
│ ├── chapter10.md
│ ├── chapter11.md
│ ├── chapter12.md
│ ├── merge.bat
│ └── merge.sh
├── pom.xml
├── standard/
│ ├── README.md
│ ├── formatter/
│ │ ├── README.md
│ │ ├── vjtools-code-conventions-eclipse.xml
│ │ └── vjtools-code-conventions-idea.xml
│ └── sonar-vj/
│ ├── README.md
│ ├── pom.xml
│ └── src/
│ └── main/
│ ├── java/
│ │ └── com/
│ │ └── vip/
│ │ └── vjkit/
│ │ └── sonarvj/
│ │ ├── SonarCheckRegistrar.java
│ │ ├── SonarDefinition.java
│ │ ├── SonarPlugin.java
│ │ ├── SonarRulesList.java
│ │ └── checks/
│ │ ├── BadConstantNameCheck.java
│ │ ├── CatchUsesExceptionWithContextCheck.java
│ │ ├── HardcodedIpCheck.java
│ │ ├── MissingCurlyBracesCheck.java
│ │ ├── NoSonarCheck.java
│ │ ├── OperatorPrecedenceCheck.java
│ │ ├── UnusedMethodParameterCheck.java
│ │ └── UnusedPrivateFieldCheck.java
│ └── resources/
│ └── com/
│ └── vip/
│ └── java/
│ └── rules/
│ ├── S1068_java.html
│ ├── S1068_java.json
│ ├── S115_java.html
│ ├── S115_java.json
│ ├── S1166_java.html
│ ├── S1166_java.json
│ ├── S1172_java.html
│ ├── S1172_java.json
│ ├── S121_java.html
│ ├── S121_java.json
│ ├── S1291_java.html
│ ├── S1291_java.json
│ ├── S1313_java.html
│ ├── S1313_java.json
│ ├── S864_java.html
│ └── S864_java.json
├── vjdump/
│ ├── README.md
│ ├── README_EN.md
│ └── vjdump.sh
├── vjkit/
│ ├── README.md
│ ├── docs/
│ │ ├── data_masking.md
│ │ └── direct_3rd.md
│ ├── pom.xml
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── vip/
│ │ │ └── vjtools/
│ │ │ └── vjkit/
│ │ │ ├── base/
│ │ │ │ ├── BooleanUtil.java
│ │ │ │ ├── EnumUtil.java
│ │ │ │ ├── ExceptionUtil.java
│ │ │ │ ├── MoreValidate.java
│ │ │ │ ├── ObjectUtil.java
│ │ │ │ ├── Platforms.java
│ │ │ │ ├── PropertiesUtil.java
│ │ │ │ ├── RuntimeUtil.java
│ │ │ │ ├── SystemPropertiesUtil.java
│ │ │ │ ├── ValueValidator.java
│ │ │ │ ├── annotation/
│ │ │ │ │ ├── NotNull.java
│ │ │ │ │ ├── Nullable.java
│ │ │ │ │ └── VisibleForTesting.java
│ │ │ │ └── type/
│ │ │ │ ├── CloneableException.java
│ │ │ │ ├── CloneableRuntimeException.java
│ │ │ │ ├── Pair.java
│ │ │ │ ├── Triple.java
│ │ │ │ └── UncheckedException.java
│ │ │ ├── collection/
│ │ │ │ ├── ArrayUtil.java
│ │ │ │ ├── CollectionUtil.java
│ │ │ │ ├── ListUtil.java
│ │ │ │ ├── MapUtil.java
│ │ │ │ ├── QueueUtil.java
│ │ │ │ ├── SetUtil.java
│ │ │ │ └── type/
│ │ │ │ ├── ConcurrentHashSet.java
│ │ │ │ ├── MoreLists.java
│ │ │ │ ├── MoreMaps.java
│ │ │ │ ├── MoreQueues.java
│ │ │ │ ├── SortedArrayList.java
│ │ │ │ └── primitive/
│ │ │ │ ├── IntObjectHashMap.java
│ │ │ │ ├── IntObjectMap.java
│ │ │ │ ├── LongObjectHashMap.java
│ │ │ │ └── LongObjectMap.java
│ │ │ ├── concurrent/
│ │ │ │ ├── Concurrents.java
│ │ │ │ ├── ThreadDumpper.java
│ │ │ │ ├── ThreadUtil.java
│ │ │ │ ├── jsr166e/
│ │ │ │ │ ├── LongAdder.java
│ │ │ │ │ └── Striped64.java
│ │ │ │ ├── limiter/
│ │ │ │ │ ├── RateLimiterUtil.java
│ │ │ │ │ ├── Sampler.java
│ │ │ │ │ └── TimeIntervalLimiter.java
│ │ │ │ ├── threadpool/
│ │ │ │ │ ├── AbortPolicyWithReport.java
│ │ │ │ │ ├── QueuableCachedThreadPool.java
│ │ │ │ │ ├── ThreadPoolBuilder.java
│ │ │ │ │ └── ThreadPoolUtil.java
│ │ │ │ └── type/
│ │ │ │ ├── BasicFuture.java
│ │ │ │ └── ThreadLocalContext.java
│ │ │ ├── datamasking/
│ │ │ │ ├── DataMask.java
│ │ │ │ ├── DataMaskJsonFilter.java
│ │ │ │ ├── MaskMapping.java
│ │ │ │ ├── MaskStrategy.java
│ │ │ │ ├── Sensitive.java
│ │ │ │ ├── SensitiveType.java
│ │ │ │ └── strategy/
│ │ │ │ ├── EmailMask.java
│ │ │ │ ├── HashMask.java
│ │ │ │ ├── NameMask.java
│ │ │ │ └── PartMask.java
│ │ │ ├── id/
│ │ │ │ └── IdUtil.java
│ │ │ ├── io/
│ │ │ │ ├── FilePathUtil.java
│ │ │ │ ├── FileTreeWalker.java
│ │ │ │ ├── FileUtil.java
│ │ │ │ ├── IOUtil.java
│ │ │ │ ├── ResourceUtil.java
│ │ │ │ ├── URLResourceUtil.java
│ │ │ │ └── type/
│ │ │ │ └── StringBuilderWriter.java
│ │ │ ├── logging/
│ │ │ │ └── PerformanceUtil.java
│ │ │ ├── mapper/
│ │ │ │ ├── BeanMapper.java
│ │ │ │ ├── JsonMapper.java
│ │ │ │ └── XmlMapper.java
│ │ │ ├── net/
│ │ │ │ ├── IPUtil.java
│ │ │ │ └── NetUtil.java
│ │ │ ├── number/
│ │ │ │ ├── MathUtil.java
│ │ │ │ ├── MoneyUtil.java
│ │ │ │ ├── NumberUtil.java
│ │ │ │ ├── RandomUtil.java
│ │ │ │ ├── SizeUnit.java
│ │ │ │ └── UnitConverter.java
│ │ │ ├── reflect/
│ │ │ │ ├── AnnotationUtil.java
│ │ │ │ ├── ClassLoaderUtil.java
│ │ │ │ ├── ClassUtil.java
│ │ │ │ └── ReflectionUtil.java
│ │ │ ├── security/
│ │ │ │ └── CryptoUtil.java
│ │ │ ├── text/
│ │ │ │ ├── Charsets.java
│ │ │ │ ├── CsvUtil.java
│ │ │ │ ├── EncodeUtil.java
│ │ │ │ ├── EscapeUtil.java
│ │ │ │ ├── HashUtil.java
│ │ │ │ ├── MoreStringUtil.java
│ │ │ │ ├── StringBuilderHolder.java
│ │ │ │ ├── TextValidator.java
│ │ │ │ └── WildcardMatcher.java
│ │ │ └── time/
│ │ │ ├── CachingDateFormatter.java
│ │ │ ├── ClockUtil.java
│ │ │ ├── DateFormatUtil.java
│ │ │ └── DateUtil.java
│ │ └── resources/
│ │ └── sys_data_mask.properties
│ └── test/
│ ├── java/
│ │ └── com/
│ │ └── vip/
│ │ └── vjtools/
│ │ ├── test/
│ │ │ ├── data/
│ │ │ │ └── RandomData.java
│ │ │ ├── log/
│ │ │ │ ├── LogbackListAppender.java
│ │ │ │ └── LogbackListAppenderTest.java
│ │ │ └── rule/
│ │ │ └── TestProgress.java
│ │ └── vjkit/
│ │ ├── base/
│ │ │ ├── BooleanUtilTest.java
│ │ │ ├── EnumUtilTest.java
│ │ │ ├── ExceptionUtilTest.java
│ │ │ ├── MoreValidateTest.java
│ │ │ ├── ObjectUtilTest.java
│ │ │ ├── PairTest.java
│ │ │ ├── PlatformsTest.java
│ │ │ ├── PropertiesUtilTest.java
│ │ │ ├── RuntimeUtilTest.java
│ │ │ ├── SystemPropertiesUtilTest.java
│ │ │ └── ValueValidatorTest.java
│ │ ├── collection/
│ │ │ ├── ArrayUtilTest.java
│ │ │ ├── CollectionUtilTest.java
│ │ │ ├── ListUtilTest.java
│ │ │ ├── MapUtilTest.java
│ │ │ ├── QueueUtilTest.java
│ │ │ ├── SetUtilTest.java
│ │ │ └── type/
│ │ │ ├── ConcurrentHashSetTest.java
│ │ │ └── SortedArrayListTest.java
│ │ ├── concurrent/
│ │ │ ├── ConcurrentsTest.java
│ │ │ ├── ThreadDumpperTest.java
│ │ │ ├── ThreadUtilTest.java
│ │ │ ├── limiter/
│ │ │ │ ├── RateLimiterUtilTest.java
│ │ │ │ ├── SamplerTest.java
│ │ │ │ └── TimeIntervalLimiterTest.java
│ │ │ ├── threadpool/
│ │ │ │ ├── AbortPolicyWithReportTest.java
│ │ │ │ ├── QueuableCachedThreadPoolTest.java
│ │ │ │ ├── ThreadPoolBuilderTest.java
│ │ │ │ └── ThreadPoolUtilTest.java
│ │ │ └── type/
│ │ │ ├── BasicFutureTest.java
│ │ │ └── ThreadLocalContextTest.java
│ │ ├── datamasking/
│ │ │ ├── DataMaskJsonFilterTest.java
│ │ │ ├── DataMaskTest.java
│ │ │ ├── MaskMappingTest.java
│ │ │ ├── data/
│ │ │ │ ├── TestChild.java
│ │ │ │ ├── TestData.java
│ │ │ │ ├── TestParent.java
│ │ │ │ └── TestUserMapingData.java
│ │ │ └── strategy/
│ │ │ ├── EmailMaskTest.java
│ │ │ ├── HashMaskTest.java
│ │ │ ├── NameMaskTest.java
│ │ │ └── PartMaskTest.java
│ │ ├── id/
│ │ │ └── IdUtilTest.java
│ │ ├── io/
│ │ │ ├── FilePathUtilTest.java
│ │ │ ├── FileTreeWalkerTest.java
│ │ │ ├── FileUtilTest.java
│ │ │ ├── IOUtilTest.java
│ │ │ ├── ResourceUtilTest.java
│ │ │ └── URLResourceTest.java
│ │ ├── logging/
│ │ │ └── PerformanceUtilsTest.java
│ │ ├── mapper/
│ │ │ ├── BeanMapperTest.java
│ │ │ ├── JsonMapperTest.java
│ │ │ └── XmlMapperTest.java
│ │ ├── net/
│ │ │ ├── IPUtilTest.java
│ │ │ └── NetUtilTest.java
│ │ ├── number/
│ │ │ ├── MathUtilTest.java
│ │ │ ├── MoneyUtilTest.java
│ │ │ ├── NumberUtilTest.java
│ │ │ ├── RandomUtilTest.java
│ │ │ └── UnitConverterTest.java
│ │ ├── reflect/
│ │ │ ├── ClassUtilTest.java
│ │ │ ├── ClassloaderUtilTest.java
│ │ │ └── ReflectionUtilTest.java
│ │ ├── security/
│ │ │ └── CryptoUtilTest.java
│ │ ├── text/
│ │ │ ├── CsvUtilTest.java
│ │ │ ├── EncodeUtilTest.java
│ │ │ ├── EscapeUtilTest.java
│ │ │ ├── HashUtilTest.java
│ │ │ ├── MoreStringUtilTest.java
│ │ │ ├── StringBuilderHolderTest.java
│ │ │ ├── TextValidatorTest.java
│ │ │ └── WildcardMatcherTest.java
│ │ └── time/
│ │ ├── CachingDatFormatterTest.java
│ │ ├── ClockUtilTest.java
│ │ ├── DateFormatUtilTest.java
│ │ └── DateUtilTest.java
│ └── resources/
│ ├── application.properties
│ ├── data_mask.properties
│ ├── logback-test.xml
│ └── test.txt
├── vjmap/
│ ├── README.md
│ ├── README_EN.md
│ ├── pom.xml
│ └── src/
│ └── main/
│ ├── assembly/
│ │ ├── distribution.xml
│ │ ├── vjmap.bat
│ │ └── vjmap.sh
│ └── java/
│ └── com/
│ └── vip/
│ └── vjtools/
│ └── vjmap/
│ ├── ClassStats.java
│ ├── ResultPrinter.java
│ ├── VJMap.java
│ ├── oops/
│ │ ├── GenAddressAccessor.java
│ │ ├── HeapHistogramVisitor.java
│ │ ├── HeapUtils.java
│ │ ├── LoadedClassAccessor.java
│ │ ├── OldgenAccessor.java
│ │ └── SurvivorAccessor.java
│ └── utils/
│ ├── FormatUtils.java
│ ├── ProgressNotifier.java
│ └── TimeController.java
├── vjmxcli/
│ ├── README.md
│ ├── pom.xml
│ └── src/
│ └── main/
│ ├── assembly/
│ │ ├── distribution.xml
│ │ ├── vjmxcli.bat
│ │ └── vjmxcli.sh
│ └── java/
│ └── com/
│ └── vip/
│ └── vjtools/
│ └── jmx/
│ ├── Client.java
│ ├── ExtraCommand.java
│ └── GCutilExpression.java
├── vjstar/
│ ├── README.md
│ ├── pom.xml
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── vip/
│ │ │ └── vjstar/
│ │ │ ├── .gitkeep
│ │ │ ├── gc/
│ │ │ │ ├── CleanUpScheduler.java
│ │ │ │ └── ProactiveGcTask.java
│ │ │ └── window/
│ │ │ ├── AtomicBitSet.java
│ │ │ ├── RequestSlidingWindow.java
│ │ │ └── TimeSlidingWindow.java
│ │ └── script/
│ │ ├── docker-cpus/
│ │ │ └── README.md
│ │ └── jvm-options/
│ │ └── jvm-options.sh
│ └── test/
│ └── java/
│ └── com/
│ └── vip/
│ └── vjstar/
│ ├── .gitkeep
│ ├── gc/
│ │ ├── Enchanter.java
│ │ └── ProactiveGcTaskDemo.java
│ └── window/
│ ├── RequestSlidingWindowTest.java
│ └── TimeSlidingWindowTest.java
└── vjtop/
├── README.md
├── README_EN.md
├── pom.xml
└── src/
├── main/
│ ├── assembly/
│ │ ├── distribution.xml
│ │ ├── vjtop.bat
│ │ └── vjtop.sh
│ └── java/
│ └── com/
│ └── vip/
│ └── vjtools/
│ └── vjtop/
│ ├── InteractiveTask.java
│ ├── ThreadPrinter.java
│ ├── TopThreadInfo.java
│ ├── VJTop.java
│ ├── VMDetailView.java
│ ├── VMInfo.java
│ ├── WarningRule.java
│ ├── data/
│ │ ├── PerfData.java
│ │ ├── ProcFileData.java
│ │ └── jmx/
│ │ ├── JmxBufferPoolManager.java
│ │ ├── JmxClient.java
│ │ ├── JmxGarbageCollectorManager.java
│ │ └── JmxMemoryPoolManager.java
│ └── util/
│ ├── Formats.java
│ ├── LongObjectHashMap.java
│ ├── LongObjectMap.java
│ ├── OptionAdvanceParser.java
│ ├── SelectPid.java
│ └── Utils.java
└── test/
└── java/
└── com/
└── vip/
└── vjtools/
└── vjtop/
└── util/
├── FormatsTest.java
└── UtilsTest.java
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Eclipse project files
.classpath
.project
.settings/
# Intellij project files
*.iml
.idea/
# Otherse
*.log
target/
vip-java-standard.md
release.sh
vjmap/vjmap/
vjtop/vjtop/
vjmxcli/vjmxcli
================================================
FILE: .travis.yml
================================================
language: java
jdk:
- openjdk9
branches:
only:
- master
script: mvn clean package
================================================
FILE: LICENSE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2006-2012] [www.springside.org.cn]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
 [](https://travis-ci.org/vipshop/vjtools)
主力于Java的唯品会,关于Java的一些小家底。
各位看官看着是好的,烦请“Star”。
[1.0.8版](https://github.com/vipshop/vjtools/releases/tag/v.1.0.8) - 2018.9.24
## Java Standard
| Project | Description |
| -------- | -------- |
| [standard](https://vipshop.github.io/vjtools/#/standard/) | 唯品会Java开发手册 |
| [code formatter](/standard/formatter) | IDE格式化模板 |
| [sonar rule](/standard/sonar-vj) | Sonar规则定制示例 |
## Java Core Library
| Project | Description |
| -------- | -------- |
| [vjkit](/vjkit) | 关于文本,集合,并发等基础功能的核心类库 |
| [vjstar](/vjstar) | 关于后端应用的性能、可用性的最佳实践 |
## Java Tools
| Project | Description | Manual |
| -------- | -------- | -------- |
| [vjtop](/vjtop) | 观察JVM进程指标及其繁忙线程 | [Chinese](/vjtop/README.md)|
| [vjmap](/vjmap) | JMAP的分代打印版 |[Chinese](/vjmap/README.md)|
| [vjdump](/vjdump) | 线上紧急收集JVM数据脚本 | [Chinese](/vjdump/README.md), [English](/vjdump/README_EN.md)|
| [vjmxcli](/vjmxcli) | JMX 查看工具 | [Chinese](/vjmxcli/README.md)|
视频:[《VJTools如何利用佛性技术玩转JVM》](http://kai.vkaijiang.com/product/course?courseID=120897)
文档:[《入门科普,围绕JVM的各种外挂技术》](https://mp.weixin.qq.com/s/cwU2rLOuwock048rKBz3ew)
其他直接使用的工具,见[常用工具](docs/other/othertools.md)
## Contributing
VJTools官方微信讨论群,请搜索微信号viptech128(唯技术),添加好友后加入。
所有报Bug、建议与咨询,请在[Issues](https://github.com/vipshop/vjtools/issues)发起;所有代码提交,请走[Pull Request](https://github.com/vipshop/vjtools/pulls)流程。
对于优秀的代码提交和建议,唯品会将不吝发挥电商本色,给予[vip.com](https://www.vip.com)购物卡的奖励 !!!
## Developers
唯品会团队: [江南白衣](http://calvin1978.blogcn.com), [郑德惠](https://github.com/zhengdehui), [黄云斌](https://github.com/huangyunbin), [梁耀曾](https://github.com/AJ-Liang), [林铭恺](https://github.com/acxlam), [李炫彬](https://github.com/lixuanbin) , [张晓玲](https://github.com/hjzhangxiaoling)
曾经一起战斗: [杨镌颖@阿里](https://github.com/yangjuanying), 陈维治@阿里
================================================
FILE: docs/.nojekyll
================================================
================================================
FILE: docs/README.md
================================================
All about java in vip.com
- Standard
- Core Libraries
- Tools
================================================
FILE: docs/_coverpage.md
================================================

- Standard
- Core Libraries
- Tools
[GitHub Project](https://github.com/vipshop/vjtools)
[Java Coding Guidelines](standard/)
================================================
FILE: docs/_sidebar.md
================================================
- [GitHub Project](https://github.com/vipshop/vjtools)
- [Java Coding Guidelines](standard/)
================================================
FILE: docs/index.html
================================================
`的选择| |增加规则|
| 11. EnumMap| |增加规则|
| | 2. ArrayList的subList结果|删除规则|
| | 6. 泛型通配符|删除规则|
| | 12.合理利用好集合的有序性|删除规则|
| | 13.利用Set元素唯一的特性|删除规则|
|12.Array 与 List互转的|使用集合转数组的方法,必须使用集合的toArray|某位老大的测试,new String[0]也不错|
----
## (九) 并发处理: 并发与多线程
对应 [阿里规范《并发处理》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/并发处理.md)
| VIP 规范 | 阿里规范 | 修改|
| -------- | -------- |-------- |
|1. 指定线程名 | |扩写规则|
|4. 正确停止线程 | |扩写规则|
|5. 编写可中断的Runnable| |增加规则|
|6. Runnable中必须捕获一切异常 |9.多线程并行处理定时任务 |扩写规则|
|7. 全局变量的线程安全 | |扩写规则|
|10. 选择分离锁, 分散锁甚至无锁的数据结构| |增加规则|
|13. volatile修饰符,AtomicXX系列的正确使用 | |扩写规则|
| |8.并发修改同一记录时,需要加锁 |删除规则|
| |10.使用CountDownLatch进行异步转同步操作 |删除规则|
| |14.HashMap在容量不够进行resize|移到《集合规约》一章|
|14. 延时初始化的正确写法 |12.双重检查锁 |冲突规则|
----
## (十) 异常处理
对应 [阿里规范《异常处理》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/异常处理.md)
| VIP 规范 | 阿里规范 | 修改|
| -------- | -------- |-------- |
|2.在特定场合,避免每次构造异常| |增加规则|
|5.异常抛出的原则| |增加规则|
|6.异常捕获的原则| |增加规则|
|7.异常处理的原则| |增加规则|
| |8.捕获异常与抛异常,必须是完全匹配 |删除规则|
| |12.对于公司外的开放接口必须使用“错误码” |删除规则|
| |13.DRY原则 |删除规则,为什么会出现在这章,太著名了|
| |9.返回值可以为null |移到《方法设计》一章|
| |10. 【推荐】防止NPE,是程序员的基本修养 |拆开到各章|
| |11.避免直接抛出RuntimeException |规则冲突|
----
## (十一) 日志规约
对应 [阿里规范《日志规约》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/日志规约.md)
| VIP 规范 | 阿里规范 | 修改|
| -------- | -------- |-------- |
|4.尽量使用异步日志| |增加规则|
|5.禁止使用System.out()| |增加规则|
|6.禁止配置日志框架打印日志打印时的类名,行号等信息| |增加规则|
| |2.日志文件推荐至少保存15天 |删除规则|
| |3.应用中的扩展日志命名方式 |删除规则|
| |6.异常信息应该包括两类信息 |移到《异常处理》|
|2.合理使用使用占位符 |4.对trace/debug/info级别的日志使用占位符 |还是要判断日志是否必然输出, 并强调条件判断与占位符之间的差别|
----
## (十二) 其他规约
保留 [阿里规范《常量定义》一章](dhttps://github.com/alibaba/p3c/blob/master/p3c-gitbook/编程规约/常量定义.md)的规则1
| VIP 规范 | 阿里规范 | 修改|
| -------- | -------- |-------- |
| | 规则2-4 | 删除规则|
| | 规则5. 如果变量值仅在一个固定范围内变化用enum类型来定义 | 移到《基本类型》|
保留 [阿里规范《其他》一章](https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/其他.md)的规则7
| VIP 规范 | 阿里规范 | 修改|
| -------- | -------- |-------- |
| | 规则2-4,6-8 | 删除规则 |
| | 规则1. 在使用正则表达式时,利用好其预编译功 | 移到《基本类型》|
* 规则3,4,5,6,7均为新增规则
================================================
FILE: docs/standard/chapter01.md
================================================
# (一) 命名规约
**Rule 1. 【强制】禁止拼音缩写,避免阅读者费劲猜测;尽量不用拼音,除非中国式业务词汇没有通用易懂的英文对应。**
```text
禁止: DZ[打折] / getPFByName() [评分]
尽量避免:Dazhe / DaZhePrice
```
----
**Rule 2. 【强制】禁止使用非标准的英文缩写**
```text
反例: AbstractClass 缩写成 AbsClass;condition 缩写成 condi。
```
----
**Rule 3. 【强制】禁用其他编程语言风格的前缀和后缀**
在其它编程语言中使用的特殊前缀或后缀,如 `_name`、`name_`、`mName`、`i_name`,在 Java 中都禁止使用。
----
**Rule 4. 【推荐】命名的好坏,在于其“模糊度”**
1)如果上下文很清晰,局部变量可以使用 `list` 这种简略命名, 否则应该使用 `userList` 这种更清晰的命名。
2)禁止 `a1, a2, a3` 这种带编号的没诚意的命名方式。
3)方法的参数名叫 `bookList` ,方法里的局部变量名叫 `theBookList` 也是很没诚意。
4)如果一个应用里同时存在 `Account、AccountInfo、AccountData` 类,或者一个类里同时有 `getAccountInfo()、getAccountData()`, `save()、 store()` 的函数,阅读者将非常困惑。
5) `callerId` 与 `calleeId`, `mydearfriendswitha` 与 `mydearfriendswithb` 这种拼写极度接近,考验阅读者眼力的。
----
**Rule 5. 【推荐】包名全部小写。点分隔符之间尽量只有一个英语单词,即使有多个单词也不使用下划线或大小写分隔**
```text
正例: com.vip.javatool
反例: com.vip.java_tool, com.vip.javaTool
```
* [Sonar-120:Package names should comply with a naming convention](https://rules.sonarsource.com/java/RSPEC-120)
----
**Rule 6. 【强制】类名与接口名使用UpperCamelCase风格,遵从驼峰形式**
Tcp, Xml等缩写也遵循驼峰形式,可约定例外如:DTO/ VO等。
``` text
正例:UserId / XmlService / TcpUdpDeal / UserVO
反例:UserID / XMLService / TCPUDPDeal / UserVo
```
* [Sonar-101:Class names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-101)
* [Sonar-114:Interface names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-114)
----
**Rule 7. 【强制】方法名、参数名、成员变量、局部变量使用lowerCamelCase风格,遵从驼峰形式**
```text
正例: localValue / getHttpMessage();
```
* [Sonar-100:Method names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-100)
* [Sonar-116:Field names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-116)
* [Sonar-117:Local variable and method parameter names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-117)
----
**Rule 8. 【强制】常量命名全大写,单词间用下划线隔开。力求语义表达完整清楚,不要嫌名字长**
```text
正例: MAX_STOCK_COUNT
反例: MAX_COUNT
```
例外:当一个static final字段不是一个真正常量,比如不是基本类型时,不需要使用大写命名。
```java
private static final Logger logger = Logger.getLogger(MyClass.class);
```
例外:枚举常量推荐全大写,但如果历史原因未遵循也是允许的,所以我们修改了Sonar的规则。
* [Sonar-115:Constant names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-115)
* [Sonar-308:Static non-final field names should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-308)
----
**Rule 9. 【推荐】如果使用到了通用的设计模式,在类名中体现,有利于阅读者快速理解设计思想**
``` text
正例:OrderFactory, LoginProxy ,ResourceObserver
```
----
**Rule 10. 【推荐】枚举类名以Enum结尾; 抽象类使用Abstract或Base开头;异常类使用Exception结尾;测试类以它要测试的类名开始,以Test结尾**
```text
正例:DealStatusEnum, AbstractView,BaseView, TimeoutException,UserServiceTest
```
* [Sonar-2166:Classes named like "Exception" should extend "Exception" or a subclass](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-2166)
* [Sonar-3577:Test classes should comply with a naming convention](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-3577)
----
**Rule 11. 【推荐】实现类尽量用Impl的后缀与接口关联,除了形容能力的接口**
```text
正例:CacheServiceImpl 实现 CacheService接口。
正例: Foo 实现 Translatable接口。
```
----
**Rule 12. 【强制】POJO类中布尔类型的变量名,不要加is前缀,否则部分框架解析会引起序列化错误**
```text
反例:Boolean isSuccess的成员变量,它的GET方法也是isSuccess(),部分框架在反射解析的时候,“以为”对应的成员变量名称是success,导致出错。
```
----
**Rule 13. 【强制】避免成员变量,方法参数,局部变量的重名复写,引起混淆**
* 类的私有成员变量名,不与父类的成员变量重名
* 方法的参数名/局部变量名,不与类的成员变量重名 (getter/setter例外)
下面错误的地方,Java在编译时很坑人的都是合法的,但给阅读者带来极大的障碍。
```java
public class A {
int foo;
}
public class B extends A {
int foo; //WRONG
int bar;
public void hello(int bar) { //WRONG
int foo = 0; //WRONG
}
public void setBar(int bar) { //OK
this.bar = bar;
}
}
```
* [Sonar-2387: Child class fields should not shadow parent class fields](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-2387)
* [Sonar: Local variables should not shadow class fields](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-1117)
----
================================================
FILE: docs/standard/chapter02.md
================================================
# (二) 格式规约
**Rule 1. 【强制】使用项目组统一的代码格式模板,基于IDE自动的格式化**
1)IDE的默认代码格式模板,能简化绝大部分关于格式规范(如空格,括号)的描述。
2)统一的模板,并在接手旧项目先进行一次全面格式化,可以避免, 不同开发者之间,因为格式不统一产生代码合并冲突,或者代码变更日志中因为格式不同引起的变更,掩盖了真正的逻辑变更。
3)设定项目组统一的行宽,建议120。
4)设定项目组统一的缩进方式(Tab或二空格,四空格均可),基于IDE自动转换。
* [VIP代码格式化模板](https://github.com/vipshop/vjtools/tree/master/standard/formatter)
----
**Rule 2. 【强制】IDE的text file encoding设置为UTF-8; IDE中文件的换行符使用Unix格式,不要使用Windows格式**
----
**Rule 3. 【推荐】 用小括号来限定运算优先级**
我们没有理由假设读者能记住整个Java运算符优先级表。除非作者和Reviewer都认为去掉小括号也不会使代码被误解,甚至更易于阅读。
```java
if ((a == b) && (c == d))
```
* [Sonar-1068:Limited dependence should be placed on operator precedence rules in expressions](https://www.sonarsource.com/products/codeanalyzers/sonarjava/rules.html#RSPEC-1068),我们修改了三目运算符 `foo!=null?foo:""` 不需要加括号。
----
**Rule 4. 【推荐】类内方法定义的顺序,不要“总是在类的最后添加新方法”**
一个类就是一篇文章,想象一个阅读者的存在,合理安排方法的布局。
1)顺序依次是:构造函数 > (公有方法>保护方法>私有方法) > getter/setter方法。
如果公有方法可以分成几组,私有方法也紧跟公有方法的分组。
2)当一个类有多个构造方法,或者多个同名的重载方法,这些方法应该放置在一起。其中参数较多的方法在后面。
```java
public Foo(int a) {...}
public Foo(int a, String b) {...}
public void foo(int a) {...}
public void foo(int a, String b) {...}
```
3)作为调用者的方法,尽量放在被调用的方法前面。
```java
public void foo() {
bar();
}
public void bar() {...}
```
----
**Rule 5. 【推荐】通过空行进行逻辑分段**
一段代码也是一段文章,需要合理的分段而不是一口气读到尾。
不同组的变量之间,不同业务逻辑的代码行之间,插入一个空行,起逻辑分段的作用。
而联系紧密的变量之间、语句之间,则尽量不要插入空行。
```java
int width;
int height;
String name;
```
----
**Rule 6.【推荐】避免IDE格式化**
对于一些特殊场景(如使用大量的字符串拼接成一段文字,或者想把大量的枚举值排成一列),为了避免IDE自动格式化,土办法是把注释符号//加在每一行的末尾,但这有视觉的干扰,可以使用@formatter:off和@formatter:on来包装这段代码,让IDE跳过它。
``` java
// @formatter:off
...
// @formatter:on
```
----
================================================
FILE: docs/standard/chapter03.md
================================================
# (三) 注释规约
**Rule 1.【推荐】基本的注释要求**
完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。
代码将被大量后续维护,注释如果对阅读者有帮助,不要吝啬在注释上花费的时间。(但也综合参见规则2,3)
第一、能够准确反应设计思想和代码逻辑;第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。
除了特别清晰的类,都尽量编写类级别注释,说明类的目的和使用方法。
除了特别清晰的方法,对外提供的公有方法,抽象类的方法,同样尽量清晰的描述:期待的输入,对应的输出,错误的处理和返回码,以及可能抛出的异常。
----
**Rule 2. 【推荐】通过更清晰的代码来避免注释**
在编写注释前,考虑是否可以通过更好的命名,更清晰的代码结构,更好的函数和变量的抽取,让代码不言自明,此时不需要额外的注释。
----
**Rule 3. 【推荐】删除空注释,无意义注释**
《Clean Code》建议,如果没有想说的,不要留着IDE自动生成的,空的@param,@return,@throws 标记,让代码更简洁。
反例:方法名为put,加上两个有意义的变量名elephant和fridge,已经说明了这是在干什么,不需要任何额外的注释。
```java
/**
* put elephant into fridge.
*
* @param elephant
* @param fridge
* @return
*/
public void put(Elephant elephant, Fridge fridge);
```
----
**Rule 4.【推荐】避免创建人,创建日期,及更新日志的注释**
代码后续还会有多人多次维护,而创建人可能会离职,让我们相信源码版本控制系统对更新记录能做得更好。
----
**Rule 5. 【强制】代码修改的同时,注释也要进行相应的修改。尤其是参数、返回值、异常、核心逻辑等的修改**
----
**Rule 6. 【强制】类、类的公有成员、方法的注释必须使用Javadoc规范,使用/\*\* xxx \*/格式,不得使用`//xxx`方式**
正确的JavaDoc格式可以在IDE中,查看调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。
----
**Rule 7. 【推荐】JavaDoc中不要为了HTML格式化而大量使用HTML标签和转义字符**
如果为了Html版JavaDoc的显示,大量使用`` `
`这样的html标签,以及`<` `"` 这样的html转义字符,严重影响了直接阅读代码时的直观性,而直接阅读代码的几率其实比看Html版的JavaDoc大得多。
另外IDE对JavaDoc的格式化也要求``之类的标签来换行,可以配置让IDE不对JavaDoc的格式化。
----
**Rule 8. 【推荐】注释不要为了英文而英文**
如果没有国际化要求,中文能表达得更清晰时还是用中文。
----
**Rule 9. 【推荐】TODO 标记,清晰说明代办事项和处理人**
“对那些临时的, 短期的解决方案, 或已经够好但仍不完美的代码使用 TODO 注释. ”
清晰描述待修改的事项,保证过几个月后仍然能够清楚要做什么修改。
如果近期会处理的事项,写明处理人。如果远期的,写明提出人。
通过 IDE 和 Sonar 的标记扫描,经常清理此类标记,线上故障经常来源于这些标记但未处理的代码。
正例:
```java
// TODO(who): to do what.
// TODO(who): when to do what.
// TODO(calvin): use xxx to replace yyy.
```
反例:
```java
// TODO: refactor it
```
不推荐使用 FIXME 标记。这样,在项目中搜索 TODO 就可以得到完整的 TODO list,而不需要搜索 TODO 和 FIXME。
* [Sonar: Track uses of "TODO" tags](https://rules.sonarsource.com/java/RSPEC-1135)
----
**Rule 10. 【推荐】合理处理注释掉的代码**
如果后续会恢复此段代码,在目标代码上方用`///`说明注释动机,而不是简单的注释掉代码。
如果很大概率不再使用,则直接删除(版本管理工具保存了历史代码)。
* [Sonar: Sections of code should not be "commented out"](https://rules.sonarsource.com/java/RSPEC-125)
----
================================================
FILE: docs/standard/chapter04.md
================================================
# (四) 方法设计
**Rule 1. 【推荐】方法的长度度量**
方法尽量不要超过100行,或其他团队共同商定的行数。
另外,方法长度超过8000个字节码时,将不会被JIT编译成二进制码。
* [Sonar-107: Methods should not have too many lines](https://rules.sonarsource.com/java/RSPEC-107),默认值改为100
* Facebook-Contrib:Performance - This method is too long to be compiled by the JIT
----
**Rule 2. 【推荐】方法的语句在同一个抽象层级上**
反例:一个方法里,前20行代码在进行很复杂的基本价格计算,然后调用一个折扣计算函数,再调用一个赠品计算函数。
此时可将前20行也封装成一个价格计算函数,使整个方法在同一抽象层级上。
----
**Rule 3. 【推荐】为了帮助阅读及方法内联,将小概率发生的异常处理及其他极小概率进入的代码路径,封装成独立的方法**
```java
if(seldomHappenCase) {
hanldMethod();
}
try {
...
} catch(SeldomHappenException e) {
handleException();
}
```
----
**Rule 4. 【推荐】尽量减少重复的代码,抽取方法**
超过5行以上重复的代码,都可以考虑抽取公用的方法。
----
**Rule 5. 【推荐】方法参数最好不超过3个,最多不超过7个**
1)如果多个参数同属于一个对象,直接传递对象。
例外: 你不希望依赖整个对象,传播了类之间的依赖性。
2)将多个参数合并为一个新创建的逻辑对象。
例外: 多个参数之间毫无逻辑关联。
3)将函数拆分成多个函数,让每个函数所需的参数减少。
* [Sonar-107: Methods should not have too many parameters](https://rules.sonarsource.com/java/RSPEC-107)
----
**Rule 6.【推荐】下列情形,需要进行参数校验**
1) 调用频次低的方法。
2) 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,代价更大。
3) 需要极高稳定性和可用性的方法。
4) 对外提供的开放接口,不管是RPC/HTTP/公共类库的API接口。
如果使用Apache Validate 或 Guava Precondition进行校验,并附加错误提示信息时,注意不要每次校验都做一次字符串拼接。
```java
//WRONG
Validate.isTrue(length > 2, "length is "+keys.length+", less than 2", length);
//RIGHT
Validate.isTrue(length > 2, "length is %d, less than 2", length);
```
----
**Rule 7.【推荐】下列情形,不需要进行参数校验**
1) 极有可能被循环调用的方法。
2) 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。
比如,一般DAO层与Service层都在同一个应用中,所以DAO层的参数校验,可以省略。
3) 被声明成private,或其他只会被自己代码所调用的方法,如果能够确定在调用方已经做过检查,或者肯定不会有问题则可省略。
即使忽略检查,也尽量在方法说明里注明参数的要求,比如vjkit中的@NotNull,@Nullable标识。
----
**Rule 8.【推荐】禁用assert做参数校验**
assert断言仅用于测试环境调试,无需在生产环境时进行的校验。因为它需要增加-ea启动参数才会被执行。而且校验失败会抛出一个AssertionError(属于Error,需要捕获Throwable)
因此在生产环境进行的校验,需要使用Apache Commons Lang的Validate或Guava的Precondition。
----
**Rule 9.【推荐】返回值可以为Null,可以考虑使用JDK8的Optional类**
不强制返回空集合,或者空对象。但需要添加注释充分说明什么情况下会返回null值。
本手册明确`防止NPE是调用者的责任`。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null的情况。
JDK8的Optional类的使用这里不展开。
----
**Rule 10.【推荐】返回值可以为内部数组和集合**
如果觉得被外部修改的可能性不大,或没有影响时,不强制在返回前包裹成Immutable集合,或进行数组克隆。
----
**Rule 11.【推荐】不能使用有继承关系的参数类型来重载方法**
因为方法重载的参数类型是根据编译时表面类型匹配的,不根据运行时的实际类型匹配。
```java
class A {
void hello(List list);
void hello(ArrayList arrayList);
}
List arrayList = new ArrayList();
// 下句调用的是hello(List list),因为arrayList的定义类型是List
a.hello(arrayList);
```
----
**Rule 12.【强制】正被外部调用的接口,不允许修改方法签名,避免对接口的调用方产生影响**
只能新增新接口,并对已过时接口加@Deprecated注解,并清晰地说明新接口是什么。
----
**Rule 13.【推荐】不使用`@Deprecated`的类或方法**
接口提供方既然明确是过时接口并提供新接口,那么作为调用方来说,有义务去考证过时方法的新实现是什么。
比如java.net.URLDecoder 中的方法decode(String encodeStr) 这个方法已经过时,应该使用双参数decode(String source, String encode)。
----
**Rule 14.【推荐】不使用不稳定方法,如com.sun.\*包下的类,底层类库中internal包下的类**
`com.sun.*`,`sun.*`包下的类,或者底层类库中名称为internal的包下的类,都是不对外暴露的,可随时被改变的不稳定类。
* [Sonar-1191: Classes from "sun.*" packages should not be used](https://rules.sonarsource.com/java/RSPEC-1191)
----
================================================
FILE: docs/standard/chapter05.md
================================================
# (五) 类设计
**Rule 1. 【推荐】类成员与方法的可见性最小化**
任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。思考:如果是一个private的方法,想删除就删除,可是一个public的service方法,或者一个public的成员变量,删除一下,不得手心冒点汗吗?
例外:为了单元测试,有时也可能将访问范围扩大,此时需要加上JavaDoc说明或vjkit中的`@VisibleForTesting`注解。
----
**Rule 2.【推荐】 减少类之间的依赖**
比如如果A类只依赖B类的某个属性,在构造函数和方法参数中,只传入该属性。让阅读者知道,A类只依赖了B类的这个属性,而不依赖其他属性,也不会调用B类的任何方法。
```java
a.foo(b); //WRONG
a.foo(b.bar); //RIGHT
```
----
**Rule 3.【推荐】 定义变量与方法参数时,尽量使用接口而不是具体类**
使用接口可以保持一定的灵活性,也能向读者更清晰的表达你的需求:变量和参数只是要求有一个Map,而不是特定要求一个HashMap。
例外:如果变量和参数要求某种特殊类型的特性,则需要清晰定义该参数类型,同样是为了向读者表达你的需求。
----
**Rule 4. 【推荐】类的长度度量**
类尽量不要超过300行,或其他团队共同商定的行数。
对过大的类进行分拆时,可考虑其内聚性,即类的属性与类的方法的关联程度,如果有些属性没有被大部分的方法使用,其内聚性是低的。
----
**Rule 5.【推荐】 构造函数如果有很多参数,且有多种参数组合时,建议使用Builder模式**
```java
Executor executor = new ThreadPoolBuilder().coreThread(10).queueLenth(100).build();
```
即使仍然使用构造函数,也建议使用chain constructor模式,逐层加入默认值传递调用,仅在参数最多的构造函数里实现构造逻辑。
```java
public A(){
A(DEFAULT_TIMEOUT);
}
public A(int timeout) {
...
}
```
----
**Rule 6.【推荐】构造函数要简单,尤其是存在继承关系的时候**
可以将复杂逻辑,尤其是业务逻辑,抽取到独立函数,如init(),start(),让使用者显式调用。
```java
Foo foo = new Foo();
foo.init();
```
----
**Rule 7.【强制】所有的子类覆写方法,必须加`@Override`注解**
比如有时候子类的覆写方法的拼写有误,或方法签名有误,导致没能真正覆写,加`@Override`可以准确判断是否覆写成功。
而且,如果在父类中对方法签名进行了修改,子类会马上编译报错。
另外,也能提醒阅读者这是个覆写方法。
最后,建议在IDE的Save Action中配置自动添加`@Override`注解,如果无意间错误同名覆写了父类方法也能被发现。
* [Sonar-1161: "@Override" should be used on overriding and implementing methods](https://rules.sonarsource.com/java/RSPEC-1161)
----
**Rule 8.【强制】静态方法不能被子类覆写。**
因为它只会根据表面类型来决定调用的方法。
```java
Base base = new Children();
// 下句实际调用的是父类的静态方法,虽然对象实例是子类的。
base.staticMethod();
```
----
**Rule 9.静态方法访问的原则**
**9.1【推荐】避免通过一个类的对象引用访问此类的静态变量或静态方法,直接用类名来访问即可**
目的是向读者更清晰传达调用的是静态方法。可在IDE的Save Action中配置自动转换。
```java
int i = objectA.staticMethod(); // WRONG
int i = ClassA.staticMethod(); // RIGHT
```
* [Sonar-2209: "static" members should be accessed statically](https://rules.sonarsource.com/java/RSPEC-2209)
* [Sonar-2440: Classes with only "static" methods should not be instantiated](https://rules.sonarsource.com/java/RSPEC-2440)
**9.2 【推荐】除测试用例,不要static import 静态方法**
静态导入后忽略掉的类名,给阅读者造成障碍。
例外:测试环境中的assert语句,大家都太熟悉了。
* [Sonar-3030: Classes should not have too many "static" imports](https://rules.sonarsource.com/java/RSPEC-3030) 但IDEA经常自动转换static import,所以暂不作为规则。
**9.3【推荐】尽量避免在非静态方法中修改静态成员变量的值**
```java
// WRONG
public void foo() {
ClassA.staticFiled = 1;
}
```
* [Sonar-2696: Instance methods should not write to "static" fields](https://rules.sonarsource.com/java/RSPEC-2696)
* [Sonar-3010: Static fields should not be updated in constructors](https://rules.sonarsource.com/java/RSPEC-3010)
----
**Rule 10.【推荐】 内部类的定义原则**
当一个类与另一个类关联非常紧密,处于从属的关系,特别是只有该类会访问它时,可定义成私有内部类以提高封装性。
另外,内部类也常用作回调函数类,在JDK8下建议写成Lambda。
内部类分匿名内部类,内部类,静态内部类三种。
1) 匿名内部类 与 内部类,按需使用:
在性能上没有区别;当内部类会被多个地方调用,或匿名内部类的长度太长,已影响对调用它的方法的阅读时,定义有名字的内部类。
2) 静态内部类 与 内部类,优先使用静态内部类:
1. 非静态内部类持有外部类的引用,能访问外类的实例方法与属性。构造时多传入一个引用对性能没有太大影响,更关键的是向阅读者传递自己的意图,内部类会否访问外部类。
2. 非静态内部类里不能定义static的属性与方法。
* [Sonar-2694: Inner classes which do not reference their owning classes should be "static"](https://rules.sonarsource.com/java/RSPEC-2694)
* [Sonar-1604: Anonymous inner classes containing only one method should become lambdas](https://rules.sonarsource.com/java/RSPEC-1604)
----
**Rule 11.【推荐】使用getter/setter方法,还是直接public成员变量的原则。**
除非因为特殊原因方法内联失败,否则使用getter方法与直接访问成员变量的性能是一样的。
使用getter/setter,好处是可以进一步的处理:
1. 通过隐藏setter方法使得成员变量只读
2. 增加简单的校验逻辑
3. 增加简单的值处理,值类型转换等
建议通过IDE生成getter/setter。
但getter/seter中不应有复杂的业务处理,建议另外封装函数,并且不要以getXX/setXX命名。
如果是内部类,以及无逻辑的POJO/VO类,使用getter/setter除了让一些纯OO论者感觉舒服,没有任何的好处,建议直接使用public成员变量。
例外:有些序列化框架只能从getter/setter反射,不能直接反射public成员变量。
----
**Rule 12.【强制】POJO类必须覆写toString方法。**
便于记录日志,排查问题时调用POJO的toString方法打印其属性值。否则默认的Object.toString()只打印`类名@数字`的无效信息。
----
**Rule 13. hashCode和equals方法的处理,遵循如下规则:**
**13.1【强制】只要重写equals,就必须重写hashCode。 而且选取相同的属性进行运算。**
**13.2【推荐】只选取真正能决定对象是否一致的属性,而不是所有属性,可以改善性能。**
**13.3【推荐】对不可变对象,可以缓存hashCode值改善性能(比如String就是例子)。**
**13.4【强制】类的属性增加时,及时重新生成toString,hashCode和equals方法。**
* [Sonar-1206: "equals(Object obj)" and "hashCode()" should be overridden in pairs](https://rules.sonarsource.com/java/RSPEC-1206)
----
**Rule 14.【强制】使用 IDE 生成 toString、hashCode 和 equals 方法。**
使用 IDE 生成而不是手写,能保证 toString 有统一的格式,equals 和 hashCode 则避免不正确的 null 值处理。
子类生成 toString 时,还需要勾选父类的属性。
----
**Rule 15. 【强制】Object的equals方法容易抛空指针异常,应使用常量或确定非空的对象来调用equals**
推荐使用java.util.Objects#equals(JDK7引入的工具类)
```java
"test".equals(object); //RIGHT
Objects.equals(object, "test"); //RIGHT
```
* [Sonar-1132: Strings literals should be placed on the left side when checking for equality](https://rules.sonarsource.com/java/RSPEC-1132)
----
**Rule 16.【强制】除了保持兼容性的情况,总是移除无用属性、方法与参数**
特别是private的属性、方法、内部类,private方法上的参数,一旦无用立刻移除。信任代码版本管理系统。
* [Sonar-3985: Unused "private" classes should be removed](https://rules.sonarsource.com/java/RSPEC-3985)
* [Sonar-1068: Unused "private" fields should be removed](https://rules.sonarsource.com/java/RSPEC-1068)
* [Sonar: Unused "private" methods should be removed](https://rules.sonarsource.com/java/RSPEC-1144)
* [Sonar-1481: Unused local variables should be removed](https://rules.sonarsource.com/java/RSPEC-1481)
* [Sonar-1172: Unused method parameters should be removed](https://rules.sonarsource.com/java/RSPEC-1172) Sonar-VJ版只对private方法的无用参数告警。
----
**Rule 17.【推荐】final关键字与性能无关,仅用于下列不可修改的场景**
1) 定义类及方法时,类不可继承,方法不可覆写;
2) 定义基本类型的函数参数和变量,不可重新赋值;
3) 定义对象型的函数参数和变量,仅表示变量所指向的对象不可修改,而对象自身的属性是可以修改的。
----
**Rule 18.【推荐】得墨忒耳法则,不要和陌生人说话**
以下调用,一是导致了对A对象的内部结构(B,C)的紧耦合,二是连串的调用很容易产生NPE,因此链式调用尽量不要过长。
```java
obj.getA().getB().getC().hello();
```
----
================================================
FILE: docs/standard/chapter06.md
================================================
# (六) 控制语句
**Rule 1. 【强制】if, else, for, do, while语句必须使用大括号,即使只有单条语句**
曾经试过合并代码时,因为没加括号,单条语句合并成两条语句后,仍然认为只有单条语句,另一条语句在循环外执行。
其他增加调试语句等情况也经常引起同样错误。
可在IDE的Save Action中配置自动添加。
```java
if (a == b) {
...
}
```
例外:一般由IDE生成的equals()函数
* [Sonar-121: Control structures should use curly braces](https://rules.sonarsource.com/java/RSPEC-121) Sonar-VJ版豁免了equals()函数
----
**Rule 2.【推荐】少用if-else方式,多用哨兵语句式以减少嵌套层次**
```java
if (condition) {
...
return obj;
}
// 接着写else的业务逻辑代码;
```
* Facebook-Contrib: Style - Method buries logic to the right (indented) more than it needs to be
----
**Rule 3.【推荐】限定方法的嵌套层次**
所有if/else/for/while/try的嵌套,当层次过多时,将引起巨大的阅读障碍,因此一般推荐嵌套层次不超过4。
通过抽取方法,或哨兵语句(见Rule 2)来减少嵌套。
```java
public void applyDriverLicense() {
if (isTooYoung()) {
System.out.println("You are too young to apply driver license.");
return;
}
if (isTooOld()) {
System.out.println("You are too old to apply driver license.");
return;
}
System.out.println("You've applied the driver license successfully.");
return;
}
```
* [Sonar-134: Control flow statements "if", "for", "while", "switch" and "try" should not be nested too deeply](https://rules.sonarsource.com/java/RSPEC-134),增大为4
----
**Rule 4.【推荐】布尔表达式中的布尔运算符(&&,||)的个数不超过4个,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性**
```java
//WRONG
if ((file.open(fileName, "w") != null) && (...) || (...)|| (...)) {
...
}
//RIGHT
boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed || (...)) {
...
}
```
* [Sonar-1067: Expressions should not be too complex](https://rules.sonarsource.com/java/RSPEC-1067),增大为4
----
**Rule 5.【推荐】简单逻辑,善用三目运算符,减少if-else语句的编写**
```java
s != null ? s : "";
```
----
**Rule 6.【推荐】减少使用取反的逻辑**
不使用取反的逻辑,有利于快速理解。且大部分情况,取反逻辑存在对应的正向逻辑写法。
```java
//WRONG
if (!(x >= 268) { ... }
//RIGHT
if (x < 268) { ... }
```
* [Sonar-1940: Boolean checks should not be inverted](https://rules.sonarsource.com/java/RSPEC-1940)
----
**Rule 7.【推荐】表达式中,能造成短路概率较大的逻辑尽量放前面,使得后面的判断可以免于执行**
```java
if (maybeTrue() || maybeFalse()) { ... }
if (maybeFalse() && maybeTrue()) { ... }
```
----
**Rule 8.【强制】switch的规则**
1)在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;
2)在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。
```java
String animal = "tomcat";
switch (animal) {
case "cat":
System.out.println("It's a cat.");
break;
case "lion": // 执行到tiger
case "tiger":
System.out.println("It's a beast.");
break;
default:
// 什么都不做,也要有default
break;
}
```
* [Sonar: "switch" statements should end with "default" clauses](https://rules.sonarsource.com/java/RSPEC-131)
----
**Rule 9.【推荐】循环体中的语句要考量性能,操作尽量移至循环体外处理**
1)不必要的耗时较大的对象构造;
2)不必要的try-catch(除非出错时需要循环下去)。
----
**Rule 10.【推荐】能用while循环实现的代码,就不用do-while循环**
while语句能在循环开始的时候就看到循环条件,便于帮助理解循环内的代码;
do-while语句要在循环最后才看到循环条件,不利于代码维护,代码逻辑容易出错。
----
================================================
FILE: docs/standard/chapter07.md
================================================
# (七) 基本类型与字符串
**Rule 1. 原子数据类型(int等)与包装类型(Integer等)的使用原则**
**1.1 【推荐】需要序列化的POJO类属性使用包装数据类型**
**1.2 【推荐】RPC方法的返回值和参数使用包装数据类型**
**1.3 【推荐】局部变量尽量使用基本数据类型**
包装类型的坏处:
1)Integer 24字节,而原子类型 int 4字节。
2)包装类型每次赋值还需要额外创建对象,如Integer var = 200, 除非数值在缓存区间内(见Integer.IntegerCache与Long.LongCache)才会复用已缓存对象。默认缓存区间为-128到127,其中Integer的缓存区间还受启动参数的影响,如-XX:AutoBoxCacheMax=20000。
3)包装类型还有==比较的陷阱(见规则3)
包装类型的好处:
1)包装类型能表达 null 的语义。
比如数据库的查询结果可能是null,如果用基本数据类型有NPE风险。又比如显示成交总额涨跌情况,如果调用的RPC服务不成功时,应该返回null,显示成-%,而不是0%。
2)集合需要包装类型,除非使用数组,或者特殊的原子类型集合。
3)泛型需要包装类型,如`Result`。
----
**Rule 2.原子数据类型与包装类型的转换原则**
**2.1【推荐】自动转换(AutoBoxing)有一定成本,调用者与被调用函数间尽量使用同一类型,减少默认转换**
```java
//WRONG, sum 类型为Long, i类型为long,每次相加都需要AutoBoxing。
Long sum=0L;
for( long i = 0; i < 10000; i++) {
sum+=i;
}
//RIGHT, 准确使用API返回正确的类型
Integer i = Integer.valueOf(str);
int i = Integer.parseInt(str);
```
* [Sonar-2153: Boxing and unboxing should not be immediately reversed](https://rules.sonarsource.com/java/RSPEC-2153)
**2.2 【推荐】自动拆箱有可能产生NPE,要注意处理**
```java
//如果intObject为null,产生NPE
int i = intObject;
```
----
**Rule 3. 数值equals比较的原则**
**3.1【强制】 所有包装类对象之间值的比较,全部使用equals方法比较**
\==判断对象是否同一个。Integer var = ?在缓存区间的赋值(见规则1),会复用已有对象,因此这个区间内的Integer使用==进行判断可通过,但是区间之外的所有数据,则会在堆上新产生,不会通过。因此如果用\== 来比较数值,很可能在小的测试数据中通过,而到了生产环境才出问题。
**3.2【强制】 BigDecimal需要使用compareTo()**
因为BigDecimal的equals()还会比对精度,2.0与2.00不一致。
* Facebook-Contrib: Correctness - Method calls BigDecimal.equals()
**3.3【强制】 Atomic\* 系列,不能使用 equals 方法**
因为 Atomic\* 系列没有覆写 equals 方法。
```java
// RIGHT
if (counter1.get() == counter2.get()) {
// ...
}
```
* [Sonar-2204: ".equals()" should not be used to test the values of "Atomic" classes](https://rules.sonarsource.com/java/RSPEC-2204)
**3.4【强制】 double 及 float 的比较,要特殊处理**
因为精度问题,浮点数间的 equals 非常不可靠,在 vjkit 的 NumberUtil 中有对应的封装函数。
```java
float f1 = 0.15f;
float f2 = 0.45f/3; // 实际等于0.14999999
// 反例
if (f1 == f2) {
// ...
}
// 反例
if (Double.compare(f1, f2) == 0) {
// ...
}
// 正例
// 绝对误差
static final float EPSILON = 0.00001f;
if (Math.abs(f1 - f2) < EPSILON) {
// ...
}
// 正例
// 相对误差
static final float EPSILON = 0.001f;
if (Math.abs(f1 - f2) / (Math.abs(f1) + Math.abs(f2)) < EPSILON) {
// ...
}
```
推荐使用“相对误差”的方式比较浮点数。
* [Sonar-1244: Floating point numbers should not be tested for equality](https://rules.sonarsource.com/java/RSPEC-1244)
----
**Rule 4. 数字类型的计算原则**
**4.1【强制】数字运算表达式,因为先进行等式右边的运算,再赋值给等式左边的变量,所以等式两边的类型要一致**
例子1: int与int相除后,哪怕被赋值给float或double,结果仍然是四舍五入取整的int。
需要强制将除数或被除数转换为float或double。
```java
double d = 24/7; //结果是3.0
double d = (double)24/7; //结果是正确的3.42857
```
例子2: int与int相乘,哪怕被赋值给long,仍然会溢出。
需要强制将乘数的一方转换为long。
```java
long l = Integer.MAX_VALUE * 2; // 结果是溢出的-2
long l = Integer.MAX_VALUE * 2L; //结果是正确的4294967294
```
另外,int的最大值约21亿,留意可能溢出的情况。
* [Sonar-2184: Math operands should be cast before assignment](https://rules.sonarsource.com/java/RSPEC-2184)
**4.2【强制】数字取模的结果不一定是正数,负数取模的结果仍然负数**
取模做数组下标时,如果不处理负数的情况,很容易ArrayIndexOutOfBoundException。
另外,Integer.MIN_VALUE取绝对值也仍然是负数。因此,vjkit的MathUtil对上述情况做了安全的封装。
```java
-4 % 3 = -1;
Math.abs(Integer.MIN_VALUE) = -2147483648;
```
* Findbugs: Style - Remainder of hashCode could be negative
**4.3【推荐】 double 或 float 计算时有不可避免的精度问题**
```java
float f = 0.45f/3; //结果是0.14999999
double d1 = 0.45d/3; //结果是正确的0.15
double d2 = 1.03d - 0.42d; //结果是0.6100000000000001
```
尽量用double而不用float,但如果是金融货币的计算,则必须使用如下选择:
选项1, 使用性能较差的BigDecimal。BigDecimal还能精确控制四舍五入或是其他取舍的方式。
选项2, 在预知小数精度的情况下,将浮点运算放大为整数计数,比如货币以"分"而不是以"元"计算。
* [Sonar-2164: Math should not be performed on floats](https://rules.sonarsource.com/java/RSPEC-2164)
----
**Rule 5. 【推荐】如果变量值仅有有限的可选值,用枚举类来定义常量**
尤其是变量还希望带有名称之外的延伸属性时,如下例:
```java
//WRONG
public String MONDAY = "MONDAY";
public int MONDAY_SEQ = 1;
//RIGHT
public enum SeasonEnum {
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
int seq;
SeasonEnum(int seq) { this.seq = seq; }
}
```
业务代码中不要依赖ordinary()函数进行业务运算,而是自定义数字属性,以免枚举值的增减调序造成影响。 例外:永远不会有变化的枚举,比如上例的一年四季。
----
**Rule 6. 字符串拼接的原则**
**6.1 【推荐】 当字符串拼接不在一个命令行内写完,而是存在多次拼接时(比如循环),使用StringBuilder的append()**
```java
String s = "hello" + str1 + str2; //Almost OK,除非初始长度有问题,见第3点.
String s = "hello"; //WRONG
if (condition) {
s += str1;
}
String str = "start"; //WRONG
for (int i = 0; i < 100; i++) {
str = str + "hello";
}
```
反编译出的字节码文件显示,其实每条用`+`进行字符拼接的语句,都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象。所以上面两个错误例子,会重复构造StringBuilder,重复toString()造成资源浪费。
* [Sonar-1643: Strings should not be concatenated using '+' in a loop](https://rules.sonarsource.com/java/RSPEC-1643)
**6.2 【强制】 字符串拼接对象时,不要显式调用对象的toString()**
如上,`+`实际是StringBuilder,本身会调用对象的toString(),且能很好的处理null的情况。
```java
//WRONG
str = "result:" + myObject.toString(); // myObject 为 null时,抛 NPE
//RIGHT
str = "result:" + myObject; // myObject 为 null 时,输出 result:null
```
**6.3【强制】使用StringBuilder,而不是有所有方法都有同步修饰符的StringBuffer**
因为内联不成功,逃逸分析并不能抹除StringBuffer上的同步修饰符
* [Sonar-1149: Synchronized classes Vector, Hashtable, Stack and StringBuffer should not be used](https://rules.sonarsource.com/java/RSPEC-1149)
**6.4 【推荐】当拼接后字符串的长度远大于16时,指定StringBuilder的大概长度,避免容量不足时的成倍扩展**
**6.5 【推荐】如果字符串长度很大且频繁拼接,可考虑ThreadLocal重用StringBuilder对象**
参考BigDecimal的toString()实现,及vjkit中的StringBuilderHolder。
----
**Rule 7. 【推荐】字符操作时,优先使用字符参数,而不是字符串,能提升性能**
```java
//WRONG
str.indexOf("e");
//RIGHT
stringBuilder.append('a');
str.indexOf('e');
str.replace('m','z');
```
其他包括split等方法,在JDK String中未提供针对字符参数的方法,可考虑使用Apache Commons StringUtils 或Guava的Splitter。
* [Sonar-3027: String function use should be optimized for single characters](https://rules.sonarsource.com/java/RSPEC-3027)
----
**Rule 8. 【推荐】利用好正则表达式的预编译功能,可以有效加快正则匹配速度**
反例:
```java
//直接使用String的matches()方法
result = "abc".matches("[a-zA-z]");
//每次重新构造Pattern
Pattern pattern = Pattern.compile("[a-zA-z]");
result = pattern.matcher("abc").matches();
```
正例:
```java
//在某个地方预先编译Pattern,比如类的静态变量
private static Pattern pattern = Pattern.compile("[a-zA-z]");
...
//真正使用Pattern的地方
result = pattern.matcher("abc").matches();
```
----
================================================
FILE: docs/standard/chapter08.md
================================================
# (八) 集合处理
**Rule 1. 【推荐】底层数据结构是数组的集合,指定集合初始大小**
底层数据结构为数组的集合包括 ArrayList,HashMap,HashSet,ArrayDequeue等。
数组有大小限制,当超过容量时,需要进行复制式扩容,新申请一个是原来容量150% or 200%的数组,将原来的内容复制过去,同时浪费了内存与性能。HashMap/HashSet的扩容,还需要所有键值对重新落位,消耗更大。
默认构造函数使用默认的数组大小,比如ArrayList默认大小为10,HashMap为16。因此建议使用ArrayList(int initialCapacity)等构造函数,明确初始化大小。
HashMap/HashSet的初始值还要考虑加载因子:
为了降低哈希冲突的概率(Key的哈希值按数组大小取模后,如果落在同一个数组下标上,将组成一条需要遍历的Entry链),默认当HashMap中的键值对达到数组大小的75%时,即会触发扩容。因此,如果预估容量是100,即需要设定`100/0.75 +1=135`的数组大小。vjkit的MapUtil的Map创建函数封装了该计算。
如果希望加快Key查找的时间,还可以进一步降低加载因子,加大初始大小,以降低哈希冲突的概率。
----
**Rule 2. 【推荐】尽量使用新式的foreach语法遍历Collection与数组**
foreach是语法糖,遍历集合的实际字节码等价于基于Iterator的循环。
foreach代码一来代码简洁,二来有效避免了有多个循环或嵌套循环时,因为不小心的复制粘贴,用错了iterator或循环计数器(i,j)的情况。
----
**Rule 3. 【强制】不要在foreach循环里进行元素的remove/add操作,remove元素可使用Iterator方式**
```java
//WRONG
for (String str : list) {
if (condition) {
list.remove(str);
}
}
//RIGHT
Iterator it = list.iterator();
while (it.hasNext()) {
String str = it.next();
if (condition) {
it.remove();
}
}
```
* Facebook-Contrib: Correctness - Method modifies collection element while iterating
* Facebook-Contrib: Correctness - Method deletes collection element while iterating
----
**Rule 4. 【强制】使用entrySet遍历Map类集合Key/Value,而不是keySet 方式进行遍历**
keySet遍历的方式,增加了N次用key获取value的查询。
* [Sonar-2864:"entrySet()" should be iterated when both the key and value are needed](https://rules.sonarsource.com/java/RSPEC-2864)
----
**Rule 5. 【强制】当对象用于集合时,下列情况需要重新实现hashCode()和 equals()**
1) 以对象做为Map的KEY时;
2) 将对象存入Set时。
上述两种情况,都需要使用hashCode和equals比较对象,默认的实现会比较是否同一个对象(对象的引用相等)。
另外,对象放入集合后,会影响hashCode(),equals()结果的属性,将不允许修改。
* [Sonar-2141:Classes that don't define "hashCode()" should not be used in hashes](https://rules.sonarsource.com/java/RSPEC-2141)
----
**Rule 6. 【强制】高度注意各种Map类集合Key/Value能不能存储null值的情况**
| Map | Key | Value |
| -------- | -------- |-------- |
|HashMap|Nullable | Nullable|
|ConcurrentHashMap| NotNull| NotNull|
|TreeMap| NotNull| Nullable |
由于HashMap的干扰,很多人认为ConcurrentHashMap是可以置入null值。同理,Set中的value实际是Map中的key。
----
**Rule 7. 【强制】长生命周期的集合,里面内容需要及时清理,避免内存泄漏**
长生命周期集合包括下面情况,都要小心处理。
1) 静态属性定义;
2) 长生命周期对象的属性;
3) 保存在ThreadLocal中的集合。
如无法保证集合的大小是有限的,使用合适的缓存方案代替直接使用HashMap。
另外,如果使用WeakHashMap保存对象,当对象本身失效时,就不会因为它在集合中存在引用而阻止回收。但JDK的WeakHashMap并不支持并发版本,如果需要并发可使用Guava Cache的实现。
----
**Rule 8. 【强制】集合如果存在并发修改的场景,需要使用线程安全的版本**
1) 著名的反例,HashMap扩容时,遇到并发修改可能造成100%CPU占用。
推荐使用`java.util.concurrent(JUC)`工具包中的并发版集合,如ConcurrentHashMap等,优于使用Collections.synchronizedXXX()系列函数进行同步化封装(等价于在每个方法都加上synchronized关键字)。
例外:ArrayList所对应的CopyOnWriteArrayList,每次更新时都会复制整个数组,只适合于读多写很少的场景。如果频繁写入,可能退化为使用Collections.synchronizedList(list)。
2) 即使线程安全类仍然要注意函数的正确使用。
例如:即使用了 ConcurrentHashMap,但直接使用 get/put 方法,仍然可能会多线程间互相覆盖。
```java
//WRONG
E e = map.get(key);
if (e == null) {
e = new E();
map.put(key, e); //仍然能两条线程并发执行put,互相覆盖
}
return e;
//RIGHT
E e = map.get(key);
if (e == null) {
e = new E();
E previous = map.putIfAbsent(key, e);
if(previous != null) {
return previous;
}
}
return e;
```
----
**Rule 9. 【推荐】正确使用集合泛型的通配符**
`List`并不是`List`的子类,如果希望泛型的集合能向上向下兼容转型,而不仅仅适配唯一类,则需定义通配符,可以按需要extends 和 super的字面意义,也可以遵循`PECS(Producer Extends Consumer Super)`原则:
1) 如果集合要被读取,定义成` extends T>`
```java
Class Stack{
public void pushAll(Iterable extends E> src){
for (E e: src)
push(e);
}
}
Stack stack = new Stack();
Iterable integers = ...;
stack.pushAll(integers);
```
2) 如果集合要被写入,定义成` super T>`
```java
Class Stack{
public void popAll(Collection super E> dist){
while(!isEmpty())
dist.add(pop);
}
}
Stack stack = new Stack();
Collection objects = ...;
stack.popAll(objects);
```
----
**Rule 10. 【推荐】`List`, `List>` 与 `List`的选择**
定义成`List`,会被IDE提示需要定义泛型。 如果实在无法确定泛型,就仓促定义成`List>`来蒙混过关的话,该list只能读,不能增改。定义成`List`呢,如规则9所述,`List` 并不是`List`的子类,除非函数定义使用了通配符。
因此实在无法明确其泛型时,使用`List`也是可以的。
----
**Rule 11. 【推荐】如果Key只有有限的可选值,先将Key封装成Enum,并使用EnumMap**
EnumMap,以Enum为Key的Map,内部存储结构为`Object[enum.size]`,访问时以`value = Object[enum.ordinal()]`获取值,同时具备HashMap的清晰结构与数组的性能。
```java
public enum COLOR {
RED, GREEN, BLUE, ORANGE;
}
EnumMap moodMap = new EnumMap (COLOR.class);
```
* [Sonar-1640: Maps with keys that are enum values should be replaced with EnumMap](https://rules.sonarsource.com/java/RSPEC-1640)
----
**Rule 12. 【推荐】Array 与 List互转的正确写法**
```java
// list -> array,构造数组时不需要设定大小
String[] array = (String[])list.toArray(); //WRONG;
String[] array = list.toArray(new String[0]); //RIGHT
String[] array = list.toArray(new String[list.size()]); //RIGHT,但list.size()可用0代替。
// array -> list
//非原始类型数组,且List不能再扩展
List list = Arrays.asList(array);
//非原始类型数组, 但希望List能再扩展
List list = new ArrayList(array.length);
Collections.addAll(list, array);
//原始类型数组,JDK8
List myList = Arrays.stream(intArray).boxed().collect(Collectors.toList());
//原始类型数组,JDK7则要自己写个循环来加入了
```
Arrays.asList(array),如果array是原始类型数组如int[],会把整个array当作List的一个元素,String[] 或 Foo[]则无此问题。
Collections.addAll()实际是循环加入元素,性能相对较低,同样会把int[]认作一个元素。
* Facebook-Contrib: Correctness - Impossible downcast of toArray() result
* Facebook-Contrib: Correctness - Method calls Array.asList on an array of primitive values
----
================================================
FILE: docs/standard/chapter09.md
================================================
# (九) 并发处理
**Rule 1. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯**
1)创建单条线程时直接指定线程名称
```java
Thread t = new Thread();
t.setName("cleanup-thread");
```
2) 线程池则使用guava或自行封装的ThreadFactory,指定命名规则。
```java
//guava 或自行封装的ThreadFactory
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(threadNamePrefix + "-%d").build();
ThreadPoolExecutor executor = new ThreadPoolExecutor(..., threadFactory, ...);
```
----
**Rule 2. 【推荐】尽量使用线程池来创建线程**
除特殊情况,尽量不要自行创建线程,更好的保护线程资源。
```java
//WRONG
Thread thread = new Thread(...);
thread.start();
```
同理,定时器也不要使用Timer,而应该使用ScheduledExecutorService。
因为Timer只有单线程,不能并发的执行多个在其中定义的任务,而且如果其中一个任务抛出异常,整个Timer也会挂掉,而ScheduledExecutorService只有那个没捕获到异常的任务不再定时执行,其他任务不受影响。
----
**Rule 3. 【强制】线程池不允许使用 Executors去创建,避资源耗尽风险**
Executors返回的线程池对象的弊端 :
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
应通过 new ThreadPoolExecutor(xxx,xxx,xxx,xxx)这样的方式,更加明确线程池的运行规则,合理设置Queue及线程池的core size和max size,建议使用vjkit封装的ThreadPoolBuilder。
----
**Rule 4. 【强制】正确停止线程**
Thread.stop()不推荐使用,强行的退出太不安全,会导致逻辑不完整,操作不原子,已被定义成Deprecate方法。
停止单条线程,执行Thread.interrupt()。
停止线程池:
* ExecutorService.shutdown(): 不允许提交新任务,等待当前任务及队列中的任务全部执行完毕后退出;
* ExecutorService.shutdownNow(): 通过Thread.interrupt()试图停止所有正在执行的线程,并不再处理还在队列中等待的任务。
最优雅的退出方式是先执行shutdown(),再执行shutdownNow(),vjkit的`ThreadPoolUtil`进行了封装。
注意,Thread.interrupt()并不保证能中断正在运行的线程,需编写可中断退出的Runnable,见规则5。
----
**Rule 5. 【强制】编写可停止的Runnable**
执行Thread.interrupt()时,如果线程处于sleep(), wait(), join(), lock.lockInterruptibly()等blocking状态,会抛出InterruptedException,如果线程未处于上述状态,则将线程状态设为interrupted。
因此,如下的代码无法中断线程:
```java
public void run() {
while (true) { //WRONG,无判断线程状态。
sleep();
}
public void sleep() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
logger.warn("Interrupted!", e); //WRONG,吃掉了异常,interrupt状态未再传递
}
}
}
```
**5.1 正确处理InterruptException**
因为InterruptException异常是个必须处理的Checked Exception,所以run()所调用的子函数很容易吃掉异常并简单的处理成打印日志,但这等于停止了中断的传递,外层函数将收不到中断请求,继续原有循环或进入下一个堵塞。
正确处理是调用`Thread.currentThread().interrupt();` 将中断往外传递。
```java
//RIGHT
public void myMethod() {
try {
...
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
```
* [Sonar-2142: "InterruptedException" should not be ignored](https://rules.sonarsource.com/java/RSPEC-2142)
**5.2 主循环及进入阻塞状态前要判断线程状态**
```java
//RIGHT
public void run() {
try {
while (!Thread.isInterrupted()) {
// do stuff
}
} catch (InterruptedException e) {
logger.warn("Interrupted!", e);
}
}
```
其他如Thread.sleep()的代码,在正式sleep前也会判断线程状态。
----
**Rule 6. 【强制】Runnable中必须捕获一切异常**
如果Runnable中没有捕获RuntimeException而向外抛出,会发生下列情况:
1) ScheduledExecutorService执行定时任务,任务会被中断,该任务将不再定时调度,但线程池里的线程还能用于其他任务。
2) ExecutorService执行任务,当前线程会中断,线程池需要创建新的线程来响应后续任务。
3) 如果没有在ThreadFactory设置自定义的UncaughtExceptionHanlder,则异常最终只打印在System.err,而不会打印在项目的日志中。
因此建议自写的Runnable都要保证捕获异常; 如果是第三方的Runnable,可以将其再包裹一层vjkit中的SafeRunnable。
```java
executor.execute(ThreadPoolUtil.safeRunner(runner));
```
----
**Rule 7. 【强制】全局的非线程安全的对象可考虑使用ThreadLocal存放**
全局变量包括单例对象,static成员变量。
著名的非线程安全类包括SimpleDateFormat,MD5/SHA1的Digest。
对这些类,需要每次使用时创建。
但如果创建有一定成本,可以使用ThreadLocal存放并重用。
ThreadLocal变量需要定义成static,并在每次使用前重置。
```java
private static final ThreadLocal SHA1_DIGEST = new ThreadLocal() {
@Override
protected MessageDigest initialValue() {
try {
return MessageDigest.getInstance("SHA");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("...", e);
}
}
};
public void digest(byte[] input) {
MessageDigest digest = SHA1_DIGEST.get();
digest.reset();
return digest.digest(input);
}
```
* [Sonar-2885: Non-thread-safe fields should not be static](https://rules.sonarsource.com/java/RSPEC-2885)
* Facebook-Contrib: Correctness - Field is an instance based ThreadLocal variable
----
**Rule 8. 【推荐】缩短锁**
1) 能锁区块,就不要锁整个方法体;
```java
//锁整个方法,等价于整个方法体内synchronized(this)
public synchronized boolean foo(){};
//锁区块方法,仅对需要保护的原子操作的连续代码块进行加锁。
public boolean foo() {
synchronized(this) {
...
...
}
//other stuff
}
```
2)能用对象锁,就不要用类锁。
```java
//对象锁,只影响使用同一个对象加锁的线程
synchronized(this) {
...
}
//类锁,使用类对象作为锁对象,影响所有线程。
synchronized(A.class) {
...
}
```
----
**Rule 10. 【推荐】选择分离锁,分散锁甚至无锁的数据结构**
* 分离锁:
1) 读写分离锁ReentrantReadWriteLock,读读之间不加锁,仅在写读和写写之间加锁;
2) Array Base的queue一般是全局一把锁,而Linked Base的queue一般是队头队尾两把锁。
* 分散锁(又称分段锁):
1)如JDK7的ConcurrentHashMap,分散成16把锁;
2)对于经常写,少量读的计数器,推荐使用JDK8或vjkit封装的LongAdder对象性能更好(内部分散成多个counter,减少乐观锁的使用,取值时再相加所有counter)
* 无锁的数据结构:
1)完全无锁无等待的结构,如JDK8的ConcurrentHashMap;
2)基于CAS的无锁有等待的数据结构,如AtomicXXX系列。
----
**Rule 11. 【推荐】基于ThreadLocal来避免锁**
比如Random实例虽然是线程安全的,但其实它的seed的访问是有锁保护的。因此建议使用JDK7的ThreadLocalRandom,通过在每个线程里放一个seed来避免了加锁。
----
**Rule 12. 【推荐】规避死锁风险**
对多个资源多个对象的加锁顺序要一致。
如果无法确定完全避免死锁,可以使用带超时控制的tryLock语句加锁。
----
**Rule 13. 【推荐】volatile修饰符,AtomicXX系列的正确使用**
多线程共享的对象,在单一线程内的修改并不保证对所有线程可见。使用volatile定义变量可以解决(解决了可见性)。
但是如果多条线程并发进行基于当前值的修改,如并发的counter++,volatile则无能为力(解决不了原子性)。
此时可使用Atomic*系列:
```java
AtomicInteger count = new AtomicInteger();
count.addAndGet(2);
```
但如果需要原子地同时对多个AtomicXXX的Counter进行操作,则仍然需要使用synchronized将改动代码块加锁。
----
**Rule 14. 【推荐】延时初始化的正确写法**
通过双重检查锁(double-checked locking)实现延迟初始化存在隐患,需要将目标属性声明为volatile型,为了更高的性能,还要把volatile属性赋予给临时变量,写法复杂。
所以如果只是想简单的延迟初始化,可用下面的静态类的做法,利用JDK本身的class加载机制保证唯一初始化。
```java
private static class LazyObjectHolder {
static final LazyObject instance = new LazyObject();
}
public void myMethod() {
LazyObjectHolder.instance.doSomething();
}
```
* [Sonar-2168: Double-checked locking should not be used](https://rules.sonarsource.com/java/RSPEC-2168)
----
================================================
FILE: docs/standard/chapter10.md
================================================
# (十) 异常处理
**Rule 1. 【强制】创建异常的消耗大,只用在真正异常的场景**
构造异常时,需要获得整个调用栈,有一定消耗。
不要用来做流程控制,条件控制,因为异常的处理效率比条件判断低。
发生概率较高的条件,应该先进行检查规避,比如:IndexOutOfBoundsException,NullPointerException等,所以如果代码里捕获这些异常通常是个坏味道。
```java
//WRONG
try {
return obj.method();
} catch (NullPointerException e) {
return false;
}
//RIGHT
if (obj == null) {
return false;
}
```
* [Sonar-1696: "NullPointerException" should not be caught](https://rules.sonarsource.com/java/RSPEC-1696)
----
**Rule 2. 【推荐】在特定场景,避免每次构造异常**
如上,异常的构造函数需要获得整个调用栈。
如果异常频繁发生,且不需要打印完整的调用栈时,可以考虑绕过异常的构造函数。
1) 如果异常的message不变,将异常定义为静态成员变量;
下例定义静态异常,并简单定义一层的StackTrace。`ExceptionUtil`见vjkit。
```java
private static RuntimeException TIMEOUT_EXCEPTION = ExceptionUtil.setStackTrace(new RuntimeException("Timeout"),
MyClass.class, "mymethod");
...
throw TIMEOUT_EXCEPTION;
```
2) 如果异常的message会变化,则对静态的异常实例进行clone()再修改message。
Exception默认不是Cloneable的,`CloneableException`见vjkit。
```java
private static CloneableException TIMEOUT_EXCEPTION = new CloneableException("Timeout") .setStackTrace(My.class,
"hello");
...
throw TIMEOUT_EXCEPTION.clone("Timeout for 40ms");
```
3)自定义异常,也可以考虑重载fillStackTrace()为空函数,但相对没那么灵活,比如无法按场景指定一层的StackTrace。
----
**Rule 3. 【推荐】自定义异常,建议继承`RuntimeException`**
详见《Clean Code》,争论已经结束,不再推荐原本初衷很好的CheckedException。
因为CheckedException需要在抛出异常的地方,与捕获处理异常的地方之间,层层定义throws XXX来传递Exception,如果底层代码改动,将影响所有上层函数的签名,导致编译出错,对封装的破坏严重。对CheckedException的处理也给上层程序员带来了额外的负担。因此其他语言都没有CheckedException的设计。
----
**Rule 4. 【推荐】异常日志应包含排查问题的足够信息**
异常信息应包含排查问题时足够的上下文信息。
捕获异常并记录异常日志的地方,同样需要记录没有包含在异常信息中,而排查问题需要的信息,比如捕获处的上下文信息。
```java
//WRONG
new TimeoutException("timeout");
logger.error(e.getMessage(), e);
//RIGHT
new TimeoutException("timeout:" + eclapsedTime + ", configuration:" + configTime);
logger.error("user[" + userId + "] expired:" + e.getMessage(), e);
```
* Facebook-Contrib: Style - Method throws exception with static message string
----
**Rule 5. 异常抛出的原则**
**5.1 【推荐】尽量使用JDK标准异常,项目标准异常**
尽量使用JDK标准的Runtime异常如`IllegalArgumentException`,`IllegalStateException`,`UnsupportedOperationException`,项目定义的Exception如`ServiceException`。
**5.2 【推荐】根据调用者的需要来定义异常类,直接使用`RuntimeException`是允许的**
是否定义独立的异常类,关键是调用者会如何处理这个异常,如果没有需要特别的处理,直接抛出RuntimeException也是允许的。
----
**Rule 6. 异常捕获的原则**
**6.1 【推荐】按需要捕获异常,捕获`Exception`或`Throwable`是允许的**
如果无特殊处理逻辑,统一捕获Exception统一处理是允许的。
捕获Throwable是为了捕获Error类异常,包括其实无法处理的`OOM` `StackOverflow` `ThreadDeath`,以及类加载,反射时可能抛出的`NoSuchMethodError` `NoClassDefFoundError`等。
**6.2【推荐】多个异常的处理逻辑一致时,使用JDK7的语法避免重复代码**
```java
try {
...
} catch (AException | BException | CException ex) {
handleException(ex);
}
```
* [Sonar-2147: Catches should be combined](https://rules.sonarsource.com/java/RSPEC-2147)
----
**Rule 7.异常处理的原则**
**7.1 【强制】捕获异常一定要处理;如果故意捕获并忽略异常,须要注释写明原因**
方便后面的阅读者知道,此处不是漏了处理。
```java
//WRONG
try {
} catch(Exception e) {
}
//RIGHT
try {
} catch(Exception ignoredExcetpion) {
//continue the loop
}
```
**7.2 【强制】异常处理不能吞掉原异常,要么在日志打印,要么在重新抛出的异常里包含原异常**
```java
//WRONG
throw new MyException("message");
//RIGHT 记录日志后抛出新异常,向上次调用者屏蔽底层异常
logger.error("message", ex);
throw new MyException("message");
//RIGHT 传递底层异常
throw new MyException("message", ex);
```
* [Sonar-1166: Exception handlers should preserve the original exceptions](https://rules.sonarsource.com/java/RSPEC-1166),其中默认包含了InterruptedException, NumberFormatException,NoSuchMethodException等若干例外
**7.3 【强制】如果不想处理异常,可以不进行捕获。但最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容**
----
**Rule 8. finally块的处理原则**
**8.1 【强制】必须对资源对象、流对象进行关闭,或使用语法try-with-resource**
关闭动作必需放在finally块,不能放在try块 或 catch块,这是经典的错误。
更加推荐直接使用JDK7的try-with-resource语法自动关闭Closeable的资源,无需在finally块处理,避免潜在问题。
```java
try (Writer writer = ...) {
writer.append(content);
}
```
**8.2 【强制】如果处理过程中有抛出异常的可能,也要做try-catch,否则finally块中抛出的异常,将代替try块中抛出的异常**
```java
//WRONG
try {
...
throw new TimeoutException();
} finally {
file.close();//如果file.close()抛出IOException, 将代替TimeoutException
}
//RIGHT, 在finally块中try-catch
try {
...
throw new TimeoutException();
} finally {
IOUtil.closeQuietly(file); //该方法中对所有异常进行了捕获
}
```
* [Sonar-1163: Exceptions should not be thrown in finally blocks](https://rules.sonarsource.com/java/RSPEC-1163)
**8.3 【强制】不能在finally块中使用return,finally块中的return将代替try块中的return及throw Exception**
```java
//WRONG
try {
...
return 1;
} finally {
return 2; //实际return 2 而不是1
}
try {
...
throw TimeoutException();
} finally {
return 2; //实际return 2 而不是TimeoutException
}
```
* [Sonar-1143: Jump statements should not occur in "finally" blocks](https://rules.sonarsource.com/java/RSPEC-1143)
----
================================================
FILE: docs/standard/chapter11.md
================================================
# (十一) 日志规约
**Rule 1. 【强制】应用中不可直接使用日志库(Log4j、Logback)中的API,而应使用日志框架SLF4J中的API**
使用门面模式的日志框架,有利于维护各个类的日志处理方式统一。
```java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static Logger logger = LoggerFactory.getLogger(Foo.class);
```
----
**Rule 2. 【推荐】对不确定会否输出的日志,采用占位符或条件判断**
```java
//WRONG
logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
```
如果日志级别是info,上述日志不会打印,但是会执行1)字符串拼接操作,2)如果symbol是对象,还会执行toString()方法,浪费了系统资源,最终日志却没有打印。
```java
//RIGHT
logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);
```
但如果symbol.getMessage()本身是个消耗较大的动作,占位符在此时并没有帮助,须要改为条件判断方式来完全避免它的执行。
```java
//WRONG
logger.debug("Processing trade with id: {} symbol : {} ", id, symbol.getMessage());
//RIGHT
if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " symbol: " + symbol.getMessage());
}
```
----
**Rule 3. 【推荐】对确定输出,而且频繁输出的日志,采用直接拼装字符串的方式**
如果这是一条WARN,ERROR级别的日志,或者确定输出的INFO级别的业务日志,直接字符串拼接,比使用占位符替换,更加高效。
Slf4j的占位符并没有魔术,每次输出日志都要进行占位符的查找,字符串的切割与重新拼接。
```java
//RIGHT
logger.info("I am a business log with id: " + id + " symbol: " + symbol);
//RIGHT
logger.warn("Processing trade with id: " + id + " symbol: " + symbol);
```
----
**Rule 4. 【推荐】尽量使用异步日志**
低延时的应用,使用异步输出的形式(以AsyncAppender串接真正的Appender),可减少IO造成的停顿。
需要正确配置异步队列长度及队列满的行为,是丢弃还是等待可用,业务上允许丢弃的尽量选丢弃。
----
**Rule 5. 【强制】禁止使用性能很低的System.out()打印日志信息**
同理也禁止e.printStackTrace();
例外: 应用启动和关闭时,担心日志框架还未初始化或已关闭。
* [Sonar-106: Standard outputs should not be used directly to log anything](https://rules.sonarsource.com/java/RSPEC-106)
* [Sonar-1148: Throwable.printStackTrace(...) should not be called](https://rules.sonarsource.com/java/RSPEC-1148)
----
**Rule 6. 【强制】禁止配置日志框架输出日志打印处的类名,方法名及行号的信息**
日志框架在每次打印时,通过主动获得当前线程的StackTrace来获取上述信息的消耗非常大,尽量通过Logger名本身给出足够信息。
----
**Rule 7. 【推荐】谨慎地记录日志,避免大量输出无效日志,信息不全的日志**
大量地输出无效日志,不利于系统性能,也不利于快速定位错误点。
记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
----
**Rule 8. 【推荐】使用warn级别而不是error级别,记录外部输入参数错误的情况**
如非必要,请不在此场景打印error级别日志,避免频繁报警。
error级别只记录系统逻辑出错、异常或重要的错误信息。
----
================================================
FILE: docs/standard/chapter12.md
================================================
# (十二) 其他规约
**Rule 1. 【参考】尽量不要让魔法值(即未经定义的数字或字符串常量)直接出现在代码中**
```java
//WRONG
String key = "Id#taobao_"+tradeId;
cache.put(key, value);
```
例外:-1,0,1,2,3 不认为是魔法数
* [Sonar-109: Magic numbers should not be used](https://rules.sonarsource.com/java/RSPEC-109) 但现实中所谓魔法数还是太多,该规则不能被真正执行。
----
**Rule 2. 【推荐】时间获取的原则**
1)获取当前毫秒数System.currentTimeMillis() 而不是new Date().getTime(),后者的消耗要大得多。
2)如果要获得更精确的,且不受NTP时间调整影响的流逝时间,使用System.nanoTime()获得机器从启动到现在流逝的纳秒数。
3)如果希望在测试用例中控制当前时间的值,则使用vjkit的Clock类封装,在测试和生产环境中使用不同的实现。
----
**Rule 3. 【推荐】变量声明尽量靠近使用的分支**
不要在一个代码块的开头把局部变量一次性都声明了(这是c语言的做法),而是在第一次需要使用它时才声明。
否则如果方法已经退出或进入其他分支,就白白初始化了变量。
```java
//WRONG
Foo foo = new Foo();
if(ok){
return;
}
foo.bar();
```
----
**Rule 4. 【推荐】不要像C那样一行里做多件事情**
```java
//WRONG
fooBar.fChar = barFoo.lchar = 'c';
argv++; argc--;
int level, size;
```
* [Sonar-1659: Multiple variables should not be declared on the same line](https://rules.sonarsource.com/java/RSPEC-1659)
----
**Rule 5. 【推荐】不要为了性能而使用JNI本地方法**
Java在JIT后并不比C代码慢,JNI方法因为要反复跨越JNI与Java的边界反而有额外的性能损耗。
因此JNI方法仅建议用于调用"JDK所没有包括的, 对特定操作系统的系统调用"
----
**Rule 6. 【推荐】正确使用反射,减少性能损耗**
获取Method/Field对象的性能消耗较大, 而如果对Method与Field对象进行缓存再反复调用,则并不会比直接调用类的方法与成员变量慢(前15次使用NativeAccessor,第15次后会生成GeneratedAccessorXXX,bytecode为直接调用实际方法)
```java
//用于对同一个方法多次调用
private Method method = ....
public void foo(){
method.invoke(obj, args);
}
//用于仅会对同一个方法单次调用
ReflectionUtils.invoke(obj, methodName, args);
```
----
**Rule 7.【推荐】可降低优先级的常见代码检查规则**
1. 接口内容的定义中,去除所有modifier,如public等。 (多个public也没啥,反正大家都看惯了)
2. 工具类,定义private构造函数使其不能被实例化。(命名清晰的工具类,也没人会去实例化它,对静态方法通过类来访问也能避免实例化)
----
================================================
FILE: docs/standard/merge.bat
================================================
@echo off
type README.md > vip-java-standard.md
type chapter01.md >> vip-java-standard.md
type chapter02.md >> vip-java-standard.md
type chapter03.md >> vip-java-standard.md
type chapter04.md >> vip-java-standard.md
type chapter05.md >> vip-java-standard.md
type chapter06.md >> vip-java-standard.md
type chapter07.md >> vip-java-standard.md
type chapter08.md >> vip-java-standard.md
type chapter09.md >> vip-java-standard.md
type chapter10.md >> vip-java-standard.md
type chapter11.md >> vip-java-standard.md
type chapter12.md >> vip-java-standard.md
type ali.md >> vip-java-standard.md
echo vip-java-standard.md is ready to be converted to pdf by any tools you like.
echo press any key to exit.
pause > nul
exit
================================================
FILE: docs/standard/merge.sh
================================================
#!/bin/sh
cat README.md > vip-java-standard.md
cat chapter01.md >> vip-java-standard.md
cat chapter02.md >> vip-java-standard.md
cat chapter03.md >> vip-java-standard.md
cat chapter04.md >> vip-java-standard.md
cat chapter05.md >> vip-java-standard.md
cat chapter06.md >> vip-java-standard.md
cat chapter07.md >> vip-java-standard.md
cat chapter08.md >> vip-java-standard.md
cat chapter09.md >> vip-java-standard.md
cat chapter10.md >> vip-java-standard.md
cat chapter11.md >> vip-java-standard.md
cat chapter12.md >> vip-java-standard.md
cat ali.md >> vip-java-standard.md
echo vip-java-standard.md is ready to be converted to pdf by any tools you like.
================================================
FILE: pom.xml
================================================
4.0.0
org.sonatype.oss
oss-parent
7
com.vip.vjtools
vjtools
1.0.9-SNAPSHOT
vjtools
pom
VJTools - VIP's core libraries and tools
UTF-8
sonatype-nexus-snapshots
https://oss.sonatype.org/content/repositories/snapshots
sonatype-nexus-releases
https://oss.sonatype.org/service/local/staging/deploy/maven2
jdk7
1.7
vjkit
vjstar
vjmap
vjtop
vjmxcli
jdk8
1.8
vjkit
vjstar
vjmap
vjtop
vjmxcli
standard/sonar-vj
jdk9
9
vjkit
vjstar
release
vjkit
vjmap
vjtop
vjmxcli
org.apache.maven.plugins
maven-release-plugin
2.5.3
v.@{project.version}
org.apache.maven.plugins
maven-gpg-plugin
1.6
sign-artifacts
verify
sign
scm:git:https://github.com/vipshop/vjtools.git
scm:git:https://github.com/vipshop/vjtools.git
https://github.com/vipshop/vjtools
v.1.0.2
https://github.com/vipshop/vjtools
Apache License 2.0
http://www.apache.org/licenses/LICENSE-2.0.txt
repo
calvin
Calvin Xiao
calvin.xiao at vipshop.com
developer
+8
================================================
FILE: standard/README.md
================================================
# Java Standard
| Project | Description |
| -------- | -------- |
| [standard](https://vipshop.github.io/vjtools/#/standard/) | 唯品会Java开发手册 |
| [code formatter](formatter) | IDE格式化模板 |
| [sonar rule](sonar-vj) | Sonar规则定制示例 |
================================================
FILE: standard/formatter/README.md
================================================
# 公司通用代码格式化模板
定制原因详见[《唯品会Java开发手册》 第二章:格式规约](https://vipshop.github.io/vjtools/#/standard/chapter02),同时参考了一些Intellij IDEA默认模板的部分设置。
将下列profile下载并导入IDE即可,导入后Profile名称为`vipshop2.0`:
* [Eclipse Code Formatter Profile](https://raw.githubusercontent.com/vipshop/vjtools/master/standard/formatter/vjtools-code-conventions-eclipse.xml)
* [Intellij Code Formatter Profile](https://raw.githubusercontent.com/vipshop/vjtools/master/standard/formatter/vjtools-code-conventions-idea.xml)
因为Intellij导入Eclipse Profile存在问题,因此同时提供了两者的Profile。
## 1. 与 Eclipse 4.6 的`Eclipse [build-in]`模板的区别
* 不格式化JavaDoc
* 注释行宽从80改为120
* 打开format on/off标志
* 参考Intellij IDEA默认模板的修改(见后)
注意:Eclipse后来的build-in模板,代码行宽已经默认120。
## 2. 与IDEA默认模板的区别
本模板参考了Intellij IDEA默认模板中如下部分:
* 简单的if语句,如果没有括号,则格式化成同一行。(勾选Control Statement->if else->Keep simple 'if' on one line)
```java
if (2 < 3) return;
```
当然,我们还是建议用括号,此处格式化成一行只是兜底的保护。
* 主动输入的空行,最多可保留两行 (Blank Lines->Existing blank lines -> Number of empty lines to preserve 从1 改为 2)
* switch 和 case 之间缩进(勾选Indentation-> Indent->Statements within switch body)
```java
switch (a) {
case 0:
doCase0();
break;
default:
doDefault();
}
```
* 数组构造时不要那么多空格(取消White Space->Arrays->Array Initializers->before opening brace,after opening brace,before closing brace)
```java
int[] a = new int[]{1, 2, 3};
```
================================================
FILE: standard/formatter/vjtools-code-conventions-eclipse.xml
================================================
================================================
FILE: standard/formatter/vjtools-code-conventions-idea.xml
================================================
================================================
FILE: standard/sonar-vj/README.md
================================================
# Sonar VJ 规则
## 概述
我们使用[Sonar](https://www.sonarqube.org/)代码检查工具来辅助[《唯品会Java开发手册》](https://vipshop.github.io/vjtools/#/standard/)的落地。选择的原因是它同时提供了服务端的公共报表,及IDE端的Sonar Lint插件,而且对比PMD与FindBugs,规则也相对容易定制。
因为Sonar有些规则存在误报的情况,我们在力所能及的范围内对规则的实现进行了修改,以符合我们的规范。
## 实现方式
根据[Writing Custom Java Rules 101](https://docs.sonarqube.org/display/PLUG/Writing+Custom+Java+Rules+101),从[Sample Project](https://github.com/SonarSource/sonar-custom-rules-examples/tree/master/java-custom-rules)复制创建,对[Sonar Java](https://github.com/SonarSource/sonar-java/tree/master/java-checks/src/main/java/org/sonar/java/checks)规则进行修改。
## 使用方式
官方的Sonar Java Plugin在不断更新,以下修改未必对应其最新版,仅作为修改示例供大家参考(修改部分在代码中以//VJ 标注)。
如果需要直接使用,编译后扔进sonar的lib目录,重启sonar后取消对原规则的检查,改为使用这些编号一样,带标题带VJ字样的规则即可。
## 修改规则列表
| 编号 | 等级 | 规则描述 | 修改 |
| -------- | -------- |-------- | -------- |
| 1068| Major | Unused "private" fields should be removed | 忽略由Lombok自动生成的getter/setter的类,私有变量不算无用变量 |
| 1172| Major | Unused method parameters should be removed | 只检查private方法是否有无用参数, 忽略其他公共方法 |
| 1166| Major | Exception handlers should preserve the original exceptions | 忽略异常变量名含ignore字样的检查,可以不进行处理,如catch(Exception ignore) |
| 121| Major | Control structures should use curly braces | if语句忽略一般由IDE生成的equals()方法,以及if(condition) return;的单行模式|
| 1068| Major |Limited dependence should be placed on operator precedence rules in expressions| 忽略三目运算符,不需要加括号来清晰优先级 |
| 115| Minor| Constant names should comply with a naming convention| 忽略对枚举成员的全大写检查 |
| 1312| Minor| IP addresses should not be hardcoded | 忽略对"127.0.0.1"的检查 |
| 1291| Info | Track uses of "NOSONAR" comments| 忽略行内含 Exception/Throwable, System.in/System.err的//NOSOANR 检查 |
================================================
FILE: standard/sonar-vj/pom.xml
================================================
4.0.0
com.vip
sonar-vj
1.0.9-SNAPSHOT
sonar-plugin
6.7.1
5.0.1.12818
1.21
2.6.2
UTF-8
org.sonarsource.sonarqube
sonar-plugin-api
${sonar.version}
provided
org.sonarsource.java
sonar-java-plugin
sonar-plugin
${java.plugin.version}
provided
org.sonarsource.java
java-frontend
${java.plugin.version}
org.sonarsource.sslr-squid-bridge
sslr-squid-bridge
2.6.1
org.codehaus.sonar.sslr
sslr-core
org.codehaus.sonar
sonar-plugin-api
org.codehaus.sonar.sslr
sslr-xpath
org.slf4j
jcl-over-slf4j
org.slf4j
slf4j-api
com.google.code.gson
gson
${gson.version}
org.sonarsource.sslr
sslr-testing-harness
${sslr.version}
test
org.slf4j
slf4j-api
1.6.2
junit
junit
4.11
test
org.assertj
assertj-core
3.6.1
test
ch.qos.logback
logback-classic
0.9.30
test
org.sonarsource.sonar-packaging-maven-plugin
sonar-packaging-maven-plugin
1.17
true
vj-rules
VJ Rules
com.vip.vjkit.sonarvj.SonarPlugin
vip sonar java plugin
true
6.7
org.apache.maven.plugins
maven-compiler-plugin
3.7.0
1.8
1.8
================================================
FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/SonarCheckRegistrar.java
================================================
package com.vip.vjkit.sonarvj;
import com.vip.vjkit.sonarvj.checks.*;
import org.sonar.plugins.java.api.CheckRegistrar;
import org.sonar.plugins.java.api.JavaCheck;
import java.util.Arrays;
public class SonarCheckRegistrar implements CheckRegistrar {
@Override
public void register(RegistrarContext registrarContext) {
registrarContext.registerClassesForRepository(SonarDefinition.REPOSITORY_KEY, Arrays.asList(checkClasses()),
Arrays.asList(testCheckClasses()));
}
public static Class extends JavaCheck>[] checkClasses() {
return new Class[] { BadConstantNameCheck.class, OperatorPrecedenceCheck.class,
UnusedMethodParameterCheck.class, UnusedPrivateFieldCheck.class, MissingCurlyBracesCheck.class,
HardcodedIpCheck.class, NoSonarCheck.class, CatchUsesExceptionWithContextCheck.class };
}
public static Class extends JavaCheck>[] testCheckClasses() {
return new Class[] {};
}
}
================================================
FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/SonarDefinition.java
================================================
package com.vip.vjkit.sonarvj;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Iterables;
import com.google.common.io.Resources;
import com.google.gson.Gson;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.server.rule.RulesDefinitionAnnotationLoader;
import org.sonar.api.utils.AnnotationUtils;
import org.sonar.check.Cardinality;
import org.sonar.squidbridge.annotations.RuleTemplate;
import javax.annotation.Nullable;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;
/**
* Declare rule metadata in server repository of rules.
* That allows to list the rules in the page "Rules".
*/
public class SonarDefinition implements RulesDefinition {
// don't change that because the path is hard coded in CheckVerifier
private static final String RESOURCE_BASE_PATH = "/com/vip/java/rules";
public static final String REPOSITORY_KEY = "v-squid";
private final Gson gson = new Gson();
@Override
public void define(Context context) {
NewRepository repository = context
.createRepository(REPOSITORY_KEY, "java")
.setName("VIPAnalyzer ");
List checks = SonarRulesList.getChecks();
new RulesDefinitionAnnotationLoader().load(repository, Iterables.toArray(checks, Class.class));
for (Class ruleClass : checks) {
newRule(ruleClass, repository);
}
repository.done();
}
@VisibleForTesting
protected void newRule(Class> ruleClass, NewRepository repository) {
org.sonar.check.Rule ruleAnnotation = AnnotationUtils.getAnnotation(ruleClass, org.sonar.check.Rule.class);
if (ruleAnnotation == null) {
throw new IllegalArgumentException("No Rule annotation was found on " + ruleClass);
}
String ruleKey = ruleAnnotation.key();
if (StringUtils.isEmpty(ruleKey)) {
throw new IllegalArgumentException("No key is defined in Rule annotation of " + ruleClass);
}
NewRule rule = repository.rule(ruleKey);
if (rule == null) {
throw new IllegalStateException("No rule was created for " + ruleClass + " in " + repository.key());
}
ruleMetadata(ruleClass, rule);
rule.setTemplate(AnnotationUtils.getAnnotation(ruleClass, RuleTemplate.class) != null);
if (ruleAnnotation.cardinality() == Cardinality.MULTIPLE) {
throw new IllegalArgumentException("Cardinality is not supported, use the RuleTemplate annotation instead for " + ruleClass);
}
}
private String ruleMetadata(Class> ruleClass, NewRule rule) {
String metadataKey = rule.key();
org.sonar.java.RspecKey rspecKeyAnnotation = AnnotationUtils.getAnnotation(ruleClass, org.sonar.java.RspecKey.class);
if (rspecKeyAnnotation != null) {
metadataKey = rspecKeyAnnotation.value();
rule.setInternalKey(metadataKey);
}
addHtmlDescription(rule, metadataKey);
addMetadata(rule, metadataKey);
return metadataKey;
}
private void addMetadata(NewRule rule, String metadataKey) {
URL resource = SonarDefinition.class.getResource(RESOURCE_BASE_PATH + "/" + metadataKey + "_java.json");
if (resource != null) {
RuleMetatada metatada = gson.fromJson(readResource(resource), RuleMetatada.class);
rule.setSeverity(metatada.defaultSeverity.toUpperCase(Locale.US));
rule.setName(metatada.title);
rule.addTags(metatada.tags);
rule.setType(RuleType.valueOf(metatada.type));
rule.setStatus(RuleStatus.valueOf(metatada.status.toUpperCase(Locale.US)));
if (metatada.remediation != null) {
rule.setDebtRemediationFunction(metatada.remediation.remediationFunction(rule.debtRemediationFunctions()));
rule.setGapDescription(metatada.remediation.linearDesc);
}
}
}
private static void addHtmlDescription(NewRule rule, String metadataKey) {
URL resource = SonarDefinition.class.getResource(RESOURCE_BASE_PATH + "/" + metadataKey + "_java.html");
if (resource != null) {
rule.setHtmlDescription(readResource(resource));
}
}
private static String readResource(URL resource) {
try {
return Resources.toString(resource, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new IllegalStateException("Failed to read: " + resource, e);
}
}
private static class RuleMetatada {
String title;
String status;
@Nullable
Remediation remediation;
String type;
String[] tags;
String defaultSeverity;
}
private static class Remediation {
String func;
String constantCost;
String linearDesc;
String linearOffset;
String linearFactor;
public DebtRemediationFunction remediationFunction(DebtRemediationFunctions drf) {
if (func.startsWith("Constant")) {
return drf.constantPerIssue(constantCost.replace("mn", "min"));
}
if ("Linear".equals(func)) {
return drf.linear(linearFactor.replace("mn", "min"));
}
return drf.linearWithOffset(linearFactor.replace("mn", "min"), linearOffset.replace("mn", "min"));
}
}
}
================================================
FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/SonarPlugin.java
================================================
package com.vip.vjkit.sonarvj;
import org.sonar.api.Plugin;
/**
* Created by cloud.huang on 18/1/5.
*/
public class SonarPlugin implements Plugin {
@Override
public void define(Context context) {
context.addExtension(SonarDefinition.class);
context.addExtension(SonarCheckRegistrar.class);
}
}
================================================
FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/SonarRulesList.java
================================================
package com.vip.vjkit.sonarvj;
import com.google.common.collect.ImmutableList;
import com.vip.vjkit.sonarvj.checks.*;
import org.sonar.plugins.java.api.JavaCheck;
import java.util.List;
/**
* Created by cloud.huang on 18/1/5.
*/
public class SonarRulesList {
public static List getChecks() {
return ImmutableList.builder().addAll(getJavaChecks()).addAll(getJavaTestChecks()).build();
}
public static List> getJavaChecks() {
return ImmutableList.>builder()
.add(BadConstantNameCheck.class)
.add(OperatorPrecedenceCheck.class)
.add(UnusedPrivateFieldCheck.class)
.add(UnusedMethodParameterCheck.class)
.add(MissingCurlyBracesCheck.class)
.add(HardcodedIpCheck.class)
.add(NoSonarCheck.class)
.add(CatchUsesExceptionWithContextCheck.class)
.build();
}
public static List> getJavaTestChecks() {
return ImmutableList.>builder()
.build();
}
}
================================================
FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/BadConstantNameCheck.java
================================================
/*
* SonarQube Java Copyright (C) 2012-2018 SonarSource SA mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.vip.vjkit.sonarvj.checks;
import java.util.List;
import java.util.regex.Pattern;
import org.sonar.check.Rule;
import org.sonar.check.RuleProperty;
import org.sonar.java.checks.serialization.SerializableContract;
import org.sonar.java.resolve.JavaType;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.Modifier;
import org.sonar.plugins.java.api.tree.ModifierKeywordTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.VariableTree;
import com.google.common.collect.ImmutableList;
/**
* 不对枚举的命名进行检查
*
* 另顺便修正了isConstantTyp一个BUG,加强了报错信息
*
* https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/naming/BadConstantNameCheck.java
*
* 0d54578 Jan 8, 2018
*/
@Rule(key = "S115")
public class BadConstantNameCheck extends IssuableSubscriptionVisitor {
private static final String DEFAULT_FORMAT = "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$";
@RuleProperty(key = "format", description = "Regular expression used to check the constant names against.", defaultValue = ""
+ DEFAULT_FORMAT)
public String format = DEFAULT_FORMAT;
private Pattern pattern = null;
@Override
public List nodesToVisit() {
// VJ Replace
return ImmutableList.of(Tree.Kind.CLASS, Tree.Kind.INTERFACE, Tree.Kind.ANNOTATION_TYPE);
// return ImmutableList.of(Tree.Kind.CLASS, Tree.Kind.ENUM, Tree.Kind.INTERFACE, Tree.Kind.ANNOTATION_TYPE);
}
@Override
public void scanFile(JavaFileScannerContext context) {
if (pattern == null) {
pattern = Pattern.compile(format, Pattern.DOTALL);
}
super.scanFile(context);
}
@Override
public void visitNode(Tree tree) {
ClassTree classTree = (ClassTree) tree;
for (Tree member : classTree.members()) {
if (member.is(Tree.Kind.VARIABLE) && hasSemantic()) {
VariableTree variableTree = (VariableTree) member;
Type symbolType = variableTree.type().symbolType();
if (isConstantType(symbolType) && (classTree.is(Tree.Kind.INTERFACE, Tree.Kind.ANNOTATION_TYPE) || isStaticFinal(variableTree))) {
checkName(variableTree);
}
}
// VJ Remove //
// else if (member.is(Tree.Kind.ENUM_CONSTANT)) {
// checkName((VariableTree) member);
// }
}
}
//VJ: 加上symbolType instanceof JavaType的判断,防止数组转换出错
private static boolean isConstantType(Type symbolType) {
return symbolType.isPrimitive() || symbolType.is("java.lang.String") || symbolType.is("java.lang.Byte") ||
symbolType.is("java.lang.Character") ||
symbolType.is("java.lang.Short") ||
symbolType.is("java.lang.Integer") ||
symbolType.is("java.lang.Long") ||
symbolType.is("java.lang.Float") ||
symbolType.is("java.lang.Double") ||
symbolType.is("java.lang.Boolean");
}
private void checkName(VariableTree variableTree) {
String name = variableTree.simpleName().name();
if (!SerializableContract.SERIAL_VERSION_UID_FIELD.equals(name)
&& !pattern.matcher(name).matches()) {
//VJ 报错信息加上变量名
reportIssue(variableTree.simpleName(),
"Rename this constant name '" + name + "' to match the regular expression '" + format + "'.");
}
}
private static boolean isStaticFinal(VariableTree variableTree) {
boolean isStatic = false;
boolean isFinal = false;
for (ModifierKeywordTree modifierKeywordTree : variableTree.modifiers().modifiers()) {
Modifier modifier = modifierKeywordTree.modifier();
if (modifier == Modifier.STATIC) {
isStatic = true;
}
if (modifier == Modifier.FINAL) {
isFinal = true;
}
}
return isStatic && isFinal;
}
}
================================================
FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/CatchUsesExceptionWithContextCheck.java
================================================
package com.vip.vjkit.sonarvj.checks;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.sonar.check.Rule;
import org.sonar.check.RuleProperty;
import org.sonar.java.checks.helpers.ExpressionsHelper;
import org.sonar.java.matcher.MethodMatcher;
import org.sonar.java.matcher.TypeCriteria;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.*;
import org.sonar.plugins.java.api.tree.Tree.Kind;
import java.util.*;
/**
* catch异常的变量名包含ignore,忽略检查
*/
@Rule(key = "S1166")
public class CatchUsesExceptionWithContextCheck extends BaseTreeVisitor implements JavaFileScanner {
private static final String EXCLUDED_EXCEPTION_TYPE = "java.lang.InterruptedException, " +
"java.lang.NumberFormatException, " +
"java.lang.NoSuchMethodException, " +
"java.text.ParseException, " +
"java.net.MalformedURLException, " +
"java.time.format.DateTimeParseException";
@RuleProperty(
key = "exceptions",
description = "List of exceptions which should not be checked",
defaultValue = "" + EXCLUDED_EXCEPTION_TYPE)
public String exceptionsCommaSeparated = EXCLUDED_EXCEPTION_TYPE;
private JavaFileScannerContext context;
private Deque> validUsagesStack;
private Iterable exceptions;
private List exceptionIdentifiers;
private Set excludedCatchTrees = new HashSet<>();
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
validUsagesStack = new ArrayDeque<>();
exceptions = Splitter.on(",").trimResults().split(exceptionsCommaSeparated);
exceptionIdentifiers = Lists.newArrayList();
for (String exception : exceptions) {
exceptionIdentifiers.add(exception.substring(exception.lastIndexOf('.') + 1));
}
if (context.getSemanticModel() != null) {
scan(context.getTree());
}
excludedCatchTrees.clear();
}
@Override
public void visitTryStatement(TryStatementTree tree) {
if (containsEnumValueOf(tree.block())) {
tree.catches().stream()
.filter(c -> c.parameter().symbol().type().is("java.lang.IllegalArgumentException"))
.findAny()
.ifPresent(excludedCatchTrees::add);
}
super.visitTryStatement(tree);
}
private static boolean containsEnumValueOf(Tree tree) {
EnumValueOfVisitor visitor = new EnumValueOfVisitor();
tree.accept(visitor);
return visitor.hasEnumValueOf;
}
private static class EnumValueOfVisitor extends BaseTreeVisitor {
private static final MethodMatcher ENUM_VALUE_OF = MethodMatcher.create()
.typeDefinition(TypeCriteria.subtypeOf("java.lang.Enum"))
.name("valueOf")
.withAnyParameters();
private boolean hasEnumValueOf = false;
@Override
public void visitMethodInvocation(MethodInvocationTree tree) {
if (ENUM_VALUE_OF.matches(tree)) {
hasEnumValueOf = true;
}
super.visitMethodInvocation(tree);
}
@Override
public void visitClass(ClassTree tree) {
// skip anonymous classes
}
@Override
public void visitLambdaExpression(LambdaExpressionTree lambdaExpressionTree) {
// skip lambdas
}
}
@Override
public void visitCatch(CatchTree tree) {
if (!isExcludedType(tree.parameter().type()) && !excludedCatchTrees.contains(tree)) {
Symbol exception = tree.parameter().symbol();
//catch异常的变量名包含ignore,忽略检查
if (StringUtils.containsIgnoreCase(exception.name(), "ignore")) {
return;
}
validUsagesStack.addFirst(Lists.newArrayList(exception.usages()));
super.visitCatch(tree);
Collection usages = validUsagesStack.pop();
if (usages.isEmpty()) {
context.reportIssue(this, tree.parameter(), "Either log or rethrow this exception.");
}
}
}
@Override
public void visitMemberSelectExpression(MemberSelectExpressionTree tree) {
IdentifierTree identifier = null;
ExpressionTree expression = tree.expression();
if (expression.is(Kind.IDENTIFIER)) {
identifier = (IdentifierTree) expression;
} else if (expression.is(Kind.PARENTHESIZED_EXPRESSION) && ((ParenthesizedTree) expression).expression().is(Kind.IDENTIFIER)) {
identifier = (IdentifierTree) ((ParenthesizedTree) expression).expression();
}
if (!validUsagesStack.isEmpty() && identifier != null) {
Iterator> iterator = validUsagesStack.iterator();
while (iterator.hasNext()) {
iterator.next().remove(identifier);
}
}
super.visitMemberSelectExpression(tree);
}
private boolean isExcludedType(Tree tree) {
return isUnqualifiedExcludedType(tree) ||
isQualifiedExcludedType(tree);
}
private boolean isUnqualifiedExcludedType(Tree tree) {
return tree.is(Kind.IDENTIFIER) &&
exceptionIdentifiers.contains(((IdentifierTree) tree).name());
}
private boolean isQualifiedExcludedType(Tree tree) {
if (!tree.is(Kind.MEMBER_SELECT)) {
return false;
}
return Iterables.contains(exceptions, concatenate((MemberSelectExpressionTree) tree));
}
public static String concatenate(ExpressionTree tree) {
if(tree == null) {
return "";
}
Deque pieces = new LinkedList<>();
ExpressionTree expr = tree;
while (expr.is(Tree.Kind.MEMBER_SELECT)) {
MemberSelectExpressionTree mse = (MemberSelectExpressionTree) expr;
pieces.push(mse.identifier().name());
pieces.push(".");
expr = mse.expression();
}
if (expr.is(Tree.Kind.IDENTIFIER)) {
IdentifierTree idt = (IdentifierTree) expr;
pieces.push(idt.name());
}
StringBuilder sb = new StringBuilder();
for (String piece: pieces) {
sb.append(piece);
}
return sb.toString();
}
}
================================================
FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/HardcodedIpCheck.java
================================================
package com.vip.vjkit.sonarvj.checks;
import com.google.common.base.Splitter;
import org.sonar.check.Rule;
import org.sonar.java.model.LiteralUtils;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.LiteralTree;
import org.sonar.plugins.java.api.tree.Tree;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 忽略127.0.0.1
*
* https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/HardcodedIpCheck.java
*
* 0d54578 Jan 8, 2018
*/
@Rule(key = "S1313")
public class HardcodedIpCheck extends BaseTreeVisitor implements JavaFileScanner {
private static final Matcher IP = Pattern
.compile("([^\\d.]*\\/)?(?(?:\\d{1,3}\\.){3}\\d{1,3}(?!\\d|\\.))(\\/.*)?").matcher("");
private JavaFileScannerContext context;
@Override
public void scanFile(final JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
}
@Override
public void visitLiteral(LiteralTree tree) {
if (tree.is(Tree.Kind.STRING_LITERAL)) {
String value = LiteralUtils.trimQuotes(tree.value());
IP.reset(value);
if (IP.matches()) {
String ip = IP.group("ip");
// VJ:ADD 忽略127.0.0.1
if ("127.0.0.1".equals(ip)) {
return;
}
// VJ:END
if (areAllBelow256(Splitter.on('.').split(ip))) {
context.reportIssue(this, tree, "Make this IP \"" + ip + "\" address configurable.");
}
}
}
}
private static boolean areAllBelow256(Iterable numbersAsStrings) {
for (String numberAsString : numbersAsStrings) {
if (Integer.valueOf(numberAsString) > 255) {
return false;
}
}
return true;
}
}
================================================
FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/MissingCurlyBracesCheck.java
================================================
package com.vip.vjkit.sonarvj.checks;
import com.google.common.collect.ImmutableList;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.tree.*;
import java.util.List;
/**
* 1. equals 方法忽略if的检查 2. if(conditon) return true; 忽略在同一行的模式
*
* https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/MissingCurlyBracesCheck.java
*
* 0d54578 Jan 8, 2018
*/
@Rule(key = "S121")
public class MissingCurlyBracesCheck extends IssuableSubscriptionVisitor {
@Override
public List nodesToVisit() {
return ImmutableList.of(Tree.Kind.IF_STATEMENT, Tree.Kind.FOR_EACH_STATEMENT, Tree.Kind.FOR_STATEMENT,
Tree.Kind.WHILE_STATEMENT, Tree.Kind.DO_STATEMENT);
}
@Override
public void visitNode(Tree tree) {
switch (tree.kind()) {
case WHILE_STATEMENT:
WhileStatementTree whileStatementTree = (WhileStatementTree) tree;
checkStatement(whileStatementTree.whileKeyword(), whileStatementTree.statement());
break;
case DO_STATEMENT:
DoWhileStatementTree doWhileStatementTree = (DoWhileStatementTree) tree;
checkStatement(doWhileStatementTree.doKeyword(), doWhileStatementTree.statement());
break;
case FOR_STATEMENT:
ForStatementTree forStatementTree = (ForStatementTree) tree;
checkStatement(forStatementTree.forKeyword(), forStatementTree.statement());
break;
case FOR_EACH_STATEMENT:
ForEachStatement forEachStatement = (ForEachStatement) tree;
checkStatement(forEachStatement.forKeyword(), forEachStatement.statement());
break;
case IF_STATEMENT:
checkIfStatement((IfStatementTree) tree);
break;
default:
break;
}
}
private void checkIfStatement(IfStatementTree ifStmt) {
// equals 方法忽略if的检查, 如果if 与处理函数在同一行忽略。
if (isInEqualsMethod(ifStmt)) {
return;
}
if (!isSameLine(ifStmt)) {
checkStatement(ifStmt.ifKeyword(), ifStmt.thenStatement());
}
StatementTree elseStmt = ifStmt.elseStatement();
if (elseStmt != null && !elseStmt.is(Tree.Kind.IF_STATEMENT)) {
checkStatement(ifStmt.elseKeyword(), elseStmt);
}
}
private boolean isSameLine(IfStatementTree ifStmt) {
StatementTree thenStmt = ifStmt.thenStatement();
if (thenStmt.is(Tree.Kind.BLOCK)) {
return false;
}
return ifStmt.firstToken().line() == thenStmt.firstToken().line();
}
private boolean isInEqualsMethod(IfStatementTree ifStmt) {
Tree tree = ifStmt.parent();
while (tree != null && !(tree instanceof MethodTree)) {
tree = tree.parent();
}
if (tree == null) {
return false;
}
MethodTree methodTree = (MethodTree) tree;
if (methodTree.simpleName().toString().equals("equals")) {
return true;
}
return false;
}
private void checkStatement(SyntaxToken reportToken, StatementTree statement) {
if (!statement.is(Tree.Kind.BLOCK)) {
reportIssue(reportToken, "Missing curly brace.");
}
}
}
================================================
FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/NoSonarCheck.java
================================================
/*
* SonarQube Java Copyright (C) 2012-2018 SonarSource SA mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.vip.vjkit.sonarvj.checks;
import java.util.Collections;
import java.util.List;
import org.sonar.check.Rule;
import org.sonar.java.RspecKey;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.Tree;
/**
* 忽略在system.out, e.printStacktrace, catch exception 后设置的NONSAR
*
* 基于TooLongLineCheck完全重写
*/
@Rule(key = "NoSonar")
@RspecKey("S1291")
public class NoSonarCheck extends IssuableSubscriptionVisitor {
private static final String PATTERN = "NOSONAR";
private static final String[] IGNORE_PATTERNS = new String[] { "Exception", "Throwable", "System.out", "System.err",
"printStackTrace" };
private static final String MESSAGE = "//NOSONAR found: ";
@Override
public List nodesToVisit() {
return Collections.emptyList();
}
@Override
public void scanFile(JavaFileScannerContext context) {
super.context = context;
super.scanFile(context);
visitFile();
}
private void visitFile() {
List lines = context.getFileLines();
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i);
if (line.contains(PATTERN) && !ignoredLine(line)) {
addIssue(i + 1, MESSAGE + line);
}
}
}
private boolean ignoredLine(String line) {
for (String ignorePattern : IGNORE_PATTERNS) {
if (line.contains(ignorePattern)) {
return true;
}
}
return false;
}
}
================================================
FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/OperatorPrecedenceCheck.java
================================================
/*
* SonarQube Java
* Copyright (C) 2012-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.vip.vjkit.sonarvj.checks;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Table;
import org.apache.commons.lang.BooleanUtils;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.*;
import java.util.*;
/**
* 三目运算符中的表达式可以不要括号
*
* https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/OperatorPrecedenceCheck.java
*
* 0d54578 Jan 8, 2018
*/
@Rule(key = "S864")
public class OperatorPrecedenceCheck extends BaseTreeVisitor implements JavaFileScanner {
private static final Table OPERATORS_RELATION_TABLE;
private static final Set ARITHMETIC_OPERATORS = EnumSet.of(
Tree.Kind.MINUS,
Tree.Kind.REMAINDER,
Tree.Kind.MULTIPLY,
Tree.Kind.PLUS
);
private static final Set EQUALITY_RELATIONAL_OPERATORS = EnumSet.of(
Tree.Kind.EQUAL_TO,
Tree.Kind.GREATER_THAN,
Tree.Kind.GREATER_THAN_OR_EQUAL_TO,
Tree.Kind.LESS_THAN,
Tree.Kind.LESS_THAN_OR_EQUAL_TO,
Tree.Kind.NOT_EQUAL_TO
);
private static final Set SHIFT_OPERATORS = EnumSet.of(
Tree.Kind.LEFT_SHIFT,
Tree.Kind.RIGHT_SHIFT,
Tree.Kind.UNSIGNED_RIGHT_SHIFT
);
private static final Tree.Kind[] CONDITIONAL_EXCLUSIONS = new Tree.Kind[]{
Tree.Kind.METHOD_INVOCATION, Tree.Kind.IDENTIFIER, Tree.Kind.MEMBER_SELECT,
Tree.Kind.PARENTHESIZED_EXPRESSION, Tree.Kind.TYPE_CAST, Tree.Kind.NEW_CLASS,
Tree.Kind.ARRAY_ACCESS_EXPRESSION, Tree.Kind.NEW_ARRAY
};
static {
OPERATORS_RELATION_TABLE = HashBasedTable.create();
put(ARITHMETIC_OPERATORS, Iterables.concat(SHIFT_OPERATORS, EnumSet.of(Tree.Kind.AND, Tree.Kind.XOR, Tree.Kind.OR)));
put(SHIFT_OPERATORS, Iterables.concat(ARITHMETIC_OPERATORS, EnumSet.of(Tree.Kind.AND, Tree.Kind.XOR, Tree.Kind.OR)));
put(EnumSet.of(Tree.Kind.AND), Iterables.concat(ARITHMETIC_OPERATORS, SHIFT_OPERATORS, EnumSet.of(Tree.Kind.XOR, Tree.Kind.OR)));
put(EnumSet.of(Tree.Kind.XOR), Iterables.concat(ARITHMETIC_OPERATORS, SHIFT_OPERATORS, EnumSet.of(Tree.Kind.AND, Tree.Kind.OR)));
put(EnumSet.of(Tree.Kind.OR), Iterables.concat(ARITHMETIC_OPERATORS, SHIFT_OPERATORS, EnumSet.of(Tree.Kind.AND, Tree.Kind.XOR)));
put(EnumSet.of(Tree.Kind.CONDITIONAL_AND), EnumSet.of(Tree.Kind.CONDITIONAL_OR));
put(EnumSet.of(Tree.Kind.CONDITIONAL_OR), EnumSet.of(Tree.Kind.CONDITIONAL_AND));
}
private JavaFileScannerContext context;
private Deque stack = new LinkedList<>();
private Set reportedLines = new HashSet<>();
private static void put(Iterable firstSet, Iterable secondSet) {
for (Tree.Kind first : firstSet) {
for (Tree.Kind second : secondSet) {
OPERATORS_RELATION_TABLE.put(first, second, true);
}
}
}
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
reportedLines.clear();
scan(context.getTree());
reportedLines.clear();
}
@Override
public void visitAnnotation(AnnotationTree tree) {
stack.push(null);
for (ExpressionTree argument : tree.arguments()) {
if (argument.is(Tree.Kind.ASSIGNMENT)) {
scan(((AssignmentExpressionTree) argument).expression());
} else {
scan(argument);
}
}
stack.pop();
}
@Override
public void visitArrayAccessExpression(ArrayAccessExpressionTree tree) {
scan(tree.expression());
stack.push(null);
scan(tree.dimension());
stack.pop();
}
@Override
public void visitBinaryExpression(BinaryExpressionTree tree) {
Tree.Kind peek = stack.peek();
Tree.Kind kind = tree.kind();
if (requiresParenthesis(peek, kind)) {
raiseIssue(tree.operatorToken().line(), tree);
}
stack.push(kind);
super.visitBinaryExpression(tree);
stack.pop();
}
private static boolean requiresParenthesis(Tree.Kind kind1, Tree.Kind kind2) {
return BooleanUtils.isTrue(OPERATORS_RELATION_TABLE.get(kind1, kind2));
}
@Override
public void visitIfStatement(IfStatementTree tree) {
super.visitIfStatement(tree);
ExpressionTree condition = tree.condition();
if (condition.is(Tree.Kind.ASSIGNMENT) && EQUALITY_RELATIONAL_OPERATORS.contains(((AssignmentExpressionTree) condition).expression().kind())) {
raiseIssue(((AssignmentExpressionTree) condition).operatorToken().line(), tree);
}
}
@Override
public void visitMethodInvocation(MethodInvocationTree tree) {
scan(tree.methodSelect());
scan(tree.typeArguments());
for (ExpressionTree argument : tree.arguments()) {
stack.push(null);
scan(argument);
stack.pop();
}
}
@Override
public void visitNewArray(NewArrayTree tree) {
stack.push(null);
super.visitNewArray(tree);
stack.pop();
}
@Override
public void visitNewClass(NewClassTree tree) {
stack.push(null);
super.visitNewClass(tree);
stack.pop();
}
@Override
public void visitParenthesized(ParenthesizedTree tree) {
stack.push(null);
super.visitParenthesized(tree);
stack.pop();
}
@Override
public void visitConditionalExpression(ConditionalExpressionTree tree) {
checkConditionalOperand(tree.trueExpression());
checkConditionalOperand(tree.falseExpression());
super.visitConditionalExpression(tree);
}
private void checkConditionalOperand(ExpressionTree tree) {
if (!(tree.is(CONDITIONAL_EXCLUSIONS) || tree instanceof LiteralTree || tree instanceof UnaryExpressionTree)) {
//VJ ADD 三目运算符中的表达式可以不要括号
if (tree.parent() != null && tree.parent().kind().equals(Tree.Kind.CONDITIONAL_EXPRESSION)) {
return;
}
//VJ END
raiseIssue(tree.firstToken().line(), tree);
}
}
private void raiseIssue(int line, Tree tree) {
if (reportedLines.add(line)) {
context.reportIssue(this, tree, "Add parentheses to make the operator precedence explicit.");
}
}
}
================================================
FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/UnusedMethodParameterCheck.java
================================================
/*
* SonarQube Java
* Copyright (C) 2012-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.vip.vjkit.sonarvj.checks;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.sonar.check.Rule;
import org.sonar.java.model.ModifiersUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Modifier;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.VariableTree;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
* 只判读private方法里的无用参数
*
* https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedMethodParameterCheck.java
*
* 2c86dbb Jan 12, 2018
*/
@Rule(key = "S1172")
public class UnusedMethodParameterCheck extends IssuableSubscriptionVisitor {
private static final String AUTHORIZED_ANNOTATION = "javax.enterprise.event.Observes";
private static final String STRUTS_ACTION_SUPERCLASS = "org.apache.struts.action.Action";
private static final Collection EXCLUDED_STRUTS_ACTION_PARAMETER_TYPES = ImmutableList.of(
"org.apache.struts.action.ActionMapping", "org.apache.struts.action.ActionForm",
"javax.servlet.http.HttpServletRequest", "javax.servlet.http.HttpServletResponse");
@Override
public List nodesToVisit() {
return ImmutableList.of(Tree.Kind.METHOD, Tree.Kind.CONSTRUCTOR);
}
@Override
public void visitNode(Tree tree) {
if (!hasSemantic()) {
return;
}
MethodTree methodTree = (MethodTree) tree;
if (methodTree.block() == null || !isIncluded(methodTree)) {
return;
}
List unused = Lists.newArrayList();
for (VariableTree var : methodTree.parameters()) {
Symbol symbol = var.symbol();
if (symbol.usages().isEmpty() && !symbol.metadata().isAnnotatedWith(AUTHORIZED_ANNOTATION)
&& !isStrutsActionParameter(var)) {
unused.add(var.simpleName());
}
}
Set unresolvedIdentifierNames = unresolvedIdentifierNames(methodTree.block());
// kill the noise regarding unresolved identifiers, and remove the one with matching names from the list of
// unused
unused = unused.stream().filter(id -> !unresolvedIdentifierNames.contains(id.name()))
.collect(Collectors.toList());
if (!unused.isEmpty()) {
reportUnusedParameters(unused);
}
}
private void reportUnusedParameters(List unused) {
List locations = new ArrayList<>();
for (IdentifierTree identifier : unused) {
locations.add(new JavaFileScannerContext.Location(
"Remove this unused method parameter " + identifier.name() + "\".", identifier));
}
IdentifierTree firstUnused = unused.get(0);
String msg;
if (unused.size() > 1) {
msg = "Remove these unused method parameters.";
} else {
msg = "Remove this unused method parameter \"" + firstUnused.name() + "\".";
}
reportIssue(firstUnused, msg, locations, null);
}
// VJ: 改为只判读private方法里的无用参数,替代原isExclude
// 大量删除原来exclude里的复杂判断
private static boolean isIncluded(MethodTree tree) {
return isPrivateMethod(tree);
}
private static boolean isStrutsActionParameter(VariableTree variableTree) {
Type superClass = variableTree.symbol().enclosingClass().superClass();
return superClass != null && superClass.isSubtypeOf(STRUTS_ACTION_SUPERCLASS)
&& EXCLUDED_STRUTS_ACTION_PARAMETER_TYPES.contains(variableTree.symbol().type().fullyQualifiedName());
}
private static boolean isPrivateMethod(MethodTree methodTree) {
return ModifiersUtils.hasModifier(methodTree.modifiers(), Modifier.PRIVATE);
}
private static Set unresolvedIdentifierNames(Tree tree) {
UnresolvedIdentifierVisitor visitor = new UnresolvedIdentifierVisitor();
tree.accept(visitor);
return visitor.unresolvedIdentifierNames;
}
private static class UnresolvedIdentifierVisitor extends BaseTreeVisitor {
private Set unresolvedIdentifierNames = new HashSet<>();
@Override
public void visitMemberSelectExpression(MemberSelectExpressionTree tree) {
// skip annotations and identifier, a method parameter will only be used in expression side (before the dot)
scan(tree.expression());
}
@Override
public void visitMethodInvocation(MethodInvocationTree tree) {
ExpressionTree methodSelect = tree.methodSelect();
if (!methodSelect.is(Tree.Kind.IDENTIFIER)) {
// not interested in simple method invocations, we are targeting usage of method parameters
scan(methodSelect);
}
scan(tree.typeArguments());
scan(tree.arguments());
}
@Override
public void visitIdentifier(IdentifierTree tree) {
if (tree.symbol().isUnknown()) {
unresolvedIdentifierNames.add(tree.name());
}
super.visitIdentifier(tree);
}
}
}
================================================
FILE: standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/UnusedPrivateFieldCheck.java
================================================
/*
* SonarQube Java Copyright (C) 2012-2018 SonarSource SA mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.vip.vjkit.sonarvj.checks;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.sonar.check.Rule;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.java.model.ModifiersUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.ExpressionStatementTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.ImportTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.MethodReferenceTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Modifier;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.Tree.Kind;
import org.sonar.plugins.java.api.tree.VariableTree;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
/**
* 不检查lombok自动生成getter/setter的类
*
* https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java
* 0d54578 Jan 8, 2018
*/
@Rule(key = "S1068")
public class UnusedPrivateFieldCheck extends IssuableSubscriptionVisitor {
private static final Tree.Kind[] ASSIGNMENT_KINDS = { Tree.Kind.ASSIGNMENT, Tree.Kind.MULTIPLY_ASSIGNMENT,
Tree.Kind.DIVIDE_ASSIGNMENT, Tree.Kind.REMAINDER_ASSIGNMENT, Tree.Kind.PLUS_ASSIGNMENT,
Tree.Kind.MINUS_ASSIGNMENT, Tree.Kind.LEFT_SHIFT_ASSIGNMENT, Tree.Kind.RIGHT_SHIFT_ASSIGNMENT,
Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, Tree.Kind.AND_ASSIGNMENT, Tree.Kind.XOR_ASSIGNMENT,
Tree.Kind.OR_ASSIGNMENT };
private List classes = Lists.newArrayList();
private ListMultimap assignments = ArrayListMultimap.create();
private Set unknownIdentifiers = new HashSet<>();
private boolean hasNativeMethod = false;
private boolean lombokClass = false;
@Override
public List nodesToVisit() {
return ImmutableList.of(Tree.Kind.IMPORT, Tree.Kind.CLASS, Tree.Kind.METHOD, Tree.Kind.EXPRESSION_STATEMENT,
Tree.Kind.IDENTIFIER);
}
@Override
public void scanFile(JavaFileScannerContext context) {
super.scanFile(context);
if (!hasNativeMethod && !lombokClass) {
classes.forEach(this::checkClassFields);
}
classes.clear();
assignments.clear();
unknownIdentifiers.clear();
hasNativeMethod = false;
lombokClass = false;
}
@Override
public void visitNode(Tree tree) {
if (!hasSemantic()) {
return;
}
switch (tree.kind()) {
case METHOD:
checkIfNativeMethod((MethodTree) tree);
break;
case CLASS:
classes.add((ClassTree) tree);
break;
case IMPORT:// VJ
checkIfLombokClass((ImportTree) tree);
break;
case EXPRESSION_STATEMENT:
collectAssignment(((ExpressionStatementTree) tree).expression());
break;
case IDENTIFIER:
collectUnknownIdentifier((IdentifierTree) tree);
break;
default:
throw new IllegalStateException("Unexpected subscribed tree.");
}
}
// VJ
private void checkIfLombokClass(ImportTree tree) {
String importStr = fullQualifiedName(tree.qualifiedIdentifier());
if (importStr.contains("lombok")) {
lombokClass = true;
}
}
// from WildcardImportsShouldNotBeUsedCheck
private static String fullQualifiedName(Tree tree) {
if (tree.is(Tree.Kind.IDENTIFIER)) {
return ((IdentifierTree) tree).name();
} else if (tree.is(Tree.Kind.MEMBER_SELECT)) {
MemberSelectExpressionTree m = (MemberSelectExpressionTree) tree;
return fullQualifiedName(m.expression()) + "." + m.identifier().name();
}
throw new UnsupportedOperationException(String.format("Kind/Class '%s' not supported", tree.getClass()));
}
private void collectUnknownIdentifier(IdentifierTree identifier) {
if (identifier.symbol().isUnknown() && !isMethodIdentifier(identifier)) {
unknownIdentifiers.add(identifier.name());
}
}
private static boolean isMethodIdentifier(IdentifierTree identifier) {
Tree parent = identifier.parent();
while (parent != null && !parent.is(Tree.Kind.METHOD_INVOCATION, Tree.Kind.METHOD_REFERENCE)) {
parent = parent.parent();
}
if (parent == null) {
return false;
}
if (parent.is(Tree.Kind.METHOD_INVOCATION)) {
return identifier.equals(methodName((MethodInvocationTree) parent));
} else {
return identifier.equals(((MethodReferenceTree) parent).method());
}
}
//VJ: copy from MethodsHelper
public static IdentifierTree methodName(MethodInvocationTree mit) {
IdentifierTree id;
if (mit.methodSelect().is(Tree.Kind.IDENTIFIER)) {
id = (IdentifierTree) mit.methodSelect();
} else {
id = ((MemberSelectExpressionTree) mit.methodSelect()).identifier();
}
return id;
}
private void checkIfNativeMethod(MethodTree method) {
if (ModifiersUtils.hasModifier(method.modifiers(), Modifier.NATIVE)) {
hasNativeMethod = true;
}
}
private void checkClassFields(ClassTree classTree) {
classTree.members().stream().filter(member -> member.is(Tree.Kind.VARIABLE)).map(VariableTree.class::cast)
.forEach(this::checkIfUnused);
}
public void checkIfUnused(VariableTree tree) {
if (hasNoAnnotation(tree)) {
Symbol symbol = tree.symbol();
String name = symbol.name();
if (symbol.isPrivate() && onlyUsedInVariableAssignment(symbol) && !"serialVersionUID".equals(name)
&& !unknownIdentifiers.contains(name)) {
reportIssue(tree.simpleName(), "Remove this unused \"" + name + "\" private field.");
}
}
}
private boolean onlyUsedInVariableAssignment(Symbol symbol) {
return symbol.usages().size() == assignments.get(symbol).size();
}
private static boolean hasNoAnnotation(VariableTree tree) {
return tree.modifiers().annotations().isEmpty();
}
private void collectAssignment(ExpressionTree expressionTree) {
if (expressionTree.is(ASSIGNMENT_KINDS)) {
addAssignment(((AssignmentExpressionTree) expressionTree).variable());
}
}
private void addAssignment(ExpressionTree tree) {
ExpressionTree variable = ExpressionUtils.skipParentheses(tree);
if (variable.is(Tree.Kind.IDENTIFIER)) {
addAssignment((IdentifierTree) variable);
} else if (variable.is(Tree.Kind.MEMBER_SELECT)) {
addAssignment(((MemberSelectExpressionTree) variable).identifier());
}
}
private void addAssignment(IdentifierTree identifier) {
Symbol reference = identifier.symbol();
if (!reference.isUnknown()) {
assignments.put(reference, identifier);
}
}
}
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1068_java.html
================================================
VJ update: 使用lobmok的类不告警
If a private field is declared but not used in the program, it can be considered dead code and should therefore be removed. This will
improve maintainability because developers will not wonder what the variable is used for.
Note that this rule does not take reflection into account, which means that issues will be raised on private fields that are only
accessed using the reflection API.
Noncompliant Code Example
public class MyClass {
private int foo = 42;
public int compute(int a) {
return a * 42;
}
}
Compliant Solution
public class MyClass {
public int compute(int a) {
return a * 42;
}
}
Exceptions
The Java serialization runtime associates with each serializable class a version number, called serialVersionUID, which is used during
deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to
serialization.
A serializable class can declare its own serialVersionUID explicitly by declaring a field named serialVersionUID that
must be static, final, and of type long. By definition those serialVersionUID fields should not be reported by this rule:
public class MyClass implements java.io.Serializable {
private static final long serialVersionUID = 42L;
}
Moreover, this rule doesn't raise any issue on annotated fields.
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1068_java.json
================================================
{
"title": "VJ: Unused \"private\" fields should be removed",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "5min"
},
"tags": [
"cert",
"unused"
],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-1068",
"sqKey": "S1068"
}
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S115_java.html
================================================
VJ update: 枚举可以是小写
Shared coding conventions allow teams to collaborate efficiently. This rule checks that all constant names match a provided regular expression.
Noncompliant Code Example
With the default regular expression ^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$:
public class MyClass {
public static final int first = 1;
}
Compliant Solution
public class MyClass {
public static final int FIRST = 1;
}
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S115_java.json
================================================
{
"title": "VJ: Constant names should comply with a naming convention",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "2min"
},
"tags": [
"convention"
],
"defaultSeverity": "Minor"
}
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1166_java.html
================================================
VJ update: catch异常的变量名包含ignore,忽略检查
When handling a caught exception, the original exception's message and stack trace should be logged or passed forward.
Noncompliant Code Example
try {
/* ... */
} catch (Exception e) { // Noncompliant - exception is lost
LOGGER.info("context");
}
try {
/* ... */
} catch (Exception e) { // Noncompliant - exception is lost (only message is preserved)
LOGGER.info(e.getMessage());
}
try {
/* ... */
} catch (Exception e) { // Noncompliant - original exception is lost
throw new RuntimeException("context");
}
Compliant Solution
try {
/* ... */
} catch (Exception e) {
LOGGER.info(e); // exception is logged
}
try {
/* ... */
} catch (Exception e) {
throw new RuntimeException(e); // exception stack trace is propagated
}
try {
/* ... */
} catch (RuntimeException e) {
doSomething();
throw e; // original exception passed forward
} catch (Exception e) {
throw new RuntimeException(e); // Conversion into unchecked exception is also allowed
}
Exceptions
InterruptedException, NumberFormatException, DateTimeParseException, ParseException and
MalformedURLException exceptions are arguably used to indicate nonexceptional outcomes. Similarly, handling
NoSuchMethodException is often required when dealing with the Java reflection API.
Because they are part of Java, developers have no choice but to deal with them. This rule does not verify that those particular exceptions are
correctly handled.
int myInteger;
try {
myInteger = Integer.parseInt(myString);
} catch (NumberFormatException e) {
// It is perfectly acceptable to not handle "e" here
myInteger = 0;
}
See
CERT, ERR00-J. - Do not suppress or ignore checked exceptions
OWASP Top 10 2017 Category A10 - Insufficient Logging & Monitoring
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1166_java.json
================================================
{
"title": "VJ:Exception handlers should preserve the original exceptions",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "10min"
},
"tags": [
"error-handling",
"owasp-a10",
"cert",
"suspicious"
],
"standards": [
"CERT"
],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-1166",
"sqKey": "S1166"
}
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1172_java.html
================================================
VJ update:只判断private函数
Unused parameters for private method misleading. Whatever the values passed to such parameters, the behavior will be the same.
Noncompliant Code Example
private void doSomething(int a, int b) { // "b" is unused
compute(a);
}
Compliant Solution
private void doSomething(int a) {
compute(a);
}
Exceptions
The rule will not raise issues for protected or public methods
See
MISRA C++:2008, 0-1-11 - There shall be no unused parameters (named or unnamed) in nonvirtual functions.
MISRA C:2012, 2.7 - There should be no unused parameters in functions
CERT, MSC12-C. - Detect and remove code that has no effect or is never
executed
CERT, MSC12-CPP. - Detect and remove code that has no effect
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1172_java.json
================================================
{
"title": "VJ: Unused private method parameters should be removed",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "5min"
},
"tags": [
"misra",
"cert",
"unused"
],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-1172",
"sqKey": "S1172"
}
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S121_java.html
================================================
VJ update: IDE自动生成的方法如equals忽略
While not technically incorrect, the omission of curly braces can be misleading, and may lead to the introduction of errors during maintenance.
Noncompliant Code Example
if (condition) // Noncompliant
executeSomething();
Compliant Solution
if (condition) {
executeSomething();
}
See
MISRA C:2004, 14.8 - The statement forming the body of a switch, while, do ... while or for statement shall be a compound statement
MISRA C:2004, 14.9 - An if (expression) construct shall be followed by a compound statement. The else keyword shall be followed by either a
compound statement, or another if statement
MISRA C++:2008, 6-3-1 - The statement forming the body of a switch, while, do ... while or for statement shall be a compound statement
MISRA C++:2008, 6-4-1 - An if (condition) construct shall be followed by a compound statement. The else keyword shall be followed by either a
compound statement, or another if statement
MISRA C:2012, 15.6 - The body of an iteration-statement or a selection-statement shall be a compound-statement
CERT, EXP19-C. - Use braces for the body of an if, for, or while statement
CERT, EXP52-J. - Use braces for the body of an if, for, or while statement
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S121_java.json
================================================
{
"title": "VJ: Control structures should use curly braces",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "2min"
},
"tags": [
"misra",
"cert",
"pitfall"
],
"standards": [
"CERT"
],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-121",
"sqKey": "S121"
}
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1291_java.html
================================================
VJ update: 忽略在异常捕获语句和System.out后的NOSONAR
Any issue to quality rule can be deactivated with the NOSONAR marker. This marker is pretty useful to exclude false-positive results
but it can also be used abusively to hide real quality flaws.
This rule raises an issue when NOSONAR is used.
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1291_java.json
================================================
{
"title": "VJ: Track uses of \"NOSONAR\" comments",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "1min"
},
"tags": [
"bad-practice"
],
"defaultSeverity": "Info",
"ruleSpecification": "RSPEC-1291",
"sqKey": "S1291"
}
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1313_java.html
================================================
VJ update: 忽略"127.0.0.1"
Hardcoding an IP address into source code is a bad idea for several reasons:
a recompile is required if the address changes
it forces the same address to be used in every environment (dev, sys, qa, prod)
it places the responsibility of setting the value to use in production on the shoulders of the developer
it allows attackers to decompile the code and thereby discover a potentially sensitive address
Noncompliant Code Example
String ip = "200.112.44.55";
Socket socket = new Socket(ip, 6667);
Compliant Solution
String ip = System.getProperty("myapplication.ip");
Socket socket = new Socket(ip, 6667);
See
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S1313_java.json
================================================
{
"title": "VJ: IP addresses should not be hardcoded",
"type": "VULNERABILITY",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "30min"
},
"tags": [
"cert"
],
"standards": [
"CERT"
],
"defaultSeverity": "Minor",
"ruleSpecification": "RSPEC-1313",
"sqKey": "S1313"
}
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S864_java.html
================================================
VJ update: 三目运算符中的简单表达式可以不要括号
The rules of operator precedence are complicated and can lead to errors. For this reason, parentheses should be used for clarification in complex
statements. However, this does not mean that parentheses should be gratuitously added around every operation.
This rule raises issues when && and || are used in combination, when assignment and equality or relational
operators are used in together in a condition, and for other operator combinations according to the following table:
+, -, *, /, %
<<, >>, >>>
&
^
|
+, -, *, /, %
x
x
x
x
<<, >>, >>>
x
x
x
x
&
x
x
x
x
^
x
x
x
x
|
x
x
x
x
Noncompliant Code Example
x = a + b - c;
x = a + 1 << b; // Noncompliant
if ( a > b || c < d || a == d) {...}
if ( a > b && c < d || a == b) {...} // Noncompliant
if (a = f(b,c) == 1) { ... } // Noncompliant; == evaluated first
Compliant Solution
x = a + b - c;
x = (a + 1) << b;
if ( a > b || c < d || a == d) {...}
if ( (a > b && c < d) || a == b) {...}
if ( (a = f(b,c)) == 1) { ... }
See
MISRA C:2004, 12.1 - Limited dependence should be placed on C's operator precedence rules in expressions
MISRA C:2004, 12.2 - The value of an expression shall be the same under any order of evaluation that the standard permits.
MISRA C:2004, 12.5 - The operands of a logical && or || shall be primary-expressions.
MISRA C++:2008, 5-0-1 - The value of an expression shall be the same under any order of evaluation that the standard permits.
MISRA C++:2008, 5-0-2 - Limited dependence should be placed on C++ operator precedence rules in expressions
MISRA C++:2008, 5-2-1 - Each operand of a logical && or || shall be a postfix-expression.
MISRA C:2012, 12.1 - The precedence of operators within expressions should be made explicit
CERT, EXP00-C. - Use parentheses for precedence of operation
CERT, EXP00-CPP. - Use parentheses for precedence of operation
CERT, EXP53-J. - Use parentheses for precedence of operation
MITRE, CWE-783 - Operator Precedence Logic Error
================================================
FILE: standard/sonar-vj/src/main/resources/com/vip/java/rules/S864_java.json
================================================
{
"title": "VJ: Limited dependence should be placed on operator precedence rules in expressions",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "2min"
},
"tags": [
"cwe",
"misra",
"cert"
],
"standards": [
"CWE",
"CERT"
],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-864",
"sqKey": "S864"
}
================================================
FILE: vjdump/README.md
================================================
# 1. 概述
VJDump是线上JVM数据紧急收集脚本。
它可以在紧急场景下(比如马上要对进程进行重启),一键收集jstack、jmap以及GC日志等相关信息,并以zip包保存(默认在目录`/tmp/vjtools/vjdump`下),保证在紧急情况下仍能收集足够的问题排查信息,减轻运维团队的工作量,以及与开发团队的沟通成本。
收集数据包括:
* thread dump数据:`jstack -l $PID`
* vjtop JVM概况及繁忙线程:`vjtop.sh -n 1 $PID` (需要将vjtop.sh 加入用户的PATH变量中)
* jmap histo 堆对象统计数据:`jmap -histo $PID` & `jmap -histo:live $PID`
* GC日志(如果JVM有设定GC日志输出)
* heap dump数据(需指定--liveheap开启):`jmap -dump:live,format=b,file=${DUMP_FILE} $PID`
# 2. 下载
[vjdump.sh](https://raw.githubusercontent.com/vipshop/vjtools/master/vjdump/vjdump.sh)
# 3. 快速入门
以目标JVM相同用户或root用户运行脚本:
```shell
# 对指定的进程PID进行急诊
vjdump.sh $pid
# 额外收集heap dump信息(jmap -dump:live的信息)
vjdump.sh --liveheap $pid
```
在收集过程中,某些命令如`jmap -histo:live $PID` 会造成JVM停顿,因此仅用于紧急情况或已摘流量的情况。为了避免连续停顿,在每条会造成停顿的收集指令之间,默认插入了1秒的执行间隔。
================================================
FILE: vjdump/README_EN.md
================================================
# VJDump
VJDump comes as a handy script for collecting diagnostic data for JVM during urgent failures to
**allow for complete analysis later**.
# 1.Introduction
When major system failures occur for reasons not yet known, rebooting may be the only option to ease user complaints.
There is so little time that even an experienced system admin might forget to pick up all he needs for offline analysis.
VJDump script is written to make us eaiser, which just packs outputs from T
jstack, jmap, gc logs into the form of a single zip file under `/tmp/vjtools/vjdump`.
**[Important]**: Commands like jstack and jmap DO cause stop-of-the-world of the target app. Make sure the target app
is isolated from user access before you run this full check in production.
Items to be collected by VJDump are:
* thread dump via `jstack -l $PID`
* vjtop JVM overview and busy threads snapshot via `vjtop.sh -n 1 -d 3 $PID` (enabled when you have our vjtop installed and
have the folder of vjtop.sh appended to the PATH environment variable)
* jmap histo object statistics via`jmap -histo $PID` & `jmap -histo:live $PID`
* GC logs if available
* heap dump (which can be optionally switched on via --liveheap):`jmap -dump:live,format=b,file=${DUMP_FILE} $PID`
# 2. Download
[vjdump.sh](https://raw.githubusercontent.com/vipshop/vjtools/master/vjdump/vjdump.sh)
# 3. Getting Started
Use the following commands under **the same user who started the target process** and collect results in `/tmp/vjtools/vjdump`.
```shell
# collect dignostics for target $pid
vjdump.sh $pid
# include heap dump also via jmap -dump:live, might take longer
vjdump.sh --liveheap $pid
```
================================================
FILE: vjdump/vjdump.sh
================================================
#!/bin/bash
USAGE()
{
echo "usage: $0 [--liveheap][-nz|--nozip][-i|--interval] "
}
if [ $# -lt 1 ]; then
USAGE
exit -1
fi
BASEDIR=/tmp/vjtools
LOGDIR=${BASEDIR}/vjdump
SLEEP_TIME=1
CLOSE_COMPRESS=0
NEED_HEAP_DUMP=0
PID="$1"
while true; do
case "$1" in
-i|--interval) SLEEP_TIME="$2"; PID="$3"; shift 1;;
-nz|--nozip) CLOSE_COMPRESS=1; PID="$2"; shift;;
--liveheap) NEED_HEAP_DUMP=1; PID="$2"; shift;;
*) break;;
esac
done
CMD="$1"
shift
START()
{
if [[ x"$PID" == x ]]; then
echo -e "The pid is empty, please enter pid".
exit -1
else
echo -e "The pid is ${PID}"
fi
# try to find $JAVA_HOME if not set
if [ -z "$JAVA_HOME" ] ; then
JAVA_HOME=`readlink -f \`which java 2>/dev/null\` 2>/dev/null | \
sed 's/\jre\/bin\/java//' | sed 's/\/bin\/java//'`
fi
if [ ! -f "$JAVA_HOME/bin/jstack" ] ; then
echo -e "\033[31m\$JAVA_HOME not found. please export JAVA_HOME manually.\033[0m"
exit -1
fi
# clean all history logs
rm -rf ${LOGDIR}/*.log ${LOGDIR}/*jmap_dump_live-*.bin
mkdir -p ${LOGDIR}
DATE=$(date "+%Y%m%d%H%M%S")
echo -e "\033[34m$(date '+%Y-%m-%d %H:%M:%S') vjdump begin. command interval is ${SLEEP_TIME}s.\033[0m"
# jstack
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process jstack."
JSTACK_LOG=${LOGDIR}/jstack-${PID}-${DATE}.log
${JAVA_HOME}/bin/jstack -l $PID > ${JSTACK_LOG}
if [[ $? != 0 ]]; then
echo -e "\033[31mprocess jstack error.\033[0m"
fi
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process jstack."
sleep ${SLEEP_TIME}
# vjtop
VJTOP_SCRIPT=vjtop.sh
which $VJTOP_SCRIPT 2>/dev/null
if [[ $? == 0 ]]; then
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process vjtop."
echo -e "It will take 3 seconds, please wait."
VJTOP_LOG=${LOGDIR}/vjtop-${PID}-${DATE}.log
$VJTOP_SCRIPT -n 3 -d 1 $PID > ${VJTOP_LOG}
if [[ $? != 0 ]]; then
echo -e "\033[31mprocess vjtop error.\033[0m"
fi
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process vjtop."
else
# no vjtop, use other replacement
# jinfo -flags $PID
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process jinfo -flags."
JINFO_FLAGS_LOG=${LOGDIR}/jinfo-flags-${PID}-${DATE}.log
${JAVA_HOME}/bin/jinfo -flags $PID 1>${JINFO_FLAGS_LOG} 2>&1
if [[ $? != 0 ]]; then
echo -e "\033[31mprocess jinfo -flags error.\033[0m"
fi
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process jinfo -flags."
#jmap -heap
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process jmap -heap."
JMAP_HEAP_LOG=${LOGDIR}/jmap_heap-${PID}-${DATE}.log
${JAVA_HOME}/bin/jmap -heap $PID > ${JMAP_HEAP_LOG}
if [[ $? != 0 ]]; then
echo -e "\033[31mprocess jmap -heap error.\033[0m"
fi
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process jmap -heap."
fi
# jmap -histo
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process jmap -histo."
JMAP_HISTO_LOG=${LOGDIR}/jmap_histo-${PID}-${DATE}.log
${JAVA_HOME}/bin/jmap -histo $PID > ${JMAP_HISTO_LOG}
if [[ $? != 0 ]]; then
echo -e "\033[31mprocess jmap -histo error.\033[0m"
fi
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process jmap -histo."
sleep ${SLEEP_TIME}
# jmap -histo:live
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process jmap -histo:live."
JMAP_HISTO_LIVE_LOG=${LOGDIR}/jmap_histo_live-${PID}-${DATE}.log
${JAVA_HOME}/bin/jmap -histo:live $PID > ${JMAP_HISTO_LIVE_LOG}
if [[ $? != 0 ]]; then
echo -e "\033[31mprocess jmap -histo:live error.\033[0m"
fi
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process jmap -histo:live."
sleep ${SLEEP_TIME}
# jmap -dump:live
if [[ $NEED_HEAP_DUMP == 1 ]]; then
JMAP_DUMP_FILE=${LOGDIR}/jmap_dump_live-${PID}-${DATE}.bin
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process jmap -dump:live."
${JAVA_HOME}/bin/jmap -dump:live,format=b,file=${JMAP_DUMP_FILE} $PID
if [[ $? != 0 ]]; then
echo -e "\033[31mprocess jmap -dump:live error.\033[0m"
fi
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process jmap -dump:live."
sleep ${SLEEP_TIME}
fi
# gc log
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to process gc log."
GCLOG=$(strings /proc/${PID}/cmdline |grep '\-Xloggc' |cut -d : -f 2)
if [[ x"$GCLOG" == x ]]; then
echo -e "No GC log existing."
else
# "\cp" means unalias cp, it can cover files without prompting
\cp -rf $GCLOG ${LOGDIR}/
if [[ $? != 0 ]]; then
echo -e "copy gc log error."
fi
fi
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to process gc log."
# packaging
if [[ $CLOSE_COMPRESS == 1 ]]; then
echo -e "The zip option is closed, no zip package will be generated."
else
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to zip all files."
# zip files without heap dump
if [ -x "$(command -v zip)" ]; then
COMPRESS_FILE=${BASEDIR}/vjdump-${PID}-${DATE}.zip
zip -j ${COMPRESS_FILE} ${LOGDIR}/*.log
else
COMPRESS_FILE=${BASEDIR}/vjdump-${PID}-${DATE}.tar.gz
(cd ${LOGDIR} && tar -zcvf ${COMPRESS_FILE} *.log)
fi
if [[ $? != 0 ]]; then
echo -e "\033[31mzip files error.\033[0m"
else
echo -e "zip files success, the zip file is \033[34m${ZIP_FILE}\033[0m"
fi
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to zip all files."
if [[ $NEED_HEAP_DUMP == 1 ]]; then
# compress all files
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Begin to zip files which include dump file."
if [ -x "$(command -v zip)" ]; then
COMPRESS_FILE_WITH_HEAP_DUMP=${BASEDIR}/vjdump-with-heap-${PID}-${DATE}.zip
zip -j ${COMPRESS_FILE_WITH_HEAP_DUMP} ${LOGDIR}/*.log ${JMAP_DUMP_FILE}
else
COMPRESS_FILE_WITH_HEAP_DUMP=${BASEDIR}/vjdump-with-heap-${PID}-${DATE}.tar.gz
(cd ${LOGDIR} && tar -zcvf ${COMPRESS_FILE_WITH_HEAP_DUMP} *.log *.bin)
fi
if [[ $? != 0 ]]; then
echo -e "\033[31mzip files which include dump file error.\033[0m"
else
echo -e "zip files which include dump file success, the zip path is \033[34m${ZIP_FILE_WITH_HEAP_DUMP}\033[0m"
fi
echo -e "$(date '+%Y-%m-%d %H:%M:%S') Finish to zip files which include dump file."
fi
fi
echo -e "\033[34m$(date '+%Y-%m-%d %H:%M:%S') vjdump finish. \033[0m"
}
case "$CMD" in
help) USAGE;;
*) START;;
esac
================================================
FILE: vjkit/README.md
================================================
# 1. Overview
唯品会Java开发基础类库,综合各门各派众多开源类库的精华而成, 让开发人员避免底层代码的重复开发,默认就拥有最佳实践,尤其在性能的方面。
综合众多开源类库的精华而成, 让开发人员避免底层代码的重复开发,默认就拥有最佳实践,尤其在性能的方面。
针对“基础,文本,数字,日期,文件,集合,并发,反射,日志脱敏”这些开发人员的日常,VJKit做了两件事情:
一是对[Guava](https://github.com/google/guava) 与[Common Lang](https://github.com/apache/commons-lang)中最常用的API的提炼归类,避免了大家直面茫茫多的API(但有些工具类如Guava Cache还是建议直接使用,详见[直用三方工具类](docs/direct_3rd.md) )
二是对各门各派的精华的借鉴移植:比如一些大项目的附送基础库: [Netty](https://github.com/netty/netty/),[ElasticSearch](https://github.com/elastic/elasticsearch), 一些专业的基础库 : [Jodd](https://github.com/oblac/jodd/), [commons-io](https://github.com/apache/commons-io), [commons-collections](https://github.com/apache/commons-collections); 一些大厂的基础库:[Facebook JCommon](https://github.com/facebook/jcommon),[twitter commons](https://github.com/twitter/commons)
具体使用文档请在IDE中阅读JavaDoc,以及对应的单元测试写法。
* [日志脱敏](docs/data_masking.md)
# 2. Usage
Maven :
```
com.vip.vjtools
vjkit
1.0.8
```
Download: [](http://search.maven.org/#search|gav|1|g:"com.vip.vjtools"%20AND%20a:"vjkit")
# 3. Dependency
要求JDK 7.0及以上版本。
| Project | Version | Optional|
|--- | --- | --- |
|[Guava](https://github.com/google/guava) | 20.0 ||
|[Apache Common Lang](https://github.com/apache/commons-lang) | 3.7 ||
|[Slf4j](https://www.slf4j.org) | 1.7.25 ||
|[Dozer](http://dozermapper.github.io/) | 5.5.1 |Optional for BeanMapper,[选型](https://github.com/vipshop/vjtools/blob/master/vjkit/src/main/java/com/vip/vjtools/vjkit/reflect/BeanMapper.java#L11) |
如果使用Optional的依赖,请参考pom文件在业务项目自行引入
# 4. Sonar Status
[https://sonarcloud.io/dashboard?id=com.vip.vjtools:vjkit](https://sonarcloud.io/dashboard?id=com.vip.vjtools:vjkit)
(sonarcloud use "Sonar Way")
================================================
FILE: vjkit/docs/data_masking.md
================================================
# 1.简介
vjmask是唯品会的日志脱敏组件,在业务中广泛使用。基于性能和通用性考虑,采用了现在的方案。让使用方用最少的配置和代码,就可以轻松实现敏感信息过滤。
# 2.使用
## 2.1 依赖
```
com.vip.vjtools
vjkit
${version}
```
## 2.2 最佳实践
脱敏组件支持对单个字符串进行脱敏,也支持toJSON和toString的序列化脱敏
```
import com.vip.vjtools.vjkit.datamasking.DataMask;
// 单个字符串,按中文姓名规则脱敏
logger.info("some sensitive info:{}",DataMask.mask(name,SensitiveType.Name));
//单个字符串,按默认方式脱敏,结果为 "t***";
logger.info("some sensitive info:{}",DataMask.mask("test"));
//对象json序列化脱敏
logger.info("some sensitive object:{}",DataMask.toJSONString(obj));
//对象toString序列化脱敏
logger.info("some sensitive object:{}",DataMask.toString(obj));
```
对类的序列化脱敏,先要对相关敏感字段标记 @Sensitive,支持字符串类型的字段,包括String、String[]和Collection. 嵌套类的敏感字段也是可以识别的。
```
public class User{
@Sensitive(type = SensitiveType.Phone) type参考2.3
private String phone;
}
```
## 2.3 脱敏规则
我们已经定义实现了常用的脱敏类型,可以直接使用
敏感信息 | 脱敏要求 | 样例 | SensitiveType
---|---|---|---
中文姓名 | 三个字及以下,只显示最后一个字;三个字以上,显示最后两个字 | *明,****小明 | Name
手机号/固定电话 | 只显示前三后三 | 138*****111 | Phone
身份证号 | 显示前五个和后二个字符 | 44010************58 | IDCard
银行卡号 | 显示前四个和后二个字符 | 6228************89 | BankCard
地址 | 保留前9个字符 | 广东省广州市荔湾区****** | Address
电子邮箱 | 只显示前一后一及@和后面的内容 | a***b@abc.com | Email
验证码 | 只显示前一后一 | a**b | Captcha
护照/军官号 | 只显示前二后二 | EI****64 | Passport
账号 | 只显示前一后一 | a****b | Account
密码 | 不显示任意字符 | ********* | Password
散列 | sha1(source+salt) ,可以通过DataMask.setSalt设置salt,用于希望在日志系统中精确找回这条日志的场景,可以再自己hash获得hash值 | 6b76e070c5b5d1b889295506faa8b98e97da7e87 | Hash
# 3.详细介绍
## 3.1 Annotation标注
要使用序列化脱敏,先要对敏感字段标注@Sensitive,注意,只对字符串相关的类型字段会生效。
```
//根据类型来标注
@Sensitive(type = SensitiveType.Name)
private String name;
//也可以自定义掩码规则
@Sensitive(keepChars = 2) //首尾保留2个字符串,如果keepChars = {1,3} 表示头部保留1个字符,尾部保留3个字符
private String[] phone;
// 散列的方式
@Sensitive(type = SensitiveType.Hash)
private String hash;
//默认的方式,只保留第一个字符串
@Sensitive
private List account;
```
## 3.2 映射配置
如果你不想对一个个类字段标注@Sensitive,也可以在resource 目录下新建一个data_mask。properties,添加敏感字段映射
```
#SensitiveType=字段名称
Name=nickName
```
那么nickName字段即使没有标注@Sensitive ,在序列化的时候,也会自动脱敏,按照SentiveType.Name的方式脱敏。
==注意,标注@Sensitive优先级高于文件配置==
组件中已经默认配置映射了以下配置,sys_data_mask.properties
```
Name=chineseName,userName
Phone=phone,phoneNum,mobile,tel,telephone
IDCard=IDCard,IdNo
BankCard=bankCard
Address=address,addr
Email=mail,email
Captcha=captcha
Passport=passport
Account=account
Password=password,passwd
```
# 4.Benchmark
我们使用JMH基准测试,测试了2个使用场景。
## 4.1 场景1
通用场景,场景1包括了10个左右的Sensitive字段。toJSONString() 平均耗时 0.003ms,toString() 平均耗时0.005ms。
```
Benchmark Mode Score Error Units
DataMaskTest.testJson(不脱敏) avgt 0.001 ± 0.001 ms/op
DataMaskTest.testMaskJson avgt 0.003 ± 0.002 ms/op
DataMaskTest.testMaskToString avgt 0.005 ± 0.003 ms/op
DataMaskTest.testToString avgt ≈ 10⁻⁴ ms/op
```
## 4.2 场景2
极限场景,场景2测试超大的类序列化,有160多个字段需要脱敏,3层嵌套。toJSONString() 平均耗时 0.064ms,toString() 平均耗时0.098ms。
```
Benchmark Mode Score Error Units
DataMaskTest.testJson avgt 0.043 ± 0.025 ms/op
DataMaskTest.testMaskJson avgt 0.064 ± 0.002 ms/op
DataMaskTest.testMaskToString avgt 0.098 ± 0.095 ms/op
DataMaskTest.testToString avgt ≈ 10⁻⁴ ms/op
```
## 4.3 结论
脱敏处理的性能和脱敏字段的数量相关。
a.如果需要脱敏的字段不多,时间在0.001ms~0.009ms范围内,对于业务来说,不会造成太多额外开销,在可控范围内。
b.如果需要脱敏的字段特别多,建议要性能测试评估
c.toString() 性能比toJson() 要慢,建议是优先使用toJson()
================================================
FILE: vjkit/docs/direct_3rd.md
================================================
# 建议直接使用的第三方类
| Project | Class |
|--- | --- |
|Common Lang | StringUtils |
| | Validate|
|Guava | Cache |
| | Ordering |
|JDK|Arrays|
| |Collections|
================================================
FILE: vjkit/pom.xml
================================================
4.0.0
com.vip.vjtools
vjkit
1.0.9-SNAPSHOT
jar
vjkit
VIP's core java libraries
20.0
3.8.1
1.7.25
1.1.11
5.5.1
2.9.10.4
4.12
2.6.0
2.18.3
1.2.70
UTF-8
1.7
${java.version}
${java.version}
com.google.guava
guava
${guava.version}
org.apache.commons
commons-lang3
${commons-lang3.version}
org.slf4j
slf4j-api
${slf4j.version}
net.sf.dozer
dozer
${dozer.version}
true
com.fasterxml.jackson.core
jackson-databind
${jackson.version}
true
com.alibaba
fastjson
${fastjson.version}
true
ch.qos.logback
logback-classic
${logback.version}
true
junit
junit
${junit.version}
test
org.assertj
assertj-core
${assertj.version}
test
org.mockito
mockito-core
${mockito.version}
test
dom4j
dom4j
1.6.1
test
jaxen
jaxen
1.1.6
test
org.skyscreamer
jsonassert
1.5.0
test
release
org.apache.maven.plugins
maven-javadoc-plugin
2.10.4
false
attach-javadocs
jar
org.apache.maven.plugins
maven-source-plugin
3.0.1
attach-sources
jar
org.apache.maven.plugins
maven-release-plugin
2.5.3
v.@{project.version}
org.apache.maven.plugins
maven-gpg-plugin
1.6
sign-artifacts
verify
sign
sonar
1.7
**/primitive/**/*.java,**/jsr166e/**/*.java,**/SortedArrayList.java,**/WildcardMatcher.java,**/CsvUtil.java
org.jacoco
jacoco-maven-plugin
0.7.2.201409121644
true
agent-for-ut
prepare-agent
org.sonarsource.scanner.maven
sonar-maven-plugin
3.4.0.905
jdk9
9
org.apache.maven.plugins
maven-surefire-plugin
2.22.0
--add-modules java.xml.bind
sonatype-nexus-snapshots
https://oss.sonatype.org/content/repositories/snapshots
sonatype-nexus-releases
https://oss.sonatype.org/service/local/staging/deploy/maven2
https://github.com/vipshop/vjtools
Apache License 2.0
http://www.apache.org/licenses/LICENSE-2.0.txt
repo
scm:git:https://github.com/vipshop/vjtools.git
scm:git:https://github.com/vipshop/vjtools.git
https://github.com/vipshop/vjtools
v.1.0.2
calvin
Calvin Xiao
calvin.xiao at vipshop.com
developer
+8
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/BooleanUtil.java
================================================
package com.vip.vjtools.vjkit.base;
import org.apache.commons.lang3.BooleanUtils;
/**
* 1. 从String(true/false, yes/no),转换为Boolean或boolean
*
* 2. 逻辑运算:取反,多个boolean的and,or 计算
*
* 封装 {@code org.apache.commons.lang3.BooleanUtils}
*/
public class BooleanUtil {
/**
* 使用标准JDK,只分析是否忽略大小写的"true", str为空时返回false
*/
public static boolean toBoolean(String str) {
return Boolean.parseBoolean(str);
}
/**
* 使用标准JDK,只分析是否忽略大小写的"true", str为空时返回null
*/
public static Boolean toBooleanObject(String str) {
return str != null ? Boolean.valueOf(str) : null;
}
/**
* 使用标准JDK,只分析是否忽略大小写的"true", str为空时返回defaultValue
*/
public static Boolean toBooleanObject(String str, Boolean defaultValue) {
return str != null ? Boolean.valueOf(str) : defaultValue;
}
/**
* 支持true/false, on/off, y/n, yes/no的转换, str为空或无法分析时返回null
*/
public static Boolean parseGeneralString(String str) {
return BooleanUtils.toBooleanObject(str);
}
/**
* 支持true/false,on/off, y/n, yes/no的转换, str为空或无法分析时返回defaultValue
*/
public static Boolean parseGeneralString(String str, Boolean defaultValue) {
return BooleanUtils.toBooleanDefaultIfNull(BooleanUtils.toBooleanObject(str), defaultValue);
}
/**
* 取反
*/
public static boolean negate(final boolean bool) {
return !bool;
}
/**
* 取反
*/
public static Boolean negate(final Boolean bool) {
return BooleanUtils.negate(bool);
}
/**
* 多个值的and
*/
public static boolean and(final boolean... array) {
return BooleanUtils.and(array);
}
/**
* 多个值的or
*/
public static boolean or(final boolean... array) {
return BooleanUtils.or(array);
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/EnumUtil.java
================================================
package com.vip.vjtools.vjkit.base;
import java.util.EnumSet;
import org.apache.commons.lang3.EnumUtils;
/**
* 枚举工具集
*
* 1. 将多个枚举值按bit与long的转换
*
* 2. 与String的转换
*
* 封装 {@code org.apache.commons.lang3.EnumUtils}
*/
public class EnumUtil {
/**
* 将若干个枚举值转换为long(按bits 1,2,4,8...的方式叠加),用于使用long保存多个选项的情况.
*/
public static > long generateBits(final Class enumClass, final Iterable extends E> values) {
return EnumUtils.generateBitVector(enumClass, values);
}
/**
* 将若干个枚举值转换为long(按bits 1,2,4,8...的方式叠加),用于使用long保存多个选项的情况.
*/
public static > long generateBits(final Class enumClass, final E... values) {
return EnumUtils.generateBitVector(enumClass, values);
}
/**
* long重新解析为若干个枚举值,用于使用long保存多个选项的情况.
*/
public static > EnumSet processBits(final Class enumClass, final long value) {
return EnumUtils.processBitVector(enumClass, value);
}
/**
* Enum转换为String
*/
public static String toString(Enum e) {
return e.name();
}
/**
* String转换为Enum
*/
public static > T fromString(Class enumClass, String value) {
return Enum.valueOf(enumClass, value);
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/ExceptionUtil.java
================================================
package com.vip.vjtools.vjkit.base;
import java.io.PrintWriter;
import java.lang.reflect.UndeclaredThrowableException;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import com.google.common.base.Throwables;
import com.vip.vjtools.vjkit.base.annotation.NotNull;
import com.vip.vjtools.vjkit.base.annotation.Nullable;
import com.vip.vjtools.vjkit.base.type.UncheckedException;
import com.vip.vjtools.vjkit.io.type.StringBuilderWriter;
/**
* 关于异常的工具类.
*
* 1. Checked/Unchecked及Wrap(如ExecutionException)的转换.
*
* 2. 打印Exception的辅助函数. (其中一些来自Common Lang ExceptionUtils)
*
* 3. 查找Cause(其中一些来自Guava Throwables)
*
* 4. StackTrace性能优化相关,尽量使用静态异常避免异常生成时获取StackTrace(Netty)
*
* @see com.vip.vjtools.vjkit.base.type.CloneableException
*/
public class ExceptionUtil {
private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
///// Checked/Unchecked及Wrap(如ExecutionException)的转换/////
/**
* 将CheckedException转换为RuntimeException重新抛出, 可以减少函数签名中的CheckException定义.
*
* CheckedException会用UndeclaredThrowableException包裹,RunTimeException和Error则不会被转变.
*
* copy from Commons Lang 3.5 ExceptionUtils.
*
* 虽然unchecked()里已直接抛出异常,但仍然定义返回值,方便欺骗Sonar。因此本函数也改变了一下返回值
*
* 示例代码:
*
*
* try{ ... }catch(Exception e){ throw unchecked(t); }
*
*
* @see ExceptionUtils#wrapAndThrow(Throwable)
*/
public static RuntimeException unchecked(@Nullable Throwable t) {
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
if (t instanceof Error) {
throw (Error) t;
}
throw new UncheckedException(t);
}
/**
* 如果是著名的包裹类,从cause中获得真正异常. 其他异常则不变.
*
* Future中使用的ExecutionException 与 反射时定义的InvocationTargetException, 真正的异常都封装在Cause中
*
* 前面 unchecked() 使用的UncheckedException同理.
*/
public static Throwable unwrap(@Nullable Throwable t) {
if (t instanceof UncheckedException || t instanceof java.util.concurrent.ExecutionException
|| t instanceof java.lang.reflect.InvocationTargetException
|| t instanceof UndeclaredThrowableException) {
return t.getCause();
}
return t;
}
/**
* 组合unwrap与unchecked,用于处理反射/Callable的异常
*/
public static RuntimeException unwrapAndUnchecked(@Nullable Throwable t) {
throw unchecked(unwrap(t));
}
////// 输出内容相关 //////
/**
* 将StackTrace[]转换为String, 供Logger或e.printStackTrace()外的其他地方使用.
*
* 为了使用StringBuilderWriter,没有用Throwables#getStackTraceAsString(Throwable)
*/
public static String stackTraceText(@NotNull Throwable t) {
StringBuilderWriter stringWriter = new StringBuilderWriter();
t.printStackTrace(new PrintWriter(stringWriter)); // NOSONAR
return stringWriter.toString();
}
/**
* 拼装 短异常类名: 异常信息.
*
* 与Throwable.toString()相比使用了短类名
*
* @see ExceptionUtils#getMessage(Throwable)
*/
public static String toStringWithShortName(@Nullable Throwable t) {
return ExceptionUtils.getMessage(t);
}
/**
* 拼装 短异常类名: 异常信息 <-- RootCause的短异常类名: 异常信息
*/
public static String toStringWithRootCause(@Nullable Throwable t) {
if (t == null) {
return StringUtils.EMPTY;
}
final String clsName = ClassUtils.getShortClassName(t, null);
final String message = StringUtils.defaultString(t.getMessage());
Throwable cause = getRootCause(t);
StringBuilder sb = new StringBuilder(128).append(clsName).append(": ").append(message);
if (cause != t) {
sb.append("; <---").append(toStringWithShortName(cause));
}
return sb.toString();
}
////////// Cause 相关 /////////
/**
* 获取异常的Root Cause.
*
* 如无底层Cause, 则返回自身
*
* @see Throwables#getRootCause(Throwable)
*/
public static Throwable getRootCause(@NotNull Throwable t) {
return Throwables.getRootCause(t);
}
/**
* 获取某种类型的cause,如果没有则返回空
*
* copy from Jodd ExceptionUtil
*/
public static T findCause(@NotNull Throwable throwable, Class cause) {
while (throwable != null) {
if (throwable.getClass().equals(cause)) {
return (T) throwable;
}
throwable = throwable.getCause();
}
return null;
}
/**
* 判断异常是否由某些底层的异常引起.
*/
@SuppressWarnings("unchecked")
public static boolean isCausedBy(@Nullable Throwable throwable,
Class extends Exception>... causeExceptionClasses) {
Throwable cause = throwable;
while (cause != null) {
for (Class extends Exception> causeClass : causeExceptionClasses) {
if (causeClass.isInstance(cause)) {
return true;
}
}
cause = cause.getCause();
}
return false;
}
/////////// StackTrace 性能优化相关////////
/**
* copy from Netty, 为静态异常设置StackTrace.
*
* 对某些已知且经常抛出的异常, 不需要每次创建异常类并很消耗性能的并生成完整的StackTrace. 此时可使用静态声明的异常.
*
* 如果异常可能在多个地方抛出,使用本函数设置抛出的类名和方法名.
*
*
* private static RuntimeException TIMEOUT_EXCEPTION = ExceptionUtil.setStackTrace(new RuntimeException("Timeout"),
* MyClass.class, "mymethod");
*
*/
public static T setStackTrace(@NotNull T throwable, Class> throwClass, String throwClazz) {
throwable.setStackTrace(
new StackTraceElement[] { new StackTraceElement(throwClass.getName(), throwClazz, null, -1) });
return throwable;
}
/**
* 清除StackTrace. 假设StackTrace已生成, 但把它打印出来也有不小的消耗.
*
* 如果不能控制StackTrace的生成,也不能控制它的打印端(如logger),可用此方法暴力清除Trace.
*
* 但Cause链依然不能清除, 只能清除每一个Cause的StackTrace.
*/
public static T clearStackTrace(@NotNull T throwable) {
Throwable cause = throwable;
while (cause != null) {
cause.setStackTrace(EMPTY_STACK_TRACE);
cause = cause.getCause();
}
return throwable;
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/MoreValidate.java
================================================
package com.vip.vjtools.vjkit.base;
import com.vip.vjtools.vjkit.base.annotation.Nullable;
/**
* 参数校验统一使用Apache Common Lang Validate, 补充一些缺少的.
*
* 为什么不用Guava的{@code com.google.common.base.Preconditions} , 一是少打几个字而已, 二是Validate的方法多,比如noNullElements()判断多个元素都不为空
*
* 目前主要参考 {@code com.google.common.math.MathPreconditions} , 补充数字为正数或非负数的校验
*
*/
public class MoreValidate extends org.apache.commons.lang3.Validate {
/**
* 校验为正数则返回该数字,否则抛出异常.
*/
public static int positive(@Nullable String role, int x) {
if (x <= 0) {
throw new IllegalArgumentException(role + " (" + x + ") must be > 0");
}
return x;
}
/**
* 校验为正数则返回该数字,否则抛出异常.
*/
public static Integer positive(@Nullable String role, Integer x) {
if (x.intValue() <= 0) {
throw new IllegalArgumentException(role + " (" + x + ") must be > 0");
}
return x;
}
/**
* 校验为正数则返回该数字,否则抛出异常.
*/
public static long positive(@Nullable String role, long x) {
if (x <= 0) {
throw new IllegalArgumentException(role + " (" + x + ") must be > 0");
}
return x;
}
/**
* 校验为正数则返回该数字,否则抛出异常.
*/
public static Long positive(@Nullable String role, Long x) {
if (x.longValue() <= 0) {
throw new IllegalArgumentException(role + " (" + x + ") must be > 0");
}
return x;
}
/**
* 校验为正数则返回该数字,否则抛出异常.
*/
public static double positive(@Nullable String role, double x) {
if (!(x > 0)) { // not x < 0, to work with NaN.
throw new IllegalArgumentException(role + " (" + x + ") must be >= 0");
}
return x;
}
/**
* 校验为非负数则返回该数字,否则抛出异常.
*/
public static int nonNegative(@Nullable String role, int x) {
if (x < 0) {
throw new IllegalArgumentException(role + " (" + x + ") must be >= 0");
}
return x;
}
/**
* 校验为非负数则返回该数字,否则抛出异常.
*/
public static Integer nonNegative(@Nullable String role, Integer x) {
if (x.intValue() < 0) {
throw new IllegalArgumentException(role + " (" + x + ") must be >= 0");
}
return x;
}
/**
* 校验为非负数则返回该数字,否则抛出异常.
*/
public static long nonNegative(@Nullable String role, long x) {
if (x < 0) {
throw new IllegalArgumentException(role + " (" + x + ") must be >= 0");
}
return x;
}
/**
* 校验为非负数则返回该数字,否则抛出异常.
*/
public static Long nonNegative(@Nullable String role, Long x) {
if (x.longValue() < 0) {
throw new IllegalArgumentException(role + " (" + x + ") must be >= 0");
}
return x;
}
/**
* 校验为非负数则返回该数字,否则抛出异常.
*/
public static double nonNegative(@Nullable String role, double x) {
if (!(x >= 0)) { // not x < 0, to work with NaN.
throw new IllegalArgumentException(role + " (" + x + ") must be >= 0");
}
return x;
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/ObjectUtil.java
================================================
package com.vip.vjtools.vjkit.base;
import java.util.Arrays;
import com.google.common.base.Objects;
import com.vip.vjtools.vjkit.base.annotation.Nullable;
/**
* 1. Object打印优化,主要解决数组的打印
*
* 2. 多个对象的HashCode串联
*/
public class ObjectUtil {
private static final String NULL = "null";
/**
* JDK7 引入的Null安全的equals
*/
public static boolean equals(@Nullable Object a, @Nullable Object b) {
return Objects.equal(a, b);
}
/**
* 多个对象的HashCode串联, 组成新的HashCode
*/
public static int hashCode(Object... objects) {
return Arrays.hashCode(objects);
}
/**
* 对象的toString(), 处理了对象为数组的情况,JDK的默认toString()只打数组的地址如 "[Ljava.lang.Integer;@490d6c15.
*/
public static String toPrettyString(Object value) {
if (value == null) {
return NULL;
}
Class> type = value.getClass();
if (type.isArray()) {
Class componentType = type.getComponentType();
if (componentType.isPrimitive()) {
return primitiveArrayToString(value, componentType);
} else {
return objectArrayToString(value);
}
} else if (value instanceof Iterable) {
// 因为Collection的处理也是默认调用元素的toString(),
// 为了处理元素是数组的情况,同样需要重载
return collectionToString(value);
}
return value.toString();
}
private static String primitiveArrayToString(Object value, Class componentType) {
StringBuilder sb = new StringBuilder();
if (componentType == int.class) {
sb.append(Arrays.toString((int[]) value));
} else if (componentType == long.class) {
sb.append(Arrays.toString((long[]) value));
} else if (componentType == double.class) {
sb.append(Arrays.toString((double[]) value));
} else if (componentType == float.class) {
sb.append(Arrays.toString((float[]) value));
} else if (componentType == boolean.class) {
sb.append(Arrays.toString((boolean[]) value));
} else if (componentType == short.class) {
sb.append(Arrays.toString((short[]) value));
} else if (componentType == byte.class) {
sb.append(Arrays.toString((byte[]) value));
} else if (componentType == char.class) {
sb.append(Arrays.toString((char[]) value));
} else {
throw new IllegalArgumentException("unsupport array type");
}
return sb.toString();
}
private static String objectArrayToString(Object value) {
StringBuilder sb = new StringBuilder();
sb.append('[');
Object[] array = (Object[]) value;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(toPrettyString(array[i]));
}
sb.append(']');
return sb.toString();
}
private static String collectionToString(Object value) {
Iterable iterable = (Iterable) value;
StringBuilder sb = new StringBuilder();
sb.append('{');
int i = 0;
for (Object o : iterable) {
if (i > 0) {
sb.append(',');
}
sb.append(toPrettyString(o));
i++;
}
sb.append('}');
return sb.toString();
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/Platforms.java
================================================
package com.vip.vjtools.vjkit.base;
import java.io.File;
import org.apache.commons.lang3.SystemUtils;
/**
* 关于系统设定,平台信息的变量(via Common Lang SystemUtils)
*/
public class Platforms {
// 文件路径分隔符
public static final String FILE_PATH_SEPARATOR = File.separator;
public static final char FILE_PATH_SEPARATOR_CHAR = File.separatorChar;
public static final char WINDOWS_FILE_PATH_SEPARATOR_CHAR = '\\';
public static final char LINUX_FILE_PATH_SEPARATOR_CHAR = '/';
// ClassPath分隔符
public static final String CLASS_PATH_SEPARATOR = File.pathSeparator;
public static final char CLASS_PATH_SEPARATOR_CHAR = File.pathSeparatorChar;
// 换行符
public static final String LINE_SEPARATOR = System.lineSeparator();
// 临时目录
public static final String TMP_DIR = SystemUtils.JAVA_IO_TMPDIR;
// 应用的工作目录
public static final String WORKING_DIR = SystemUtils.USER_DIR;
// 用户 HOME目录
public static final String USER_HOME = SystemUtils.USER_HOME;
// Java HOME目录
public static final String JAVA_HOME = SystemUtils.JAVA_HOME;
// Java版本
public static final String JAVA_SPECIFICATION_VERSION = SystemUtils.JAVA_SPECIFICATION_VERSION; // e.g. 1.8
public static final String JAVA_VERSION = SystemUtils.JAVA_VERSION; // e.g. 1.8.0_102
public static final boolean IS_JAVA7 = SystemUtils.IS_JAVA_1_7;
public static final boolean IS_JAVA8 = SystemUtils.IS_JAVA_1_8;
public static final boolean IS_ATLEASET_JAVA7 = IS_JAVA7 || IS_JAVA8;
public static final boolean IS_ATLEASET_JAVA8 = IS_JAVA8;
// 操作系统类型及版本
public static final String OS_NAME = SystemUtils.OS_NAME;
public static final String OS_VERSION = SystemUtils.OS_VERSION;
public static final String OS_ARCH = SystemUtils.OS_ARCH; // e.g. x86_64
public static final boolean IS_LINUX = SystemUtils.IS_OS_LINUX;
public static final boolean IS_UNIX = SystemUtils.IS_OS_UNIX;
public static final boolean IS_WINDOWS = SystemUtils.IS_OS_WINDOWS;
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/PropertiesUtil.java
================================================
package com.vip.vjtools.vjkit.base;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vip.vjtools.vjkit.io.URLResourceUtil;
import com.vip.vjtools.vjkit.number.NumberUtil;
import com.vip.vjtools.vjkit.text.Charsets;
/**
* 关于Properties的工具类
*
* 1. 统一风格读取Properties到各种数据类型
*
* 2. 从文件或字符串装载Properties
*/
public class PropertiesUtil {
private static Logger logger = LoggerFactory.getLogger(PropertiesUtil.class);
/////////////////// 读取Properties ////////////////////
public static Boolean getBoolean(Properties p, String name, Boolean defaultValue) {
return BooleanUtil.toBooleanObject(p.getProperty(name), defaultValue);
}
public static Integer getInt(Properties p, String name, Integer defaultValue) {
return NumberUtil.toIntObject(p.getProperty(name), defaultValue);
}
public static Long getLong(Properties p, String name, Long defaultValue) {
return NumberUtil.toLongObject(p.getProperty(name), defaultValue);
}
public static Double getDouble(Properties p, String name, Double defaultValue) {
return NumberUtil.toDoubleObject(p.getProperty(name), defaultValue);
}
public static String getString(Properties p, String name, String defaultValue) {
return p.getProperty(name, defaultValue);
}
/////////// 加载Properties////////
/**
* 从文件路径加载properties. 默认使用utf-8编码解析文件
*
* 路径支持从外部文件或resources文件加载, "file://"或无前缀代表外部文件, "classpath:"代表resources
*/
public static Properties loadFromFile(String generalPath) {
Properties p = new Properties();
try (Reader reader = new InputStreamReader(URLResourceUtil.asStream(generalPath), Charsets.UTF_8)) {
p.load(reader);
} catch (IOException e) {
logger.warn("Load property from " + generalPath + " failed", e);
}
return p;
}
/**
* 从字符串内容加载Properties
*/
public static Properties loadFromString(String content) {
Properties p = new Properties();
try (Reader reader = new StringReader(content)) {
p.load(reader);
} catch (IOException ignored) { // NOSONAR
}
return p;
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/RuntimeUtil.java
================================================
package com.vip.vjtools.vjkit.base;
import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang3.StringUtils;
/**
* 运行时工具类
*
* 1.取得当前进程PID, JVM参数
*
* 2.注册JVM关闭钩子, 获得CPU核数
*
* 3.通过StackTrace 获得当前方法的类名方法名,调用者的类名方法名(获取StackTrace有消耗,不要滥用)
*/
public class RuntimeUtil {
private static AtomicInteger shutdownHookThreadIndex = new AtomicInteger(0);
/////// RuntimeMXBean相关 //////
/**
* 获得当前进程的PID
*
* 当失败时返回-1
*/
public static int getPid() {
// format: "pid@hostname"
String jvmName = ManagementFactory.getRuntimeMXBean().getName();
String[] split = jvmName.split("@");
if (split.length != 2) {
return -1;
}
try {
return Integer.parseInt(split[0]);
} catch (Exception e) { // NOSONAR
return -1;
}
}
/**
* 返回应用启动到现在的毫秒数
*/
public static long getUpTime() {
return ManagementFactory.getRuntimeMXBean().getUptime();
}
/**
* 返回输入的JVM参数列表
*/
public static String getVmArguments() {
List vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
return StringUtils.join(vmArguments, " ");
}
//////////// Runtime 相关////////////////
/**
* 获取CPU核数
*/
public static int getCores() {
return Runtime.getRuntime().availableProcessors();
}
/**
* 注册JVM关闭时的钩子程序
*/
public static void addShutdownHook(Runnable runnable) {
Runtime.getRuntime().addShutdownHook(
new Thread(runnable, "Thread-ShutDownHook-" + shutdownHookThreadIndex.incrementAndGet()));
}
//////// 通过StackTrace 获得当前方法的调用者 ////
/**
* 通过StackTrace,获得调用者的类名.
*
* 获取StackTrace有消耗,不要滥用
*/
public static String getCallerClass() {
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
if (stacktrace.length >= 4) {
StackTraceElement element = stacktrace[3];
return element.getClassName();
} else {
return StringUtils.EMPTY;
}
}
/**
* 通过StackTrace,获得调用者的"类名.方法名()"
*
* 获取StackTrace有消耗,不要滥用
*/
public static String getCallerMethod() {
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
if (stacktrace.length >= 4) {
StackTraceElement element = stacktrace[3];
return element.getClassName() + '.' + element.getMethodName() + "()";
} else {
return StringUtils.EMPTY;
}
}
/**
* 通过StackTrace,获得当前方法的类名.
*
* 获取StackTrace有消耗,不要滥用
*/
public static String getCurrentClass() {
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
if (stacktrace.length >= 3) {
StackTraceElement element = stacktrace[2];
return element.getClassName();
} else {
return StringUtils.EMPTY;
}
}
/**
* 通过StackTrace,获得当前方法的"类名.方法名()"
*
* 获取StackTrace有消耗,不要滥用
*/
public static String getCurrentMethod() {
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
if (stacktrace.length >= 3) {
StackTraceElement element = stacktrace[2];
return element.getClassName() + '.' + element.getMethodName() + "()";
} else {
return StringUtils.EMPTY;
}
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/SystemPropertiesUtil.java
================================================
package com.vip.vjtools.vjkit.base;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CopyOnWriteArrayList;
import com.vip.vjtools.vjkit.number.NumberUtil;
/**
* 关于SystemProperties的工具类
*
* 1. 统一风格的读取系统变量到各种数据类型,其中Boolean.readBoolean的风格不统一,Double则不支持,都进行了扩展.
*
* 2. 简单的合并系统变量(-D),环境变量 和默认值,以系统变量优先,在未引入Commons Config时使用.
*
* 3. Properties 本质上是一个HashTable,每次读写都会加锁,所以不支持频繁的System.getProperty(name)来检查系统内容变化 因此扩展了一个ListenableProperties,
* 在其所关心的属性变化时进行通知.
*/
public class SystemPropertiesUtil {
/**
* 读取Boolean类型的系统变量,为空时返回null,代表未设置,而不是Boolean.getBoolean()的false.
*/
public static Boolean getBoolean(String name) {
String stringResult = System.getProperty(name);
return BooleanUtil.toBooleanObject(stringResult);
}
/**
* 读取Boolean类型的系统变量,为空时返回默认值, 而不是Boolean.getBoolean()的false.
*/
public static Boolean getBoolean(String name, Boolean defaultValue) {
String stringResult = System.getProperty(name);
return BooleanUtil.toBooleanObject(stringResult, defaultValue);
}
/**
* 读取String类型的系统变量,为空时返回null.
*/
public static String getString(String name) {
return System.getProperty(name);
}
/**
* 读取String类型的系统变量,为空时返回默认值
*/
public static String getString(String name, String defaultValue) {
return System.getProperty(name, defaultValue);
}
/**
* 读取Integer类型的系统变量,为空时返回null.
*/
public static Integer getInteger(String name) {
return Integer.getInteger(name);
}
/**
* 读取Integer类型的系统变量,为空时返回默认值
*/
public static Integer getInteger(String name, Integer defaultValue) {
return Integer.getInteger(name, defaultValue);
}
/**
* 读取Long类型的系统变量,为空时返回null.
*/
public static Long getLong(String name) {
return Long.getLong(name);
}
/**
* 读取Integer类型的系统变量,为空时返回默认值
*/
public static Long getLong(String name, Long defaultValue) {
return Long.getLong(name, defaultValue);
}
/**
* 读取Double类型的系统变量,为空时返回null.
*/
public static Double getDouble(String propertyName) {
return NumberUtil.toDoubleObject(System.getProperty(propertyName), null);
}
/**
* 读取Double类型的系统变量,为空时返回默认值.
*/
public static Double getDouble(String propertyName, Double defaultValue) {
Double propertyValue = NumberUtil.toDoubleObject(System.getProperty(propertyName), null);
return propertyValue != null ? propertyValue : defaultValue;
}
/////////// 简单的合并系统变量(-D),环境变量 和默认值,以系统变量优先.///////////////
/**
* 合并系统变量(-D),环境变量 和默认值,以系统变量优先
*/
public static String getString(String propertyName, String envName, String defaultValue) {
checkEnvName(envName);
String propertyValue = System.getProperty(propertyName);
if (propertyValue != null) {
return propertyValue;
} else {
propertyValue = System.getenv(envName);
return propertyValue != null ? propertyValue : defaultValue;
}
}
/**
* 合并系统变量(-D),环境变量 和默认值,以系统变量优先
*/
public static Integer getInteger(String propertyName, String envName, Integer defaultValue) {
checkEnvName(envName);
Integer propertyValue = NumberUtil.toIntObject(System.getProperty(propertyName), null);
if (propertyValue != null) {
return propertyValue;
} else {
propertyValue = NumberUtil.toIntObject(System.getenv(envName), null);
return propertyValue != null ? propertyValue : defaultValue;
}
}
/**
* 合并系统变量(-D),环境变量 和默认值,以系统变量优先
*/
public static Long getLong(String propertyName, String envName, Long defaultValue) {
checkEnvName(envName);
Long propertyValue = NumberUtil.toLongObject(System.getProperty(propertyName), null);
if (propertyValue != null) {
return propertyValue;
} else {
propertyValue = NumberUtil.toLongObject(System.getenv(envName), null);
return propertyValue != null ? propertyValue : defaultValue;
}
}
/**
* 合并系统变量(-D),环境变量 和默认值,以系统变量优先
*/
public static Double getDouble(String propertyName, String envName, Double defaultValue) {
checkEnvName(envName);
Double propertyValue = NumberUtil.toDoubleObject(System.getProperty(propertyName), null);
if (propertyValue != null) {
return propertyValue;
} else {
propertyValue = NumberUtil.toDoubleObject(System.getenv(envName), null);
return propertyValue != null ? propertyValue : defaultValue;
}
}
/**
* 合并系统变量(-D),环境变量 和默认值,以系统变量优先
*/
public static Boolean getBoolean(String propertyName, String envName, Boolean defaultValue) {
checkEnvName(envName);
Boolean propertyValue = BooleanUtil.toBooleanObject(System.getProperty(propertyName), null);
if (propertyValue != null) {
return propertyValue;
} else {
propertyValue = BooleanUtil.toBooleanObject(System.getenv(envName), null);
return propertyValue != null ? propertyValue : defaultValue;
}
}
/**
* 检查环境变量名不能有'.',在linux下不支持
*/
private static void checkEnvName(String envName) {
if (envName == null || envName.indexOf('.') != -1) {
throw new IllegalArgumentException("envName " + envName + "is null or has dot which is not valid");
}
}
/////////// ListenableProperties /////////////
/**
* Properties 本质上是一个HashTable,每次读写都会加锁,所以不支持频繁的System.getProperty(name)来检查系统内容变化 因此扩展了一个ListenableProperties,
* 在其所关心的属性变化时进行通知.
*
* @see ListenableProperties
*/
public static synchronized void registerSystemPropertiesListener(PropertiesListener listener) {
Properties currentProperties = System.getProperties();
// 将System的properties实现替换为ListenableProperties
if (!(currentProperties instanceof ListenableProperties)) {
ListenableProperties newProperties = new ListenableProperties(currentProperties);
System.setProperties(newProperties);
currentProperties = newProperties;
}
((ListenableProperties) currentProperties).register(listener);
}
/**
* Properties 本质上是一个HashTable,每次读写都会加锁,所以不支持频繁的System.getProperty(name)来检查系统内容变化 因此扩展了Properties子类,
* 在其所关心的属性变化时进行通知.
*
* @see PropertiesListener
*/
public static class ListenableProperties extends Properties {
private static final long serialVersionUID = -8282465702074684324L;
protected transient List listeners = new CopyOnWriteArrayList();
public ListenableProperties(Properties properties) {
super(properties);
}
public void register(PropertiesListener listener) {
listeners.add(listener);
}
@Override
public synchronized Object setProperty(String key, String value) {
Object result = put(key, value);
for (PropertiesListener listener : listeners) {
if (listener.propertyName.equals(key)) {
listener.onChange(key, value);
}
}
return result;
}
}
/**
* 获取所关心的Property变更的Listener基类.
*/
public abstract static class PropertiesListener {
// 关心的Property
protected String propertyName;
public PropertiesListener(String propertyName) {
this.propertyName = propertyName;
}
public abstract void onChange(String propertyName, String value);
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/ValueValidator.java
================================================
package com.vip.vjtools.vjkit.base;
import org.apache.commons.lang3.StringUtils;
/**
* 数值校验取值器
*
* 提供对配置值进行校验,并根据结果决定是否使用默认值。
*
* Guava, Commons Lang里的Validate类用于判断并抛异常。
*
* 而ValueValidator的行为是取默认值,多用于配置值的处理。
*
* 除默认提供的Validator,可自行扩写。
*/
public class ValueValidator {
/**
* 对目标值进行校验,并根据校验结果取值
*
* 使用示例(校验目标值是否大于0, 如果小于 0 则取值为 1)
*
* ValueValidator.checkAndGet(idleTime, 1, Validator.INTEGER_GT_ZERO_VALIDATOR)
*
* @param value 校验值
* @param defaultValue 校验失败默认值
* @param v 校验器
* @return 经Validator校验后的返回值,校验成功返回 value, 校验失败返回 defaultValue
*/
public static T checkAndGet(T value, T defaultValue, Validator v) {
if (v.validate(value)) {
return value;
}
return defaultValue;
}
/**
* 对值进行规则匹配的验证器
*/
public interface Validator {
/**
* 校验值是否匹配
*/
boolean validate(T value);
/**
* 校验器: 数值配置不为null, 且大于0较验
*/
Validator INTEGER_GT_ZERO_VALIDATOR = new Validator() {
@Override
public boolean validate(Integer value) {
return (value != null && value > 0);
}
};
/**
* 校验器: 字符串不为空串较验
*/
Validator STRING_EMPTY_VALUE_VALIDATOR = new Validator() {
@Override
public boolean validate(String value) {
return StringUtils.isNotEmpty(value);
}
};
/**
* 校验器: BOOL字符串较验
*/
Validator STRICT_BOOL_VALUE_VALIDATOR = new Validator() {
@Override
public boolean validate(String value) {
return "true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value);
}
};
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/annotation/NotNull.java
================================================
package com.vip.vjtools.vjkit.base.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* 标注参数、属性、方法不可为 Null
*/
@Target({ ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD })
public @interface NotNull {
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/annotation/Nullable.java
================================================
package com.vip.vjtools.vjkit.base.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* 标注参数、属性、方法可为 Null
*/
@Target({ ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD })
public @interface Nullable {
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/annotation/VisibleForTesting.java
================================================
package com.vip.vjtools.vjkit.base.annotation;
/**
* 标注因为方便UT,将方法/属性的可访问范围扩大了,参考Guava
*/
public @interface VisibleForTesting {
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/CloneableException.java
================================================
package com.vip.vjtools.vjkit.base.type;
import com.vip.vjtools.vjkit.base.ExceptionUtil;
/**
* 适用于异常信息需要变更的情况, 可通过clone(),不经过构造函数(也就避免了获得StackTrace)地从之前定义的静态异常中克隆,再设定新的异常信息
*
* private static CloneableException TIMEOUT_EXCEPTION = new CloneableException("Timeout") .setStackTrace(My.class,
* "hello"); ...
*
* throw TIMEOUT_EXCEPTION.clone("Timeout for 40ms");
*
*/
public class CloneableException extends Exception implements Cloneable {
private static final long serialVersionUID = -6270471689928560417L;
protected String message; // NOSONAR
public CloneableException() {
super((Throwable) null);
}
public CloneableException(String message) {
super((Throwable) null);
this.message = message;
}
public CloneableException(String message, Throwable cause) {
super(cause);
this.message = message;
}
@Override
public CloneableException clone() { // NOSONAR
try {
return (CloneableException) super.clone();
} catch (CloneNotSupportedException e) {// NOSONAR
return null;
}
}
@Override
public String getMessage() {
return message;
}
/**
* 简便函数,定义静态异常时使用
*/
public CloneableException setStackTrace(Class> throwClazz, String throwMethod) {
ExceptionUtil.setStackTrace(this, throwClazz, throwMethod);
return this;
}
/**
* 简便函数, clone并重新设定Message
*/
public CloneableException clone(String message) {
CloneableException newException = this.clone();
newException.setMessage(message);
return newException;
}
/**
* 简便函数, 重新设定Message
*/
public CloneableException setMessage(String message) {
this.message = message;
return this;
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/CloneableRuntimeException.java
================================================
package com.vip.vjtools.vjkit.base.type;
import com.vip.vjtools.vjkit.base.ExceptionUtil;
/**
* 适用于异常信息需要变更的情况, 可通过clone(),不经过构造函数(也就避免了获得StackTrace)地从之前定义的静态异常中克隆,再设定新的异常信息
*
* @see CloneableException
*/
public class CloneableRuntimeException extends RuntimeException implements Cloneable {
private static final long serialVersionUID = 3984796576627959400L;
protected String message; // NOSONAR
public CloneableRuntimeException() {
super((Throwable) null);
}
public CloneableRuntimeException(String message) {
super((Throwable) null);
this.message = message;
}
public CloneableRuntimeException(String message, Throwable cause) {
super(cause);
this.message = message;
}
@Override
public CloneableRuntimeException clone() { // NOSONAR
try {
return (CloneableRuntimeException) super.clone();
} catch (CloneNotSupportedException e) { // NOSONAR
return null;
}
}
@Override
public String getMessage() {
return message;
}
/**
* 简便函数,定义静态异常时使用
*/
public CloneableRuntimeException setStackTrace(Class> throwClazz, String throwMethod) {
ExceptionUtil.setStackTrace(this, throwClazz, throwMethod);
return this;
}
/**
* 简便函数, clone并重新设定Message
*/
public CloneableRuntimeException clone(String message) {
CloneableRuntimeException newException = this.clone();
newException.setMessage(message);
return newException;
}
/**
* 简便函数, 重新设定Message
*/
public CloneableRuntimeException setMessage(String message) {
this.message = message;
return this;
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/Pair.java
================================================
package com.vip.vjtools.vjkit.base.type;
import com.vip.vjtools.vjkit.base.annotation.Nullable;
/**
* 引入一个简简单单的Pair, 用于返回值返回两个元素.
*
* copy from Twitter Common
*/
public class Pair {
@Nullable
private final L left;
@Nullable
private final R right;
/**
* Creates a new pair.
*/
public Pair(@Nullable L left, @Nullable R right) {
this.left = left;
this.right = right;
}
@Nullable
public L getLeft() {
return left;
}
@Nullable
public R getRight() {
return right;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((left == null) ? 0 : left.hashCode());
return prime * result + ((right == null) ? 0 : right.hashCode());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Pair other = (Pair) obj;
if (left == null) {
if (other.left != null) {
return false;
}
} else if (!left.equals(other.left)) {
return false;
}
if (right == null) {
if (other.right != null) {
return false;
}
} else if (!right.equals(other.right)) {
return false;
}
return true;
}
@Override
public String toString() {
return "Pair [left=" + left + ", right=" + right + ']';
}
/**
* 根据等号左边的泛型,自动构造合适的Pair
*/
public static Pair of(@Nullable L left, @Nullable R right) {
return new Pair(left, right);
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/Triple.java
================================================
package com.vip.vjtools.vjkit.base.type;
import com.vip.vjtools.vjkit.base.annotation.Nullable;
/**
* 引入一个简简单单的Triple, 用于返回值返回三个元素.
*
* copy from Twitter Common
*/
public class Triple {
@Nullable
private final L left;
@Nullable
private final M middle;
@Nullable
private final R right;
/**
* Creates a new Triple.
*/
public Triple(@Nullable L left, @Nullable M middle, @Nullable R right) {
this.left = left;
this.middle = middle;
this.right = right;
}
@Nullable
public L getLeft() {
return left;
}
@Nullable
public M getMiddle() {
return middle;
}
@Nullable
public R getRight() {
return right;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((left == null) ? 0 : left.hashCode());
result = prime * result + ((middle == null) ? 0 : middle.hashCode());
return prime * result + ((right == null) ? 0 : right.hashCode());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Triple other = (Triple) obj;
if (left == null) {
if (other.left != null) {
return false;
}
} else if (!left.equals(other.left)) {
return false;
}
if (middle == null) {
if (other.middle != null) {
return false;
}
} else if (!middle.equals(other.middle)) {
return false;
}
if (right == null) {
if (other.right != null) {
return false;
}
} else if (!right.equals(other.right)) {
return false;
}
return true;
}
@Override
public String toString() {
return "Triple [left=" + left + ", middle=" + middle + ", right=" + right + ']';
}
/**
* 根据等号左边的泛型,自动构造合适的Triple
*/
public static Triple of(@Nullable L left, @Nullable M middle, @Nullable R right) {
return new Triple(left, middle, right);
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/base/type/UncheckedException.java
================================================
package com.vip.vjtools.vjkit.base.type;
/**
* CheckedException的wrapper.
*
* 返回Message时, 将返回内层Exception的Message.
*/
public class UncheckedException extends RuntimeException {
private static final long serialVersionUID = 4140223302171577501L;
public UncheckedException(Throwable wrapped) {
super(wrapped);
}
@Override
public String getMessage() {
return super.getCause().getMessage();
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/ArrayUtil.java
================================================
package com.vip.vjtools.vjkit.collection;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import com.google.common.collect.ObjectArrays;
import com.google.common.primitives.Doubles;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.vip.vjtools.vjkit.base.annotation.Nullable;
/**
* 数组工具类.
*
* 1. 创建Array的函数
*
* 2. 数组的乱序与contact相加
*
* 3. 从Array转换到Guava的底层为原子类型的List
*
* JDK Arrays的其他函数,如sort(), toString() 请直接调用
*
* Common Lang ArrayUtils的其他函数,如subarray(),reverse(),indexOf(), 请直接调用
*/
public class ArrayUtil {
/**
* 传入类型与大小创建数组.
*
* Array.newInstance()的性能并不差
*/
@SuppressWarnings("unchecked")
public static T[] newArray(Class type, int length) {
return (T[]) Array.newInstance(type, length);
}
/**
* 从collection转为Array, 以 list.toArray(new String[0]); 最快 不需要创建list.size()的数组.
*
* 本函数等价于list.toArray(new String[0]); 用户也可以直接用后者.
*
* https://shipilev.net/blog/2016/arrays-wisdom-ancients/
*/
@SuppressWarnings("unchecked")
public static T[] toArray(Collection col, Class type) {
return col.toArray((T[]) Array.newInstance(type, 0));
}
/**
* Swaps the two specified elements in the specified array.
*/
private static void swap(Object[] arr, int i, int j) {
Object tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
/**
* 将传入的数组乱序
*/
public static T[] shuffle(T[] array) {
if (array != null && array.length > 1) {
Random rand = new Random();
return shuffle(array, rand);
} else {
return array;
}
}
/**
* 将传入的数组乱序
*/
public static T[] shuffle(T[] array, Random random) {
if (array != null && array.length > 1 && random != null) {
for (int i = array.length; i > 1; i--) {
swap(array, i - 1, random.nextInt(i));
}
}
return array;
}
/**
* 添加元素到数组头.
*/
public static T[] concat(@Nullable T element, T[] array) {
return ObjectArrays.concat(element, array);
}
/**
* 添加元素到数组末尾.
*/
public static T[] concat(T[] array, @Nullable T element) {
return ObjectArrays.concat(array, element);
}
////////////////// guava Array 转换为底层为原子类型的List ///////////
/**
* 原版将数组转换为List.
*
* 注意转换后的List不能写入, 否则抛出UnsupportedOperationException
*
* @see java.util.Arrays#asList(Object...)
*/
public static List asList(T... a) {
return Arrays.asList(a);
}
/**
* Arrays.asList()的加强版, 返回一个底层为原始类型int的List
*
* 与保存Integer相比节约空间,同时只在读取数据时AutoBoxing.
*
* @see java.util.Arrays#asList(Object...)
* @see com.google.common.primitives.Ints#asList(int...)
*
*/
public static List intAsList(int... backingArray) {
return Ints.asList(backingArray);
}
/**
* Arrays.asList()的加强版, 返回一个底层为原始类型long的List
*
* 与保存Long相比节约空间,同时只在读取数据时AutoBoxing.
*
* @see java.util.Arrays#asList(Object...)
* @see com.google.common.primitives.Longs#asList(long...)
*/
public static List longAsList(long... backingArray) {
return Longs.asList(backingArray);
}
/**
* Arrays.asList()的加强版, 返回一个底层为原始类型double的List
*
* 与保存Double相比节约空间,同时也避免了AutoBoxing.
*
* @see java.util.Arrays#asList(Object...)
* @see com.google.common.primitives.Doubles#asList(double...)
*/
public static List doubleAsList(double... backingArray) {
return Doubles.asList(backingArray);
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/CollectionUtil.java
================================================
package com.vip.vjtools.vjkit.collection;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Ordering;
import com.vip.vjtools.vjkit.base.type.Pair;
/**
* 通用Collection的工具集
*
* 1. 集合是否为空,取得集合中首个及最后一个元素,判断集合是否完全相等
*
* 2. 集合的最大最小值,及Top N, Bottom N
*
* 关于List, Map, Queue, Set的特殊工具集,另见特定的Util.
*
* 另JDK中缺少ComparableComparator和NullComparator,直到JDK8才补上。
* 因此平时请使用guava的Ordering.natural(),fluentable的API更好用,可以链式设置nullFirst,nullLast,reverse
*
* @see com.google.common.collect.Ordering
*/
public class CollectionUtil {
/**
* 判断是否为空.
*/
public static boolean isEmpty(Collection> collection) {
return (collection == null) || collection.isEmpty();
}
/**
* 判断是否不为空.
*/
public static boolean isNotEmpty(Collection> collection) {
return (collection != null) && !(collection.isEmpty());
}
/**
* 取得Collection的第一个元素,如果collection为空返回null.
*/
public static T getFirst(Collection collection) {
if (isEmpty(collection)) {
return null;
}
if (collection instanceof List) {
return ((List) collection).get(0);
}
return collection.iterator().next();
}
/**
* 获取Collection的最后一个元素,如果collection为空返回null.
*/
public static T getLast(Collection collection) {
if (isEmpty(collection)) {
return null;
}
// 当类型List时,直接取得最后一个元素.
if (collection instanceof List) {
List list = (List) collection;
return list.get(list.size() - 1);
}
return Iterators.getLast(collection.iterator());
}
/**
* 两个集合中的所有元素按顺序相等.
*/
public static boolean elementsEqual(Iterable> iterable1, Iterable> iterable2) {
return Iterables.elementsEqual(iterable1, iterable2);
}
///////////// 求最大最小值,及Top N, Bottom N//////////
/**
* 返回无序集合中的最小值,使用元素默认排序
*/
public static > T min(Collection extends T> coll) {
return Collections.min(coll);
}
/**
* 返回无序集合中的最小值
*/
public static T min(Collection extends T> coll, Comparator super T> comp) {
return Collections.min(coll, comp);
}
/**
* 返回无序集合中的最大值,使用元素默认排序
*/
public static > T max(Collection extends T> coll) {
return Collections.max(coll);
}
/**
* 返回无序集合中的最大值
*/
public static T max(Collection extends T> coll, Comparator super T> comp) {
return Collections.max(coll, comp);
}
/**
* 同时返回无序集合中的最小值和最大值,使用元素默认排序
*
* 在返回的Pair中,第一个为最小值,第二个为最大值
*/
public static > Pair minAndMax(Collection extends T> coll) {
Iterator extends T> i = coll.iterator();
T minCandidate = i.next();
T maxCandidate = minCandidate;
while (i.hasNext()) {
T next = i.next();
if (next.compareTo(minCandidate) < 0) {
minCandidate = next;
} else if (next.compareTo(maxCandidate) > 0) {
maxCandidate = next;
}
}
return Pair.of(minCandidate, maxCandidate);
}
/**
* 返回无序集合中的最小值和最大值
*
* 在返回的Pair中,第一个为最小值,第二个为最大值
*/
public static Pair minAndMax(Collection extends T> coll, Comparator super T> comp) {
Iterator extends T> i = coll.iterator();
T minCandidate = i.next();
T maxCandidate = minCandidate;
while (i.hasNext()) {
T next = i.next();
if (comp.compare(next, minCandidate) < 0) {
minCandidate = next;
} else if (comp.compare(next, maxCandidate) > 0) {
maxCandidate = next;
}
}
return Pair.of(minCandidate, maxCandidate);
}
/**
* 返回Iterable中最大的N个对象, back by guava.
*/
public static > List topN(Iterable coll, int n) {
return Ordering.natural().greatestOf(coll, n);
}
/**
* 返回Iterable中最大的N个对象, back by guava.
*/
public static List topN(Iterable coll, int n, Comparator super T> comp) {
return Ordering.from(comp).greatestOf(coll, n);
}
/**
* 返回Iterable中最小的N个对象, back by guava.
*/
public static > List bottomN(Iterable coll, int n) {
return Ordering.natural().leastOf(coll, n);
}
/**
* 返回Iterable中最小的N个对象, back by guava.
*/
public static List bottomN(Iterable coll, int n, Comparator super T> comp) {
return Ordering.from(comp).leastOf(coll, n);
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/ListUtil.java
================================================
package com.vip.vjtools.vjkit.collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import com.google.common.collect.Lists;
/**
* 关于List的工具集合.
*
* 1. 常用函数(如是否为空,sort/binarySearch/shuffle/reverse(via JDK Collection)
*
* 2. 各种构造函数(from guava and JDK Collection)
*
* 3. 各种扩展List类型的创建函数
*
* 5. 集合运算:交集,并集, 差集, 补集,from Commons Collection,但对其不合理的地方做了修正
*/
@SuppressWarnings("unchecked")
public class ListUtil {
/**
* 判断是否为空.
*/
public static boolean isEmpty(List> list) {
return (list == null) || list.isEmpty();
}
/**
* 判断是否不为空.
*/
public static boolean isNotEmpty(List> list) {
return (list != null) && !(list.isEmpty());
}
/**
* 获取第一个元素, 如果List为空返回 null.
*/
public static T getFirst(List list) {
if (isEmpty(list)) {
return null;
}
return list.get(0);
}
/**
* 获取最后一个元素,如果List为空返回null.
*/
public static T getLast(List list) {
if (isEmpty(list)) {
return null;
}
return list.get(list.size() - 1);
}
///////////////// from Guava的构造函数///////////////////
/**
* 根据等号左边的类型,构造类型正确的ArrayList.
*
* @deprecated JDK7开始已经简化
*/
@Deprecated
public static ArrayList newArrayList() {
return new ArrayList();
}
/**
* 根据等号左边的类型,构造类型正确的ArrayList, 并初始化元素.
*
* @see com.google.common.collect.Lists#newArrayList(Object...)
*/
public static ArrayList newArrayList(T... elements) {
return Lists.newArrayList(elements);
}
/**
* 根据等号左边的类型,构造类型正确的ArrayList, 并初始化元素.
*
* @see com.google.common.collect.Lists#newArrayList(Iterable)
*/
public static ArrayList newArrayList(Iterable elements) {
return Lists.newArrayList(elements);
}
/**
* 根据等号左边的类型,构造类型正确的ArrayList, 并初始化数组大小.
*
* @see com.google.common.collect.Lists#newArrayListWithCapacity(int)
*/
public static ArrayList newArrayListWithCapacity(int initSize) {
return new ArrayList(initSize);
}
/**
* 根据等号左边的类型,构造类型正确的LinkedList.
*
* @deprecated JDK7开始已经简化
*/
@Deprecated
public static LinkedList newLinkedList() {
return new LinkedList();
}
/**
* 根据等号左边的类型,构造类型正确的LinkedList.
*
* @see com.google.common.collect.Lists#newLinkedList()
*/
public static LinkedList newLinkedList(Iterable extends T> elements) {
return Lists.newLinkedList(elements);
}
/**
* 根据等号左边的类型,构造类型正确的CopyOnWriteArrayList.
*
* @deprecated JDK7开始已经简化
*/
@Deprecated
public static CopyOnWriteArrayList newCopyOnWriteArrayList() {
return new CopyOnWriteArrayList();
}
/**
* 根据等号左边的类型,构造类型转换的CopyOnWriteArrayList, 并初始化元素.
*/
public static CopyOnWriteArrayList newCopyOnWriteArrayList(T... elements) {
return new CopyOnWriteArrayList(elements);
}
///////////////// from JDK Collections的常用构造函数 ///////////////////
/**
* 返回一个空的结构特殊的List,节约空间.
*
* 注意返回的List不可写, 写入会抛出UnsupportedOperationException.
*
* @see java.util.Collections#emptyList()
*/
public static final List emptyList() {
return Collections.emptyList();
}
/**
* 如果list为null,转化为一个安全的空List.
*
* 注意返回的List不可写, 写入会抛出UnsupportedOperationException.
*
* @see java.util.Collections#emptyList()
*/
public static List emptyListIfNull(final List list) {
return list == null ? (List) Collections.EMPTY_LIST : list;
}
/**
* 返回只含一个元素但结构特殊的List,节约空间.
*
* 注意返回的List不可写, 写入会抛出UnsupportedOperationException.
*
* @see java.util.Collections#singletonList(Object)
*/
public static List singletonList(T o) {
return Collections.singletonList(o);
}
/**
* 返回包装后不可修改的List.
*
* 如果尝试写入会抛出UnsupportedOperationException.
*
* @see java.util.Collections#unmodifiableList(List)
*/
public static List unmodifiableList(List extends T> list) {
return Collections.unmodifiableList(list);
}
/**
* 返回包装后同步的List,所有方法都会被synchronized原语同步.
*
* 用于CopyOnWriteArrayList与 ArrayDequeue均不符合的场景
*/
public static List synchronizedList(List list) {
return Collections.synchronizedList(list);
}
///////////////// from JDK Collections的常用函数 ///////////////////
/**
* 升序排序, 采用JDK认为最优的排序算法, 使用元素自身的compareTo()方法
*
* @see java.util.Collections#sort(List)
*/
public static > void sort(List list) {
Collections.sort(list);
}
/**
* 倒序排序, 采用JDK认为最优的排序算法,使用元素自身的compareTo()方法
*
* @see java.util.Collections#sort(List)
*/
public static > void sortReverse(List list) {
Collections.sort(list, Collections.reverseOrder());
}
/**
* 升序排序, 采用JDK认为最优的排序算法, 使用Comparator.
*
* @see java.util.Collections#sort(List, Comparator)
*/
public static void sort(List list, Comparator super T> c) {
Collections.sort(list, c);
}
/**
* 倒序排序, 采用JDK认为最优的排序算法, 使用Comparator
*
* @see java.util.Collections#sort(List, Comparator)
*/
public static void sortReverse(List list, Comparator super T> c) {
Collections.sort(list, Collections.reverseOrder(c));
}
/**
* 二分法快速查找对象, 使用Comparable对象自身的比较.
*
* list必须已按升序排序.
*
* 如果不存在,返回一个负数,代表如果要插入这个对象,应该插入的位置
*
* @see java.util.Collections#binarySearch(List, Object)
*/
public static int binarySearch(List extends Comparable super T>> sortedList, T key) {
return Collections.binarySearch(sortedList, key);
}
/**
* 二分法快速查找对象,使用Comparator.
*
* list必须已按升序排序.
*
* 如果不存在,返回一个负数,代表如果要插入这个对象,应该插入的位置
*
* @see java.util.Collections#binarySearch(List, Object, Comparator)
*/
public static int binarySearch(List extends T> sortedList, T key, Comparator super T> c) {
return Collections.binarySearch(sortedList, key, c);
}
/**
* 随机乱序,使用默认的Random.
*
* @see java.util.Collections#shuffle(List)
*/
public static void shuffle(List> list) {
Collections.shuffle(list);
}
/**
* 随机乱序,使用传入的Random.
*
* @see java.util.Collections#shuffle(List, Random)
*/
public static void shuffle(List> list, Random rnd) {
Collections.shuffle(list, rnd);
}
/**
* 返回一个倒转顺序访问的List,仅仅是一个倒序的View,不会实际多生成一个List
*
* @see com.google.common.collect.Lists#reverse(List)
*/
public static List reverse(final List list) {
return Lists.reverse(list);
}
///////////////// from guava的函数 ///////////////////
/**
* List分页函数
*/
public static List> partition(List list, int size) {
return Lists.partition(list, size);
}
///////////////// 其他处理函数 ///////////////
/**
* 清理掉List中的Null对象
*/
public static void notNullList(List list) {
if (isEmpty(list)) {
return;
}
Iterator ite = list.iterator();
while (ite.hasNext()) {
T obj = ite.next();
// 清理掉null的集合
if (null == obj) {
ite.remove();
}
}
}
public static void uniqueNotNullList(List list) {
if (isEmpty(list)) {
return;
}
Iterator ite = list.iterator();
Set set = new HashSet<>((int) (list.size() / 0.75F + 1.0F));
while (ite.hasNext()) {
T obj = ite.next();
// 清理掉null的集合
if (null == obj) {
ite.remove();
continue;
}
// 清理掉重复的集合
if (set.contains(obj)) {
ite.remove();
continue;
}
set.add(obj);
}
}
///////////////// 集合运算 ///////////////////
/**
* list1,list2的并集(在list1或list2中的对象),产生新List
*
* 对比Apache Common Collection4 ListUtils, 优化了初始大小
*/
public static List union(final List extends E> list1, final List extends E> list2) {
final List result = new ArrayList(list1.size() + list2.size());
result.addAll(list1);
result.addAll(list2);
return result;
}
/**
* list1, list2的交集(同时在list1和list2的对象),产生新List
*
* copy from Apache Common Collection4 ListUtils,但其做了不合理的去重,因此重新改为性能较低但不去重的版本
*
* 与List.retainAll()相比,考虑了的List中相同元素出现的次数, 如"a"在list1出现两次,而在list2中只出现一次,则交集里会保留一个"a".
*/
public static List intersection(final List extends T> list1, final List extends T> list2) {
List extends T> smaller = list1;
List extends T> larger = list2;
if (list1.size() > list2.size()) {
smaller = list2;
larger = list1;
}
// 克隆一个可修改的副本
List newSmaller = new ArrayList(smaller);
List result = new ArrayList(smaller.size());
for (final T e : larger) {
if (newSmaller.contains(e)) {
result.add(e);
newSmaller.remove(e);
}
}
return result;
}
/**
* list1, list2的差集(在list1,不在list2中的对象),产生新List.
*
* 与List.removeAll()相比,会计算元素出现的次数,如"a"在list1出现两次,而在list2中只出现一次,则差集里会保留一个"a".
*/
public static List difference(final List extends T> list1, final List extends T> list2) {
final List result = new ArrayList(list1);
final Iterator extends T> iterator = list2.iterator();
while (iterator.hasNext()) {
result.remove(iterator.next());
}
return result;
}
/**
* list1, list2的补集(在list1或list2中,但不在交集中的对象,又叫反交集)产生新List.
*
* copy from Apache Common Collection4 ListUtils,但其并集-交集时,初始大小没有对交集*2,所以做了修改
*/
public static List disjoint(final List extends T> list1, final List extends T> list2) {
List intersection = intersection(list1, list2);
List towIntersection = union(intersection, intersection);
return difference(union(list1, list2), towIntersection);
}
}
================================================
FILE: vjkit/src/main/java/com/vip/vjtools/vjkit/collection/MapUtil.java
================================================
package com.vip.vjtools.vjkit.collection;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import org.apache.commons.lang3.Validate;
import com.google.common.base.Preconditions;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.vip.vjtools.vjkit.base.annotation.NotNull;
import com.vip.vjtools.vjkit.base.annotation.Nullable;
/**
* 关于Map的工具集合,
*
* 1. 常用函数(如是否为空, 两个map的Diff对比,针对value值的排序)
*
* 2. 对于并发Map,增加putIfAbsent(返回最终值版), createIfAbsent这两个重要函数(from Common Lang)
*
* 3. 便捷的构造函数(via guava,Java Collections,并增加了用数组,List等方式初始化Map的函数)
*
* 4. JDK Collections的empty,singleton
*/
@SuppressWarnings("unchecked")
public class MapUtil {
public static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 判断是否为空.
*/
public static boolean isEmpty(final Map, ?> map) {
return (map == null) || map.isEmpty();
}
/**
* 判断是否为空.
*/
public static boolean isNotEmpty(final Map, ?> map) {
return (map != null) && !map.isEmpty();
}
/**
* ConcurrentMap的putIfAbsent()返回之前的Value,此函数封装返回最终存储在Map中的Value
*
* @see org.apache.commons.lang3.concurrent.ConcurrentUtils#putIfAbsent(ConcurrentMap, Object, Object)
*/
public static V putIfAbsentReturnLast(@NotNull final ConcurrentMap map, final K key, final V value) {
final V result = map.putIfAbsent(key, value);
return result != null ? result : value;
}
/**
* 如果Key不存在则创建,返回最后存储在Map中的Value.
*
* 如果创建Value对象有一定成本, 直接使用PutIfAbsent可能重复浪费,则使用此类,传入一个被回调的ValueCreator,Lazy创建对象。
*
* @see org.apache.commons.lang3.concurrent.ConcurrentUtils#createIfAbsent(ConcurrentMap, Object,
* org.apache.commons.lang3.concurrent.ConcurrentInitializer)
*/
public static V createIfAbsentReturnLast(@NotNull final ConcurrentMap map, final K key,
@NotNull final ValueCreator extends V> creator) {
final V value = map.get(key);
if (value == null) {
return putIfAbsentReturnLast(map, key, creator.get());
}
return value;
}
/**
* Lazy创建Value值的回调类
*
* @see MapUtil#createIfAbsentReturnLast(ConcurrentMap, Object, ValueCreator)
*/
public interface ValueCreator {
/**
* 创建对象
*/
T get();
}
///////////////// from Guava的构造函数///////////////////
/**
* 根据等号左边的类型, 构造类型正确的HashMap.
*
* 未初始化数组大小, 默认为16个桶.
*
* @deprecated JDK7开始已经简化
*/
@Deprecated
public static HashMap newHashMap() {
return new HashMap();
}
/**
* 根据等号左边的类型, 构造类型正确的HashMap.
*
* 注意HashMap中有0.75的加载因子的影响, 需要进行运算后才能正确初始化HashMap的大小.
*
* 加载因子也是HashMap中减少Hash冲突的重要一环,如果读写频繁,总记录数不多的Map,可以比默认值0.75进一步降低,建议0.5
*
* @see com.google.common.collect.Maps#newHashMap
*/
public static HashMap newHashMapWithCapacity(int expectedSize, float loadFactor) {
int finalSize = (int) (expectedSize / loadFactor + 1.0F);
return new HashMap(finalSize, loadFactor);
}
/**
* 根据等号左边的类型, 构造类型正确的HashMap.
*
* 同时初始化第一个元素
*/
public static HashMap newHashMap(final K key, final V value) {
HashMap map = new HashMap();
map.put(key, value);
return map;
}
/**
* 根据等号左边的类型, 构造类型正确的HashMap.
*
* 同时初始化元素.
*/
public static HashMap newHashMap(@NotNull final K[] keys, @NotNull final V[] values) {
Validate.isTrue(keys.length == values.length, "keys.length is %d but values.length is %d", keys.length,
values.length);
HashMap map = new HashMap(keys.length * 2);
for (int i = 0; i < keys.length; i++) {
map.put(keys[i], values[i]);
}
return map;
}
/**
* 根据等号左边的类型, 构造类型正确的HashMap.
*
* 同时初始化元素.
*/
public static HashMap newHashMap(@NotNull final List keys, @NotNull final List values) {
Validate.isTrue(keys.size() == values.size(), "keys.length is %s but values.length is %s", keys.size(),
values.size());
HashMap map = new HashMap(keys.size() * 2);
Iterator keyIt = keys.iterator();
Iterator valueIt = values.iterator();
while (keyIt.hasNext()) {
map.put(keyIt.next(), valueIt.next());
}
return map;
}
/**
* 根据等号左边的类型,构造类型正确的TreeMap.
*
* @see com.google.common.collect.Maps#newTreeMap()
*/
@SuppressWarnings("rawtypes")
public static TreeMap newSortedMap() {
return new TreeMap();
}
/**
* 根据等号左边的类型,构造类型正确的TreeMap.
*
* @see com.google.common.collect.Maps#newTreeMap(Comparator)
*/
public static TreeMap newSortedMap(@Nullable Comparator comparator) {
return Maps.newTreeMap(comparator);
}
/**
* 相比HashMap,当key是枚举类时, 性能与空间占用俱佳.
*/
public static , V> EnumMap